如何在NestedScrollView中获得overScroll高度?

时间:2019-05-17 03:23:53

标签: android android-scrollview android-nestedscrollview overscroll

我想在NestedScrollView中拥有滚动侦听器,以使我的顶级ImageView在用户滚动时获得缩放。像this

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

View中有一个protected中使用的overScrollBy方法ScrollView,开发人员在其CustomScrollView中将其覆盖。不幸的是,它不是overScrollBy NestedScrollView而是使用自己的overScrollByCombat,它是私有的,因此我无法覆盖它。因此,我对如何在"overScrollListener"中获得CustomNestedScrollView颇有兴趣。

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

如果已经有任何与NestedScrollView产生相同效果的库,欢迎您推荐。

1 个答案:

答案 0 :(得分: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)
            );

        }
    }
}