android NestedScroll嵌套滑动机制完全解析-原来如此简单(修正自己的一个错误说法)

android的嵌套滑动机制其实很简单,现在android的很多组件都实现了,比如RecycleView和ScrollView。

其实他的机制使用起来也很简单,用它来实现下面的效果非常方便:

android NestedScroll嵌套滑动机制完全解析-原来如此简单(修正自己的一个错误说法)_第1张图片


首先,我们需要简单了解一下几个与嵌套滑动的类:NestedScrollingChild, NestedScrollingChildHelper 和 NestedScrollingParent , NestedScrollingParentHelper

千万别被有四个类而吓到,说了很简单就肯定很简单。

现在,我们自定义两个类

1、

public class MyNestedScrollChildL extends LinearLayout implements NestedScrollingChild {
2、

public class MyNestedScrollParent extends LinearLayout implements NestedScrollingParent {
两个自定义类都继承LinearLayout,实现嵌套滑动的子类和父类接口,然后布局文件肯定就是想的那样了:

    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

            android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher" />

            android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#f0f"
        android:text="@string/topStr" />

            android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical">

                    android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/contenttv"
            android:textColor="#f0f" />


    
下面我们看看,我们自己的子类和父类需要实现的方法,

先来看看父类,父类相对简单:

@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
    Log.i(Tag,"onStartNestedScroll--"+"child:"+child+",target:"+target+",nestedScrollAxes:"+nestedScrollAxes);
    return true;
}


@Override
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
    Log.i(Tag,"onNestedScrollAccepted"+"child:"+child+",target:"+target+",nestedScrollAxes:"+nestedScrollAxes);
  
}

@Override
public void onStopNestedScroll(View target) {
    Log.i(Tag,"onStopNestedScroll--target:"+target);
    
}

@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
    Log.i(Tag,"onNestedScroll--"+"target:"+target+",dxConsumed"+dxConsumed+",dyConsumed:"+dyConsumed
    +",dxUnconsumed:"+dxUnconsumed+",dyUnconsumed:"+dyUnconsumed);
}

@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {

    
    Log.i(Tag,"onNestedPreScroll--getScrollY():"+getScrollY()+",dx:"+dx+",dy:"+dy+",consumed:"+consumed);
}

@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
    Log.i(Tag,"onNestedFling--target:"+target);
    return false;
}

@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
    Log.i(Tag,"onNestedPreFling--target:"+target);
    return false;
}
哎哟我去,不是很简单嘛,这么多方法,简单个锤子呀,千万别急,虽然有这么多方法需要实现,但是,但是需要我们自己动手的很少,

首先,第一个方法肯定要的喽

@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
    Log.i(Tag,"onStartNestedScroll--"+"child:"+child+",target:"+target+",nestedScrollAxes:"+nestedScrollAxes);
    return true;
}
nestedScrollAxes,嵌套滑动的坐标系,也就是用来判断X轴滑动还是Y轴滑动,这里可以根据需要返回true和false,当然了如果返回false,嵌套滑动就没得玩了,

官方解释如下

Returns:
true if this ViewParent accepts the nested scroll operation,如果要接受嵌套滑动操作,就返回true吧。

第二个需要动手的方法,从名字可以看出,在滑动之前会被调用,他的作用就是子类在滑动的时候,分发一下,是否有父类需要消费滑动,这个时候,父类就可以根据自己的业务逻辑进行消费部分和全部消费自己决定了,和滑动了,消费的方法非常简单:

consumed[1]=dy;

@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {

    
      consumed[1]=dy;//完全消费y轴的滑动
Log. i( Tag , "onNestedPreScroll--getScrollY():"+getScrollY()+ ",dx:"+dx+ ",dy:"+dy+ ",consumed:"+consumed) ;}
 好了,就动手两个方法,真的很简单,但是其他的方法,我们需要帮手了NestedScrollingParentHelper,就让这个劳工去帮我们干活去吧 
  

private void init() {
    mParentHelper = new NestedScrollingParentHelper(this);

}

@Override
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
    Log.i(Tag,"onNestedScrollAccepted"+"child:"+child+",target:"+target+",nestedScrollAxes:"+nestedScrollAxes);
    mParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
}

@Override
public void onStopNestedScroll(View target) {
    Log.i(Tag,"onStopNestedScroll--target:"+target);
    mParentHelper.onStopNestedScroll(target);
}
其他的方法,不用做实现了,先从简单的开始

父类的基本工作已经做完,如果你使用ScrollView作为子类,那么你就可以直接在onNestedPreScroll中进行消费和逻辑处理了,demo的列子中有一个继承scrollView的子类,但是你的api版本的ScrollView是不是已经实现了嵌套机制哦哦!!!

