一、概述
NestedScrollingParent 和 NestedScrollingChild 两个类是Android在support.v4中提供的,这是一套全新的嵌套滑动机制,用来实现一些传统的自定义ViewGroup事件分发处理所不能完成的效果。
按照传统事件分发角度来看,事件是由父View发起的,如果父View需要自己做滑动效果就要拦截掉事件并通过自己的onTouchEvent方法进行消耗,父View拦截之后,子View是没有机会接收到此事件,事件分发,只要拦截了,当前手势接下来的时间都会交给父View(拦截者)来处理;如果父View不需要,也就是不拦截事件,交给子View消耗,那么如果不使用特殊方法的话父View也没法再处理此事件。
而NestedScrolling机制不一样,它是父View和子View在滑动过程中的交互。
比如:子View在处理事件前,会将事件的信息传递给父View,父View可以决定是否消耗掉事件或者消耗一部分,然后子View将父View消耗的部分减去,自己在处理剩下的,如果处理完后还有剩下的,可以再次交给父View,由父View处理。RecyclerView就是实现了NestedScrollingChild2,在onTouchEvent方法中,消耗滑动事件前,通过嵌套机制,把滑动信息交给父View,父View消耗后,自己在滑动剩下的偏移量。
二、方法
在Lollipop及以上版本的View源码中多了这么几个方法:
void setNestedScrollingEnabled(boolean enabled);
boolean isNestedScrollingEnabled();
boolean startNestedScroll(@ScrollAxis int axes);
void stopNestedScroll();
boolean hasNestedScrollingParent();
boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow);
boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
@Nullable int[] offsetInWindow);
boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
boolean dispatchNestedPreFling(float velocityX, float velocityY);
比如在RecyclerView源码中实现了NestScrollingChild 接口,并在onTouchEvent方法中就调用了dispatchNestedPreScroll :
public boolean onTouchEvent(MotionEvent e) {
...
switch (action) {
...
case MotionEvent.ACTION_MOVE: {
...
if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset, TYPE_TOUCH)) {
dx -= mScrollConsumed[0];
dy -= mScrollConsumed[1];
vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
// Updated the nested offsets
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
}
...
}
return true;
}
在处理move事件前,将用户触发的dx ,dy,等信息传递给父View,父View可以将dx或者dy消耗或者部分消耗后,把剩余dx或dy交给子View,再由子View处理。
如果想要一个View能够产生NestScroll事件 首先这个View必须实现NestScrollChild接口(Android 5.0以上的所有View都实现了NestScrollChild接口),其次必须调用setNestedScrollingEnabled方法来将一个普通事件转换为NestScroll事件。
而在ViewGroup中多了这些方法:
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);
public void onStopNestedScroll(View target);
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed);
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
public boolean onNestedPreFling(View target, float velocityX, float velocityY);
public int getNestedScrollAxes();
下面是对这些方法的理解:
/**
* 只有在onStartNestedScroll返回true的时候才会接着调用onNestedScrollAccepted,
* 这个判断是需要我们自己来处理的,
* 不是直接的父子关系一样可以正常进行
*/
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes)
/**
* 字面意思可以理解出来父View接受了子View的邀请,可以在此方法中做一些初始化的操作。
*/
public void onNestedScrollAccepted(View child, View target, int axes)
/**
* 每次子View在滑动前都需要将滑动细节传递给父View,
* 一般情况下是在ACTION_MOVE中调用
* public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow),
* dispatchNestedPreScroll在ScrollView的Action_Move中被调用
* 然后父View就会被回调public void onNestedPreScroll(View target, int dx, int dy, int[] consumed)。
*/
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed)
/**
* 接下来子View就要进自己的滑动操作了,滑动完成后子View还需要调用
* public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow)
* 将自己的滑动结果再次传递给父View,父View对应的会被回调
* public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed),
* 但这步操作有一个前提,就是父View没有将滑动值全部消耗掉,因为父View全部消耗掉,子View就不应该再进行滑动了
* 子View进行自己的滑动操作时也是可以不全部消耗掉这些滑动值的,剩余的可以再次传递给父View,
* 使父View在子View滑动结束后还可以根据子View剩余的值再次执行某些操作。
*/
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)
/**
* ACTION_UP或者ACTION_CANCEL的到来,
* 子View需要调用public void stopNestedScroll()来告知父View本次NestedScrollig结束,
* 父View对应的会被回调public void onStopNestedScroll(View target),
*/
public void onStopNestedScroll(View child)