美文网首页
Android 嵌套滑动解决

Android 嵌套滑动解决

作者: 覆水无言 | 来源:发表于2021-01-21 16:36 被阅读0次

    NestedScrollingParent与NestedScrollingChild

    1、 嵌套滑动的解决方案先看注释了解方法

    这时Google官方给的处理方案,在Androidx或者support包中

    public interface NestedScrollingChild {
      
        //设置是否允许嵌套滑动,允许的话设为true
        void setNestedScrollingEnabled(boolean enabled);
     
        //是否允许嵌套滑动
        boolean isNestedScrollingEnabled();
    
        //开始嵌套滑动 axes:滑动方向,一般用于通知父控件我要看是滑动了
        boolean startNestedScroll(@ScrollAxis int axes);
     
        //停止嵌套滑动,一般也用于通知父控件
        void stopNestedScroll();
    
        // 判断父控件NestedScrollingParent 的onStartNestedScroll方法是否返回true,
        //只有true,才能继续一系列的嵌套滑动
        boolean hasNestedScrollingParent();
    
        /**
         * 子控件消费了一部分滑动之后通知父控件滑动
         * @param dxConsumed    子控件X方向已消耗长度
         * @param dyConsumed    子控件Y方向已消耗长度
         * @param dxUnconsumed  子控件X方向未消耗长度
         * @param dyUnconsumed  子控件Y方向未消耗长度
         * @param offsetInWindow  控件在window上的偏移
         * @return 数据是否可以分发
         */
        boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
                int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow);
    
        /**
         * 子控件滑动之前通知父控件滑动
         * @param dx    x方向上触摸滑动距离
         * @param dy    y方向上触摸滑动距离
         * @param consumed  父控件滑动后未消耗的距离,是父控件返回的值,不是子控件的值
         *                   consumed[0]-X  consumed[1]为Y 
         * @param offsetInWindow  控件在window上的偏移
         * @return  数据是否可以分发
         */
        boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
                @Nullable int[] offsetInWindow);
    
        //子控件消耗Fling滑动后通知父控件,velocityX每秒滑动速度,
        boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
    
        //子控件消耗Fling滑动之前通知父控件fling滑动
        boolean dispatchNestedPreFling(float velocityX, float velocityY);
    }
    
    public interface NestedScrollingParent {
        //子控件开始嵌套滑动通知了父控件,这个方法接收调用,父控件决定是否嵌套滑动 @return 是否嵌套滑动
        boolean onStartNestedScroll(@NonNull View child, @NonNull View target, @ScrollAxis int axes);
     
        //子控件开始嵌套滑动通知了父控件,这个方法也会接收调用,
        void onNestedScrollAccepted(@NonNull View child, @NonNull View target, @ScrollAxis int axes);
    
        //子控件停止嵌套滑动通知父控件
        void onStopNestedScroll(@NonNull View target);
    
        //子控件调用 dispatchNestedScroll 父控件这个方法接收调用
        void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
                int dxUnconsumed, int dyUnconsumed);
    
        //子控件调用 dispatchNestedPreScroll 父控件这个方法接收调用 consumed需要父控件赋值
        void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed);
     
         //子控件调用 dispatchNestedFling 父控件这个方法接收调用,惯性滑动
        boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed);
    
        //子控件调用 dispatchNestedPreFling 父控件这个方法接收调用,惯性滑动
        boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY);
    
        //获取当前父控件的滑动方向
        int getNestedScrollAxes();
    }
    
    

    了解Android事件分发的可以思考一下,事件分发是父控件分发给子控件,父控件也是dispath方法分发,子控件on方法接收,这里其实就是反过来,子控件dispath分发,父控件接收处理,处理结果有返回给子控件。

    2、嵌套滑动调用流程

    子view 父View
    startNestedScroll onStartNestedScroll、onNestedScrollAccepted
    dispatchNestedPreScroll onNestedPreScroll
    dispatchNestedScroll onNestedScroll
    stopNestedScroll onStopNestedScroll

    子控件开始滑动调用startNestedScroll通知父控件我要看是滑动了,父控件就会被调用onStartNestedScroll、onNestedScrollAccepted,并返回给子控件我要不要滑动,如果父控件不滑动到此结束,子控件自己滑动就行了,
    父控件需要滑动,则子控件开始决定怎样配合滑动,

    1. 子控件先滑动父控件后滑动,子控件滑动后,调用dispatchNestedScroll,父控件onNestedScroll被调用,如果有父控件没消费的一部分滑动由参数dxUnconsumed返回给子控件继续滑动,
    2. 父控件先滑动子控件后滑动,子控件先调用dispatchNestedPreScroll,父控件则onNestedPreScroll被调用,父控件滑动后将没有消耗的滑动赋值给consumed,交给子控件,子控件开始滑动,
      3:dispatchNestedFling与dispatchNestedPreFling一样逻辑

    3、示例解析

    示例源码

    3.1 自定义NestedScrollChildLayout和NestedScrollParentLayout子父控件

    只要实现NestedScrollingChild 和 NestedScrollingParent接口即可,实现所有方法。

    3.2 NestedScrollingChildHelper与NestedScrollingParentHelper

    通过名字可以看出这是子控件和父控件嵌套滑动各个控件的帮助类,他们的方法几乎都对应着两个嵌套滑动接口。在androidx或support包中。

    1. NestedScrollingChildHelper: 子控件调用,寻找父控件并调用父控件中的NestedScrollingParent方法。
    2. NestedScrollingParentHelper:父控件调用,寻找子控件调用子控件的NestedScrollingChild 方法。

    3.3 子控件处理触摸滑动事件实现嵌套滑动

    触摸滑动处理方法 handleMoveY(int distanceY), 这里只处理上下滑动

        private void handleMoveY(int distanceY){
            //父控件是否允许滑动
            if (startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL)) {
                if (distanceY > 0) {
                    //向上滑, 父控件先滑动子控件再滑动
                    if (dispatchNestedPreScroll(0,   distanceY, consumed, offset)) {
                        //consumed为父控件消耗的距离, 未消耗的子控件继续滑动
                        int unConsumed =   distanceY - consumed[1];
                        if (unConsumed != 0) {
                            scrollBy(0, unConsumed);
                        }
                    } else {
                        scrollBy(0,   distanceY);
                    }
                } else {
                    //向下滑, 子控件先滑动父控件后滑动
                    if (getScrollY() >= 0) {
                        if (getScrollY() == 0) {
                            //子控件已不再需要滑动,父控件滑动
                            dispatchNestedScroll(0, 0, 0, distanceY, offset);
                            return ;
                        }
                        //子控件可以消耗所有滑动,先滑动自己
                        if (getScrollY() +   distanceY >= 0) {
                            scrollBy(0,   distanceY);
                        } else if (getScrollY() != 0) {
                            //子控件滑动一部分,剩余给父控件滑动
                            int consume = getScrollY();
                            int unConsumed = (int) distanceY + consume;
                            scrollTo(0, -consume);  //先自己滑动
                            dispatchNestedScroll(0, consume, 0, unConsumed, offset);
                        } else {
                            dispatchNestedScroll(0, 0, 0,   distanceY, offset);
                        }
                    } else {
                        scrollTo(0, 0);
                    }
                }
            }
        }
    

    由上面注释可以看出,嵌套滑动完全按照 2、嵌套滑动调用流程来完成,

    1. 向上滑,父控件先消耗一定的滑动距离,子控件滑动剩余距离。父控件则隐藏第一个控件。
    2. 向下滑,子控件先滑动到顶部,之后将剩余的滑动距离或后续的滑动分发给父控件。父控件显示出第一个控件。

    3.4 父控件处理子控件分发的滑动

    父控件需要的滑动是隐藏或显示第一个控件,则父控件可滑动的距离就是第一个控件的高度

        //向下滑动,子控件已滑动顶部,分发剩余滑动与后续的滑动,父控件开始滑入第一个控件。
        @Override
        public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
            super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
            Log.i(TAG, "onNestedScroll: dyConsumed=" + dxConsumed + "  dyUnconsumed=" + dyUnconsumed);
            //父控件后滑动为显示上部控件
            if (getScrollY() > 0 && dyUnconsumed < 0) {
                if (getScrollY() + dyUnconsumed >0) {
                    scrollBy(0, dyUnconsumed);
                } else {
                    scrollTo(0, 0);
                }
            }
        }
    
        //向上滑动,父控件先滑动,将已消耗的滑动距离传给子控件,子控件继续滑动,实现父控件第一个控件滑出屏幕。
        @Override
        public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
            super.onNestedPreScroll(target, dx, dy, consumed);
            Log.i(TAG, "onNestedPreScroll: dx=" + dx + " dy=" + dy);
            View view = getChildAt(0);
            //父控件先滑动为隐藏上部控件,
            if (getScrollY() < view.getHeight() && dy > 0) {
                if (getScrollY() + dy <= view.getHeight()) {
                    scrollBy(0, dy);
                    consumed[1] = dy;
                } else {
                    consumed[1] = view.getHeight() - getScrollY();
                    scrollTo(0, view.getHeight());
                }
            }
        }
    

    4. 接口系统化

    在Android api 21之后,嵌套滑动的的接口已经系统化,Android原生系统view与ViewGroup就自带了嵌套滑动的所有接口方法,

    相关文章

      网友评论

          本文标题:Android 嵌套滑动解决

          本文链接:https://www.haomeiwen.com/subject/gtrdzktx.html