【问题标题】:How to get overScroll Height in NestedScrollView?如何在 NestedScrollView 中获取 overScroll 高度?
【发布时间】:2019-10-04 08:34:58
【问题描述】:

我想在 NestedScrollView 中获得过度滚动侦听器,以便在用户滚动时使我的顶部 ImageView 得到缩放。类似this

上面的库使用ScrollView,在我的情况下我需要NestedScrollView。所以我想遵循开发人员的相同方法,但在解决一些问题时遇到了一些麻烦。

View 中有一个protected 方法overScrollByScrollView 中使用,开发人员在他的CustomScrollView 中覆盖了该方法。不幸的是,而不是 overScrollBy NestedScrollView 使用它自己的 overScrollByCombat 这是私有的,我不能覆盖它。所以,我有点纠结于如何在我的CustomNestedScrollView 中获取"overScrollListener"

我能想到的唯一解决方案实际上是制作我的PreCustomNestedScrollView,我只是复制粘贴NestedScrollView 的源代码并将overScrollByCombat 设置为公开。它有效,但我认为这不是一种优雅的方式。

如果已经有与NestedScrollView相同效果的此类库,欢迎推荐。

【问题讨论】:

  • 这个概念类似于折叠工具栏布局@musooff
  • @BrahmaDatta 是的,我知道它类似于折叠工具栏,但我需要使用nestedscrollview 来完成
  • 也许你可以覆盖onOverScrolled()这个方法修饰符被保护,根据android-28中的NestedScrollView.java源代码,onOverScrolled()会在overScrollByCompat()中被调用
  • @Magic 我考虑过,但它会用newScrollY 调用,如果它在顶部,它将始终是0。此方法不会给出 overScroll 的数量。
  • @musooff 我已经检查了您在github link 上方传递的代码。 StretchTopViewExample 覆盖视图的 overScrollBy() 并使用 deltaY 参数来调整 topView 的高度。你可以试试这个。

标签: android android-scrollview android-nestedscrollview overscroll


【解决方案1】:

这里有两种方法可以得到这个。

这是一个演示 LinkGif

  1. 使用 CoordiantorLayout 行为实现

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;


public class OverScrollBounceBehavior extends CoordinatorLayout.Behavior<View> {

    private static final String TAG = "Behavior";

    private int mNormalHeight = 0;
    private int mMaxHeight = 0;
    private float mFactor = 1.8f;
    private int mOverScrollY;
    private View mTargetView;
    private OnScrollChangeListener mListener;

    public OverScrollBounceBehavior() {
    }

