安卓实现嵌套滚动的机制解析
要知道在安卓中,处理事件分发的机制和滑动冲突都是通过View的dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent这三个方法互相配合完成的。
但是这种机制是由父View发起,一直向下传递,而且这个机制有个缺点:
父View将事件分发下去之后,一旦有子View决定处理该系列事件(即子View的onTouchEvent返回了true),那么该系列事件将会一直下发给子View处理,即使子View在某个onTouch事件中不想处理(即返回false),那它的父View也不会再继续处理此系列事件,除非父View拦截某个事件,但是父View拦截了touch事件之后,该系列事件就不会下发给子View处理了
于是乎就有了NestedScrolling机制了,NestedScrolling机制中,通过子View在处理滑动事件的手告诉父View我要开始滑动了,此时父View便会决定要不要跟着一起动,或者会寻找有没有另外的子View要跟着一起滑动。NestedScrolling机制主要依赖以下四个类:
- NestedScrollingChild
- NestedScrollingParent
- NestedScrollingChildHelper
- NestedScrollingParentHelper
一般作为滑动列表的子View实现NestedScrollingChild接口,作为parent的父View则实现NestedScrollingParent,这样滑动的子View就可以将滑动事件分发给父View,并且父View也可以将滑动事件分发给其他的子View了。
贴出NestedScrollingChild和NestedScrollingParent两个接口的API解释:
public interface NestedScrollingChild {
/**
* 设置嵌套滑动是否能用
*/
@Override
public void setNestedScrollingEnabled(boolean enabled);
/**
* 判断嵌套滑动是否可用
*/
@Override
public boolean isNestedScrollingEnabled();
/**
* 开始发起嵌套滑动
*
* @param axes 表示方向轴,有横向和竖向
*/
@Override
public boolean startNestedScroll(int axes);
/**
* 停止嵌套滑动
*/
@Override
public void stopNestedScroll();
/**
* 判断是否有支持嵌套滑动的父View
*/
@Override
public boolean hasNestedScrollingParent() ;
/**
* 惯性滑动时调用
* @param velocityX x 轴上的滑动速率
* @param velocityY y 轴上的滑动速率
* @param consumed 是否被消费
* @return true 如果惯性滑动被父View或其它子View消耗
*/
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) ;
/**
* 进行惯性滑动前调用
* @param velocityX x 轴上的滑动速率
* @param velocityY y 轴上的滑动速率
* @return true 父View消耗了惯性滑动
*/
@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) ;
/**
* 子view处理滑动之后调用(即子view滑动完之后通知父view)
* @param dxConsumed x轴上被消费的距离(横向)
* @param dyConsumed y轴上被消费的距离(竖向)
* @param dxUnconsumed x轴上未被消费的距离
* @param dyUnconsumed y轴上未被消费的距离
* @param offsetInWindow 子View的窗体偏移量
* @return true 事件被父View处理, false 事件没有被父View处理
*/
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow) ;
/**
* 在子View的onInterceptTouchEvent或者onTouch中,调用该方法通知父View滑动的距离(即在子view处理滑动之前通知父View是否要消耗滑动事件)
* @param dx x轴上滑动的距离
* @param dy y轴上滑动的距离
* @param consumed 父view消费掉的滑动长度
* @param offsetInWindow 子View的窗体偏移量
* @return 支持的嵌套的父View 是否处理了 滑动事件
*/
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow);
}
public interface NestedScrollingParent {
/**
* 回应子view发起的嵌套滑动
* @param child 实现了NestedScrollingParent的view
* @param target 发起嵌套滑动的子view
* @param axes 滑动方向
* @return true 表示接受嵌套滑动
*/
boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes);
/**
* 接受嵌套滑动时回调
* @param child 实现了NestedScrollingParent的view
* @param target 发起嵌套滑动的子view
* @param axes 滑动方向
*/
void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes);
/**
* 嵌套滑动结束回调
* @param target 发起嵌套滑动的子view
*/
void onStopNestedScroll(@NonNull View target);
/**
* 回应正在进行的嵌套滑动(后于子view)
* @param target 发起嵌套滚动的子view
* @param dxConsumed 已经消耗的水平滚动距离
* @param dyConsumed 已经消耗的垂直滚动距离
* @param dxUnconsumed 未消耗的水平滚动距离
* @param dyUnconsumed 未消耗的垂直滚动距离
*/
void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed);
/**
* 嵌套滚动开始之前回调(先于子view)
* @param target 发起嵌套滚动的子view
* @param dx 消耗的水平滚动距离
* @param dy 消耗的垂直滚动距离
* @param consumed 输出此父view消耗的水平和垂直滚动距离
*/
void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed);
/**
* 开始惯性滑动时回调(后于子view)
* @param var1
* @param var2
* @param var3
* @param var4
* @return
*/
boolean onNestedFling(@NonNull View var1, float var2, float var3, boolean var4);
/**
* 惯性滑动之前回调(先于子view)
* @param var1
* @param var2
* @param var3
* @return
*/
boolean onNestedPreFling(@NonNull View var1, float var2, float var3);
/**
* 返回NestedScrollingParent的滑动方向
* @return
*/
int getNestedScrollAxes();
}
ok,对两个接口的API有了一定的了解之后,现在我们来看一下NestedScrollingChild接口和NestedScrollingParent接口中的方法对应关系:
NestedScrollingChildImpl | NestedScrollingParentImpl |
---|---|
startNestedScroll | onStartNestedScroll, onNestedScrollAccepted |
dispatchNestedPreScroll | onNestedPreScroll |
dispatchNestedScroll | onNestedScroll |
stopNestedScroll | onStopNestedScroll |
接下来写个实例验证一下,先上效果图:
首先我们自定义一个YNestedScrollingChild继承自LinearLayout,然后实现NestedScrollingChild接口,并实现里面的方法,一般作为发起嵌套滑动的子View,只需自己实现onTouchEvent,在onTouchEvent里面处理相应的逻辑即可,其他NestedScrollingChild接口里面的方法就交给NestedScrollingChildHelper或它的super View去处理。
说明一点:为什么上面说可以交给super View处理呢?因为在API21或以上,安卓View里卖弄已经帮我们实现了NestedScrolling机制,即不需要我们手动实现NestedScrollingChild和NestedScrollingParent接口了,例如:
public class NestedScrollingChildImpl extends RelativeLayout {
public NestedScrollingChildImpl(Context context