嵌套滑动机制详解

嵌套滑动机制主要涉及两个接口

public interface NestedScrollingChild {
    void setNestedScrollingEnabled(boolean var1);
    boolean isNestedScrollingEnabled();

    boolean startNestedScroll(int var1);//开启嵌套滚动流程
    //循环遍历parent,先判断是否是NestedScrollingParent的实例,如果是,那就调用parent的onStartNestedScroll和onNestedScrollAccepted

    void stopNestedScroll();
    boolean hasNestedScrollingParent();
    boolean dispatchNestedScroll(int var1, int var2, int var3, int var4, @Nullable int[] var5);

    boolean dispatchNestedPreScroll(int var1, int var2, @Nullable int[] var3, @Nullable int[] var4);
    //在子view自己进行滚动之前调用此方法,询问父view是否要在子view之前进行滚动。
  //此方法的前两个参数用于告诉父View此次要滚动的距离;而第三第四个参数用于子view获取父view消费掉的距离和父view位置的偏移量。
  //第一第二个参数为输入参数,即常规的函数参数,调用函数的时候我们需要为其传递确切的值。而第三第四个参数为输出参数,调用函数时我们只需要传递容器(在这里就是两个数组),在调用结束后,我们就可以从容器中获取函数输出的值。
  //如果parent消费了一部分或全部距离,则此方法返回true。

    boolean dispatchNestedFling(float var1, float var2, boolean var3);
    boolean dispatchNestedPreFling(float var1, float var2);
}

public interface NestedScrollingParent {
    boolean onStartNestedScroll(@NonNull View var1, @NonNull View var2, int var3);//决定是否要配合其进行嵌套滚动

    void onNestedScrollAccepted(@NonNull View var1, @NonNull View var2, int var3);
    void onStopNestedScroll(@NonNull View var1);
    void onNestedScroll(@NonNull View var1, int var2, int var3, int var4, int var5);

    void onNestedPreScroll(@NonNull View var1, int var2, int var3, @NonNull int[] var4);
    //child调用dispatchNestedPreScroll(),这时可以回调到parent的OnNestedPreScroll(),parent可以在这个回调中先于child滚动。

    boolean onNestedFling(@NonNull View var1, float var2, float var3, boolean var4);
    boolean onNestedPreFling(@NonNull View var1, float var2, float var3);
    int getNestedScrollAxes();
}

对它们打log如下

E/child: startNestedScroll-----   是否开启
E/parent: onStartNestedScroll——是否配合

E/child: dispatchNestedPreScroll——如果为true,将事件给parent
E/parent: onNestedPreScroll——此时dy有数据而consume没数据,给consume数据赋值。赋完值马上回传
E/数据parent: dy=14,consumed[1]0
E/数据child: dy=14,consumed[1]14

所以一般嵌套滑动的child都有如下重要的函数

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        //按下
        case MotionEvent.ACTION_DOWN:
            lastY = (int) event.getRawY();
            startNestedScroll(ViewCompat.SCROLL_AXIS_HORIZONTAL | ViewCompat.SCROLL_AXIS_VERTICAL);//开启嵌套滑动
            break;
        //移动
        case MotionEvent.ACTION_MOVE:
            ......
            if (dispatchNestedPreScroll(0, dy, consumed, offset)) {//如果找到了支持嵌套滑动的父类,父类进行了一系列的滑动
                Log.e("数据child", "dy=" + dy + ",consumed[1]" + consumed[1]);
                //获取滑动距离进行滑动,具体情况具体分析
                int remain = dy - consumed[1];
                if (remain != 0) {
                    scrollBy(0, -remain);
                }
            } else {//如果没有嵌套滑动,那么自己滑动
                scrollBy(0, -dy);
            }
            break;
        case MotionEvent.ACTION_UP:
            stopNestedScroll();
            break;
    }
    return true;
}

父类实例中最重要的函数

//在此可以判断参数target是哪一个子view以及滚动的方向,然后决定是否要配合其进行嵌套滚动
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
    Log.e("parent", "onStartNestedScroll");
    if (target instanceof MyNestedScrollingChild2) {
        return true;
    }
    return false;
}

//先于child滚动
//前3个为输入参数,最后一个是输出参数
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
    Log.e("parent", "onNestedPreScroll");
    Log.e("数据parent", "dy=" + dy + ",consumed[1]" + consumed[1]);
    if (showImg(dy) || hideImg(dy)) {//如果需要显示或隐藏图片,即需要自己(parent)滚动
        scrollBy(0, -dy);//滚动
        consumed[1] = dy;//告诉child我消费了多少
    }
}

参考文章:https://www.cnblogs.com/wjtaigwh/p/6398562.html


但是


一般情况下不大可能就是一个光秃秃的NestedScrollingChild。如果child是recycleview的情况呢(里面还有滑动事件,而且本身还继承NestedScrollingChild)
我们先来看看recycleview的onTouchEvent函数

switch (action) {
    case MotionEvent.ACTION_DOWN: {
        mScrollPointerId = MotionEventCompat.getPointerId(e, 0);
        mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
        mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);

        int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
        if (canScrollHorizontally) {
            nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
        }
        if (canScrollVertically) {
            nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
        }
        startNestedScroll(nestedScrollAxis);//******************************************************************
    } break;

    case MotionEventCompat.ACTION_POINTER_DOWN: {
        mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex);
        mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f);
        mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f);
    } break;

    case MotionEvent.ACTION_MOVE: {
        final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId);
        if (index < 0) {
            Log.e(TAG, "Error processing scroll; pointer index for id " +
                    mScrollPointerId + " not found. Did any MotionEvents get skipped?");
            return false;
        }

        final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);
        final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);
        int dx = mLastTouchX - x;
        int dy = mLastTouchY - y;

        if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {//*************************************************
            dx -= mScrollConsumed[0];
            dy -= mScrollConsumed[1];
            vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
            // Updated the nested offsets
            mNestedOffsets[0] += mScrollOffset[0];
            mNestedOffsets[1] += mScrollOffset[1];
        }

        if (mScrollState != SCROLL_STATE_DRAGGING) {
            boolean startScroll = false;
            if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
                if (dx > 0) {
                    dx -= mTouchSlop;
                } else {
                    dx += mTouchSlop;
                }
                startScroll = true;
            }
            if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
                if (dy > 0) {
                    dy -= mTouchSlop;
                } else {
                    dy += mTouchSlop;
                }
                startScroll = true;
            }
            if (startScroll) {
                setScrollState(SCROLL_STATE_DRAGGING);
            }
        }

        if (mScrollState == SCROLL_STATE_DRAGGING) {//*****************************在这里随着手指的移动而移动,调用scrollByInternal
            mLastTouchX = x - mScrollOffset[0];
            mLastTouchY = y - mScrollOffset[1];

            if (scrollByInternal(
                    canScrollHorizontally ? dx : 0,
                    canScrollVertically ? dy : 0,
                    vtev)) {
                getParent().requestDisallowInterceptTouchEvent(true);
            }
        }
    } break;

具体parent代码也可以参考下面的文章(之所以加dispatchTouchEvent是为了增加快速滑动效果,否则滑动效果太生硬)

参考文章:http://blog.csdn.net/lmj121212/article/details/53046582

我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=1ai6bm2q8c7cq

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值