    public OverScrollBounceBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                                       @NonNull View child,
                                       @NonNull View directTargetChild,
                                       @NonNull View target,
                                       int nestedScrollAxes, int type) {
        findTargetView();
        Log.d(TAG, "onStartNestedScroll " + "type = " + type);
        //TYPE_TOUCH handle over scroll
        if (checkTouchType(type) && checkTargetView()) {
            mOverScrollY = 0;
            mNormalHeight = mTargetView.getHeight();
            mMaxHeight = (int) (mNormalHeight * mFactor);
        }
        return true;
    }

    public void setFactor(float factor) {
        this.mFactor = factor;
    }

    public void setOnScrollChangeListener(OnScrollChangeListener listener) {
        this.mListener = listener;
    }

    public void setTargetView(View targetView) {
        //set a target view from outside, target view should be NestedScrollView child
        this.mTargetView = targetView;
    }

    private void findTargetView() {
        //implement a fixed find target view as you wish
    }

    @Override
    public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                               @NonNull View child,
                               @NonNull View target,
                               int dxConsumed, int dyConsumed,
                               int dxUnconsumed, int dyUnconsumed,
                               int type) {
        //unconsumed == 0 no overScroll
        //unconsumed > 0 overScroll up
        if (dyUnconsumed >= 0) {
            return;
        }
        Log.d(TAG, "onNestedScroll : dyUnconsumed = " + dyUnconsumed);
        mOverScrollY -= dyUnconsumed;
        Log.d(TAG, "onNestedScroll : mOverScrollY = " + mOverScrollY + "type = " + type);
        //TYPE_TOUCH handle over scroll
        if (checkTouchType(type) && checkTargetView()) {
            if (mOverScrollY > 0 && mTargetView.getLayoutParams().height + Math.abs(mOverScrollY) <= mMaxHeight) {
                mTargetView.getLayoutParams().height += Math.abs(mOverScrollY);
                mTargetView.requestLayout();
                if (mListener != null) {
                    mListener.onScrollChanged(calculateRate(mTargetView, mMaxHeight, mNormalHeight));
                }
            }
        }
    }

    @Override
    public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                                   @NonNull View child,
                                   @NonNull View target,
                                   int type) {
        Log.d(TAG, "onStopNestedScroll" + "type = " + type);
        //TYPE_TOUCH handle over scroll
        if (checkTouchType(type)
                && checkTargetView()
                && mTargetView.getHeight() > mNormalHeight) {
            ResetAnimation animation = new ResetAnimation(mTargetView, mNormalHeight, mListener);
            animation.setDuration(300);
            mTargetView.startAnimation(animation);
        }
    }

    private boolean checkTouchType(int type) {
        return type == ViewCompat.TYPE_TOUCH;
    }

    private boolean checkTargetView() {
        return mTargetView != null;
    }

    public static class ResetAnimation extends Animation {
        int targetHeight;
        int originalHeight;
        int extraHeight;
        View view;
        OnScrollChangeListener listener;

        ResetAnimation(View view, int targetHeight, OnScrollChangeListener listener) {
            this.view = view;
            this.targetHeight = targetHeight;
            this.originalHeight = view.getHeight();
            this.extraHeight = this.targetHeight - originalHeight;
            this.listener = listener;
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            int newHeight = (int) (targetHeight - extraHeight * (1 - interpolatedTime));
            view.getLayoutParams().height = newHeight;
            view.requestLayout();
            if (listener != null) {
                listener.onScrollChanged(calculateRate(view, originalHeight, targetHeight));
            }
        }
    }

    public interface OnScrollChangeListener {
        void onScrollChanged(float rate);
    }

    private static float calculateRate(View targetView, int maxHeight, int targetHeight) {
        float rate = 0;
        if (targetView != null) {
            rate = (maxHeight - (float) targetView.getLayoutParams().height) / (maxHeight - targetHeight);
        }
        return rate;
    }
}

  1. 使用 NestedScrollView 的子类实现

(1)。在包android.support.v4.widget中创建一个委托子类

 and override `overScrollByCompat()` to invoke customized `openedOverScrollByCompat()` method.

(2)。创建你的所有者StretchTopNestedScrollView覆盖

openedOverScrollByCompat() 那么你就可以为所欲为。

代表视图

package android.support.v4.widget;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;

public class OpenedNestedScrollView extends NestedScrollView {

    public OpenedNestedScrollView(@NonNull Context context) {
        this(context, null);
    }