@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {

    if(showImg(dy)||hideImg(dy)){//如果父亲自己要滑动,则拦截
        consumed[1]=dy;
        scrollBy(0,dy);
        Log.i("onNestedPreScroll","Parent滑动:"+dy);
    }
    Log.i(Tag,"onNestedPreScroll--getScrollY():"+getScrollY()+",dx:"+dx+",dy:"+dy+",consumed:"+consumed);
}
下面我们看自己的子类,自己来实现 NestedScrollingChild和 NestedScrollingChildHelper,放心也很简单,看看我们需要实现的方法吧


@Override
public void setNestedScrollingEnabled(boolean enabled) {
    Log.i(Tag, "setNestedScrollingEnabled:" + enabled);
}

@Override
public boolean isNestedScrollingEnabled() {
    Log.i(Tag, "isNestedScrollingEnabled");
    return false;
}

@Override
public boolean startNestedScroll(int axes) {

    
    return false;
}

@Override
public void stopNestedScroll() {
    Log.i(Tag, "stopNestedScroll");
   
}

@Override
public boolean hasNestedScrollingParent() {
    Log.i(Tag, "hasNestedScrollingParent");
    return false;
}

@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
                                    int dyUnconsumed, int[] offsetInWindow) {
    Log.i(Tag, "dispatchNestedScroll:dxConsumed:" + dxConsumed + "," +
            "dyConsumed:" + dyConsumed + ",dxUnconsumed:" + dxUnconsumed + ",dyUnconsumed:" +
            dyUnconsumed + ",offsetInWindow:" + offsetInWindow);
    return false;
}

@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
    Log.i(Tag, "dispatchNestedPreScroll:dx" + dx + ",dy:" + dy + ",consumed:" + consumed + ",offsetInWindow:" + offsetInWindow);
    return false;
}

艾玛,又是这么多,但是需要我们动手还是那句话,很少。

第一个,是否可以嵌套滑动,我们需要返回true吧

@Override
public boolean isNestedScrollingEnabled() {
    Log.i(Tag, "isNestedScrollingEnabled");
    return true;
}

第二个,然后没了,哈哈,其他的让帮手来吧NestedScrollingParentHelper,请看下面

@Override
public void setNestedScrollingEnabled(boolean enabled) {
    Log.i(Tag, "setNestedScrollingEnabled:" + enabled);
    getScrollingChildHelper().setNestedScrollingEnabled(enabled);
}

@Override
public boolean isNestedScrollingEnabled() {
    Log.i(Tag, "isNestedScrollingEnabled");
    return getScrollingChildHelper().isNestedScrollingEnabled();
}

@Override
public boolean startNestedScroll(int axes) {

    return getScrollingChildHelper().startNestedScroll(axes);
}

@Override
public void stopNestedScroll() {
    Log.i(Tag, "stopNestedScroll");
    getScrollingChildHelper().stopNestedScroll();
}

@Override
public boolean hasNestedScrollingParent() {
    Log.i(Tag, "hasNestedScrollingParent");
    return getScrollingChildHelper().hasNestedScrollingParent();
}

@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
                                    int dyUnconsumed, int[] offsetInWindow) {
    Log.i(Tag, "dispatchNestedScroll:dxConsumed:" + dxConsumed + "," +
            "dyConsumed:" + dyConsumed + ",dxUnconsumed:" + dxUnconsumed + ",dyUnconsumed:" +
            dyUnconsumed + ",offsetInWindow:" + offsetInWindow);
    return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed,
            dxUnconsumed, dyUnconsumed, offsetInWindow);
}

@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
    Log.i(Tag, "dispatchNestedPreScroll:dx" + dx + ",dy:" + dy + ",consumed:" + consumed + ",offsetInWindow:" + offsetInWindow);
    return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}

@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
    Log.i(Tag, "dispatchNestedFling:velocityX:" + velocityX + ",velocityY:" + velocityY + ",consumed:" + consumed);
    return getScrollingChildHelper().dispatchNestedFling(velocityX, velocityY, consumed);
}

@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
    Log.i(Tag, "dispatchNestedPreFling:velocityX:" + velocityX + ",velocityY:" + velocityY);
    return getScrollingChildHelper().dispatchNestedPreFling(velocityX, velocityY);
}
其实,连是否可以嵌套滑动的方法,都是交给帮手,当然了,我告诉帮手要可以滑动了,,,

