Why
Android事件分发机制一般情况下能满足大部分情况下View事件处理,其中的关键点就是事件拦截和事件上传,具体的详情这里不做展开。假设现在有这样一种场景,ViewGroup有且仅有一个Child View,�
要求是Child消费down事件、ViewGroup消费move事件,这种情况就不太好处理,原因是Child消费down事件后,后续的move、up和cancel的onTouchEvent返回false, 事件都会发送到Child, ViewGroup不会有机会处理。可以看出down事件是否消费很关键,根据默认的事件分发机制似乎不能完成这里的要求,好在google提供了design(Android L版本提出), design库其中的一个重要特性就是嵌套滑动机制。�
What
嵌套滑动可以简单的理解成父(ViewGroup)和子(View或者ViewGroup)共同处理move事件,流程是这样的:�子View先将滑动事件交给父View处理,�父View根据情况消耗全部或者部分move偏移量,子View再消耗剩余的move偏移量,最后子View再通知父View scroll消耗的情况,父View根据这个需求再进行滑动,整个过程的顺序如下:
0,父View先处理scroll
1,子View再处理scroll
2,父View再处理scroll
3,子View再处理scroll
元素
主要接口有2个,分别是 NestedScrollingParent
和 NestedScrollingChild
,父 View 需要实现 NestedScrollingParent接口,而子 View 需要实现 NestedScrollingChild接口。
NestedScrollingChild接口的源码如下:
public interface NestedScrollingChild {
//设置是否开启嵌套滑动功能
public void setNestedScrollingEnabled(boolean enabled);
public boolean isNestedScrollingEnabled();
//开始一个新的嵌套滑动,参数指定横向或者纵向
public boolean startNestedScroll(int axes);
//结束一个已经开始的嵌套
public void stopNestedScroll();
//是否有对应的parent配合处理滑动事件
public boolean hasNestedScrollingParent();
//scroll的预处理过程,对应上图的0和1
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);
//scroll的后处理过程,对应上图的2和3
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);
//fling和scroll和类似
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
}
NestedScrollingParent接口的源码如下:
public interface NestedScrollingParent {
//是否接受子View的嵌套滑动
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);
//成功后的回调
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);
//结束后的回调
public void onStopNestedScroll(View target);
//预处理过程,对应上图的0
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
//后处理过程,对应上图的2
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed);
//fling和scroll和类似
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
public boolean onNestedPreFling(View target, float velocityX, float velocityY);
//横向还是纵向
public int getNestedScrollAxes();
}
google还提供了2个辅助类来帮助实现整个过程,分别是NestedScrollingChildHelper 和NestedScrollingParentHelper,从名字可以看出分别作用的对象是Child和Parent。
比如Child的用法如下:
// NestedScrollingChild
@Override
public void setNestedScrollingEnabled(boolean enabled) {
getScrollingChildHelper().setNestedScrollingEnabled(enabled);
}
@Override
public boolean isNestedScrollingEnabled() {
return getScrollingChildHelper().isNestedScrollingEnabled();
}
@Override
public boolean startNestedScroll(int axes) {
return getScrollingChildHelper().startNestedScroll(axes);
}
@Override
public void stopNestedScroll() {
getScrollingChildHelper().stopNestedScroll();
}
@Override
public boolean hasNestedScrollingParent() {
return getScrollingChildHelper().hasNestedScrollingParent();
}
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
int dyUnconsumed, int[] offsetInWindow) {
return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed, offsetInWindow);
}
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return getScrollingChildHelper().dispatchNestedFling(velocityX, velocityY, consumed);
}
@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return getScrollingChildHelper().dispatchNestedPreFling(velocityX, velocityY);
}
都是直接的调用helper的方法,可以看出google为了减少开发者的学习和开发成本费了很多心思。Parent的helper相比于Child提供的方法就不是那么多了,主要的逻辑主要靠开发者自己实现。
流程
Child | Parent |
---|---|
startNestedScroll | onStartNestedScroll、onNestedScrollAccepted |
dispatchNestedPreScroll | onNestedPreScroll |
dispatchNestedScroll | onNestedScroll |
stopNestedScroll | onStopNestedScroll |
可以看出是一一对应的关系,整个过程是Child主导,Parent配合完成,用文字说明一下整个过程:
1. 准备过程
Child发起一个嵌套滑动处理,调用startNestedScroll.
Parent回调onStartNestedScroll说明收到了来自Child的请求,根据情况决定是否接受Child请求,通过返回值告诉Child结果。
Parent如果接受Child的请求还会回调onNestedScrollAccepted。
2. 处理过程
每次滑动前,Child 先询问 Parent 是否需要滑动,即 dispatchNestedPreScroll(),这就回调到 Parent 的 onNestedPreScroll(),Parent 可以在这个回调中“劫持”掉 Child 的滑动,也就是先于 Child 滑动。
Child 滑动以后,会调用 onNestedScroll(),回调到 Parent 的 onNestedScroll(),这里就是 Child 滑动后,剩下的给 Parent 处理,也就是 后于 Child 滑动。
3. 结束过程
Child调用stopNestedScroll发起一个结束请求,Parent回调回调onStopNestedScroll处理相关逻辑,比如重置参数等待下次滑动请求
版本兼容
虽然是Api21版本才提出的,google也提供了兼容方案,相关接口再android.support.v4包中