    public OpenedNestedScrollView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, -1);
    }

    public OpenedNestedScrollView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    boolean overScrollByCompat(int deltaX, int deltaY,
                               int scrollX, int scrollY,
                               int scrollRangeX, int scrollRangeY,
                               int maxOverScrollX, int maxOverScrollY,
                               boolean isTouchEvent) {
        return openedOverScrollByCompat(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
    }

    protected boolean openedOverScrollByCompat(int deltaX, int deltaY,
                                               int scrollX, int scrollY,
                                               int scrollRangeX, int scrollRangeY,
                                               int maxOverScrollX, int maxOverScrollY,
                                               boolean isTouchEvent) {
        return super.overScrollByCompat(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
    }
}

您的所有者视图

ublic class StretchTopNestedScrollView extends OpenedNestedScrollView {

    private View mTopView, mBottomView;
    private int mNormalHeight, mMaxHeight;
    private onOverScrollChanged mChangeListener;
    private float mFactor = 1.6f;

    private interface OnTouchEventListener {
        void onTouchEvent(MotionEvent ev);
    }

    public StretchTopNestedScrollView(Context context) {
        this(context, null);
    }

    public StretchTopNestedScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, -1);
    }

    public StretchTopNestedScrollView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public void setFactor(float f) {
        mFactor = f;

        mTopView.postDelayed(new Runnable() {
            @Override
            public void run() {
                mNormalHeight = mTopView.getHeight();
                mMaxHeight = (int) (mNormalHeight * mFactor);
            }
        }, 50);
    }

    public View getTopView() {
        return mTopView;
    }

    public View getBottomView() {
        return mBottomView;
    }

    @Override
    public void onFinishInflate() {
        super.onFinishInflate();

        if (getChildCount() > 1)
            throw new IllegalArgumentException("Root layout must be a LinearLayout, and only one child on this view!");

        if (getChildCount() == 0 || !(getChildAt(0) instanceof LinearLayout))
            throw new IllegalArgumentException("Root layout is not a LinearLayout!");

        if (getChildCount() == 1 && (getChildAt(0) instanceof LinearLayout)) {
            LinearLayout parent = (LinearLayout) getChildAt(0);

            if (parent.getChildCount() != 2) {
                throw new IllegalArgumentException("Root LinearLayout's has not EXACTLY two Views!");
            } else {
                mTopView = parent.getChildAt(0);
                mBottomView = parent.getChildAt(1);

                mTopView.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        mNormalHeight = mTopView.getHeight();
                        mMaxHeight = (int) (mNormalHeight * mFactor);
                    }
                }, 50);
            }
        }

    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
    }

    @Override
    protected boolean openedOverScrollByCompat(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {

        if (scrollY == 0) {
            //down, zoom in
            if (deltaY < 0 && mTopView.getLayoutParams().height + Math.abs(deltaY) > mMaxHeight) {
                mTopView.getLayoutParams().height = mMaxHeight;
            } else if (deltaY < 0 && mTopView.getLayoutParams().height + Math.abs(deltaY) <= mMaxHeight) {
                mTopView.getLayoutParams().height += Math.abs(deltaY);
            }
            //up, zoom out
            else if (deltaY > 0 && mTopView.getLayoutParams().height - Math.abs(deltaY) < mNormalHeight) {
                mTopView.getLayoutParams().height = mNormalHeight;
            } else if (deltaY > 0 && mTopView.getLayoutParams().height - Math.abs(deltaY) >= mNormalHeight) {
                mTopView.getLayoutParams().height -= Math.abs(deltaY);
            }
        }

        if (mChangeListener != null) mChangeListener.onChanged(
                (mMaxHeight - (float) mTopView.getLayoutParams().height) / (mMaxHeight - mNormalHeight)
        );

        if (deltaY != 0 && scrollY == 0) {
            mTopView.requestLayout();
            mBottomView.requestLayout();
        }

        if (mTopView.getLayoutParams().height == mNormalHeight) {
            super.overScrollBy(deltaX, deltaY, scrollX,
                    scrollY, scrollRangeX, scrollRangeY,
                    maxOverScrollX, maxOverScrollY, isTouchEvent);
        }

        return true;

    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        touchListener.onTouchEvent(ev);
        return super.onTouchEvent(ev);
    }

    public interface onOverScrollChanged {
        void onChanged(float v);
    }

    public void setChangeListener(onOverScrollChanged changeListener) {
        mChangeListener = changeListener;
    }

    private OnTouchEventListener touchListener = new OnTouchEventListener() {
        @Override
        public void onTouchEvent(MotionEvent ev) {
            if (ev.getAction() == MotionEvent.ACTION_UP) {
                if (mTopView != null && mTopView.getHeight() > mNormalHeight) {
                    ResetAnimation animation = new ResetAnimation(mTopView, mNormalHeight);
                    animation.setDuration(400);
                    mTopView.startAnimation(animation);
                }
            }
        }
    };

    public class ResetAnimation extends Animation {
        int targetHeight;
        int originalHeight;
        int extraHeight;
        View mView;

        ResetAnimation(View view, int targetHeight) {
            this.mView = view;
            this.targetHeight = targetHeight;
            originalHeight = view.getHeight();
            extraHeight = this.targetHeight - originalHeight;
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            int newHeight = (int) (targetHeight - extraHeight * (1 - interpolatedTime));
            mView.getLayoutParams().height = newHeight;
            mView.requestLayout();

            if (mChangeListener != null) mChangeListener.onChanged(
                    (mMaxHeight - (float) mTopView.getLayoutParams().height) / (mMaxHeight - mNormalHeight)
            );

        }
    }
}

【讨论】:

  • 确实这是最好的解决方法。你能给delegate子类提供任何参考吗?我从来不知道有这种扩展库的方式。
  • @musooff 上面的github演示代码链接已经过去了,你可以去查看一下。
  • 是的,我检查过了。我实现了你的方式。我要问的是您是否可以提供与delegate subclass 相关的资源。我的意思是你是怎么知道你可以创建这样的课程的?
  • 你玩过滚动。滚动时有一种奇怪的行为。有时flingin不起作用。知道是什么原因造成的吗?
  • 这是一个演示,你可以知道如何使用 CoordinatorLayout 行为来处理过度滚动效果,你可以根据需要对其进行优化。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-09
  • 1970-01-01
  • 2020-05-10
相关资源
最近更新 更多