private NestedScrollingChildHelper getScrollingChildHelper() {
    if (mScrollingChildHelper == null) {
        mScrollingChildHelper = new NestedScrollingChildHelper(this);
        mScrollingChildHelper.setNestedScrollingEnabled(true);
    }
    return mScrollingChildHelper;
}
目前为止,架子就搭建完了,其实,总结一下,我们基本没做啥,很多交给帮手了,总结下我们自己做了啥,父类,需要让自己可以接受嵌套滑动,返回true,然后自己消费下滑动,子类需要能够设置可以嵌套滑动,如果从源码来看的话,子类的帮手回去寻找是否有父类能够消费,当然我们返回了true,然后子类开始滑动之前帮手问父类要消费多少,然后将消费的结果返回给子类,还有子类拿到消费后的结果,如果没有消费完,自己可以根据业务来消费一下,如果,子类消费了之后,还有剩余啥的,还想告诉父类,那么就再调用这个方法来分发,可以看看RecyclerView源码的这个嵌套滑动部分就会很清楚了

@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
                                    int dyUnconsumed, int[] offsetInWindow) {
    Log.i(Tag, "dispatchNestedScroll:dxConsumed:" + dxConsumed + "," +
            "dyConsumed:" + dyConsumed + ",dxUnconsumed:" + dxUnconsumed + ",dyUnconsumed:" +
            dyUnconsumed + ",offsetInWindow:" + offsetInWindow);
    return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed,
            dxUnconsumed, dyUnconsumed, offsetInWindow);
}


子类一共有两个分发的方法,一个是在滑动之前分发给父类,一个是在父类消费之后,如果子类还想根据业务在分发,可以再次分发,说白了就是子类和父类他们两个在一个又一次告诉对方消费的信息

@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
    Log.i(Tag, "dispatchNestedPreScroll:dx" + dx + ",dy:" + dy + ",consumed:" + consumed + ",offsetInWindow:" + offsetInWindow);
    return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}

最后两个关键点了,一个是子类的down事件的时候,开始嵌套滑动,然后再move事件中,把嵌套滑动分发给父类,就完了,父类和子类各自根据自己的需要来滑动和消费吧

@Override
public boolean onTouchEvent(MotionEvent e) {
    switch (e.getAction()) {
        case MotionEvent.ACTION_DOWN:

        {
  

            mLastTouchY = (int) (e.getRawY() + 0.5f);
            int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;

            nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
            startNestedScroll(nestedScrollAxis);
        }
        break;
        case MotionEvent.ACTION_MOVE: {
            Log.i("aaa", "Child--getRawY:" + e.getRawY());

            int x = (int) (e.getX() + 0.5f);
            int y = (int) (e.getRawY() + 0.5f);
            int dx = mLastTouchX - x;
            int dy = mLastTouchY - y;
            Log.i(Tag, "child:dy:" + dy + ",mLastTouchY:" + mLastTouchY + ",y;" + y);
            Log.i(Tag, "xxx");
            mLastTouchY = y;
            mLastTouchX = x;
            if (
(dx, dy, mScrollConsumed, mScrollOffset)) {//分发滑动,如果父类一点儿也没有消费会返回false

                dy -= mScrollConsumed[1];//如果父类消费了,我就不处理了,根据自己的业务决定吧
                if (dy == 0) {
                    return true;
                }

            } else {
                scrollBy(0, dy);

            }
        }


    }

    return true;
}

完了,就是这么简单,总结下:

一、父类要做的事情

1、onStartNestedScroll方法,根据需要返回true,才能够使用嵌套滑动机制

2、消费,在两个方法中都可以消费onNestedScroll  和onNestedPreScroll,区别已经说过了,一个是滑动之前,一个是滑动之后

二、子类要做的事情

1、设置能够使用嵌套滑动isNestedScrollingEnabled()

2、根据需要开启滑动机制startNestedScroll

3、根据需要分发滑动事件dispatchNestedPreScroll,dispatchNestedScroll

就是这么简单,其实普通的事件分发机制,它的好处在于,利用helper类,来完成了滑动信息的交换,而如果利用事件分机制,一旦一个事件被某个控件消费之后,其他控件就悲剧了,同一系类事件收不到了,之后手动重新分发一个down事件开始从来,,,所以嵌套滑动机制非常棒,快用用吧。


修正一个上文的错误说法:

父类的基本工作已经做完,如果你使用ScrollView作为子类,那么你就可以直接在onNestedPreScroll中进行消费和逻辑处理了

解决:

经过测试,如果直接用ScrollView作为我写的MyNestedScrollParent ,并不能滑动,经过查看源码,ScrollView确实会去调用view中的嵌套滑动的一系列事件,包括判断父类是否支持嵌套滑动,和嵌套滑动的分发,但是我们MyNestedScrollParent 始终无法接受到嵌套滑动事件,所以自己实现ScrollView继承NestedScrollingChildHelper ,相当于覆写了view中的嵌套滑动的方法,这样我肯定就能收到嵌套滑动的事件了,感谢鱼儿_飞吧让我发现了这个错误的说法或者说是bug吧。另外修改他想要的效果源码

直接使用scrollview源码


下一篇android NestedScrolling嵌套滑动实战之联合滚动fling效果


源码下载

点击打开链接








你可能感兴趣的:(自定义控件)