1.什么是NestedScrolling?
NestedScrolling(嵌套滑动机制)是Google在Android 5.0版本之后提供的一种新的滑动特性, 是一种嵌套view之间嵌套滑动的互动机制, 更方便于实现复杂的嵌套滑动效果。
2.为什么会有NestedScrolling?
view的touch事件分发机制是自上而下的, 主要是通过这几个函数来处理:dispatchTouchEvent一一传递,onInterceptTouchEvent一一拦截、onTouchEvent一一消费
这种机制有个痛点:
touch事件 要么被 父View 处理,要么被 子View 处理,很难在两者之间进行交互处理。
但Google提供的NestedScrolling机制能够很好地解决这个问题。
3.NestedScrolling的实现分析
NestedScrolling实现主要依赖以下四个类:
** NestedScrollingChild **
** NestedScrollingParent **
** NestedScrollingChildHelper**
** NestedScrollingParentHelper**
NestedScrollingChild 和 NestedScrollingParent 是接口类, NestedScrollingParent 和 NestedScrollingParentHelper则是嵌套父view和子view嵌套滑动交互的辅助类, 主要是实现了嵌套父view与子view之间的交互。所以想要实现NestedScrolling,
嵌套子view需实现NestedScrollingChild 接口, 将相关事件委托给NestedScrollingChildHelper处理
嵌套父view需实现NestedScrollingParent 接口, 将滑动事件委托给NestedScrollingParentHelper处理。
NestedScrollingChild
//设置是否支持嵌套滑动
@Override
public void setNestedScrollingEnabled(boolean enabled) {
super.setNestedScrollingEnabled(enabled);
mChildHelper.setNestedScrollingEnabled(enabled);
}
//嵌套滑动是否可用
@Override
public boolean isNestedScrollingEnabled() {
return mChildHelper.isNestedScrollingEnabled();
}
//设置嵌套滑动方向
@Override
public boolean startNestedScroll(int axes) {
return mChildHelper.startNestedScroll(axes);
}
//停止嵌套滑动
@Override
public void stopNestedScroll() {
mChildHelper.stopNestedScroll();
}
//父View是否支持嵌套滑动
@Override
public boolean hasNestedScrollingParent() {
return mChildHelper.hasNestedScrollingParent();
}
//处理滑动事件
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow)
return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
}
//处理滑动事件前的准备动作
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
//处理滑行事件
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
}
//滑行前的准备动作
@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);
}
NestedScrollingParent
//是否允许嵌套滑动
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
return super.onStartNestedScroll(child, target, nestedScrollAxes);
}
//onStartNestedScroll()方法返回true会调用该函数
@Override
public void onNestedScrollAccepted(View child, View target, int axes) {
super.onNestedScrollAccepted(child, target, axes);
}
//嵌套滑动停止
@Override
public void onStopNestedScroll(View target) {
super.onStopNestedScroll(target);
}
//子View的嵌套滑动事件
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
}
//子View嵌套滑动前的准备动作
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
super.onNestedPreScroll(target, dx, dy, consumed);
}
//嵌套滑动子View的滑行情况
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
return super.onNestedFling(target, velocityX, velocityY, consumed);
}
//嵌套滑动子View滑行前的准备动作
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
return super.onNestedPreFling(target, velocityX, velocityY);
}
//嵌套滑动方向
@Override
public int getNestedScrollAxes() {
return super.getNestedScrollAxes();
}
4.NestedScrolling工作流程
在NestedScrolling(嵌套滑动机制)中, 嵌套子View是事件发起者, 嵌套父View则对事件进行响应处理和结果回调。工作流程如下:
4.1 startNestedScroll
子View调用startNestedScroll方法开启嵌套滑动, 寻找支持NestedScrolling的父View, 通知父View开始嵌套滑动,这时父View的onStartNestedScroll和onNestedScrollAccepted会被调用。
具体实现我们看一下NestedScrollingChildHelper里面的方法:
public boolean startNestedScroll(int axes) {
if (hasNestedScrollingParent()) {
// Already in progress
return true;
}
if (isNestedScrollingEnabled()) {
ViewParent p = mView.getParent();
View child = mView;
while (p != null) {
if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) {
mNestedScrollingParent = p;
ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes);
return true;
}
if (p instanceof View) {
child = (View) p;
}
p = p.getParent();
}
}
return false;
}
如果父View的onStartNestedScroll返回true,表示接受此次的嵌套滑动, 会继续执行onNestedScrollAccepted方法;
如果返回false则表示不想处理此次的滑动, 那么子View就会继续向上寻找支持滑动的父View。
4.2 dispatchNestedPreScroll
滑动开始前, 子View会调用dispatchNestedPreScroll方法回调父View的onNestedPreScroll方法, 询问父View是否需要进行滑动处理, 如果父View进行了滑动处理, 会将消费的距离信息通过consumed回传给子View, 子View根据父View的消费情况来决定自己的滑动处理。 如果父View不需要滑动处理,则子View自己进行滑动处理。
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
if (dx != 0 || dy != 0) {
int startX = 0;
int startY = 0;
if (offsetInWindow != null) {
mView.getLocationInWindow(offsetInWindow);
startX = offsetInWindow[0];
startY = offsetInWindow[1];
}
if (consumed == null) {
if (mTempNestedScrollConsumed == null) {
mTempNestedScrollConsumed = new int[2];
}
consumed = mTempNestedScrollConsumed;
}
consumed[0] = 0;
consumed[1] = 0;
ViewParentCompat.onNestedPreScroll(mNestedScrollingParent, mView, dx, dy, consumed);
if (offsetInWindow != null) {
mView.getLocationInWindow(offsetInWindow);
offsetInWindow[0] -= startX;
offsetInWindow[1] -= startY;
}
return consumed[0] != 0 || consumed[1] != 0;
} else if (offsetInWindow != null) {
offsetInWindow[0] = 0;
offsetInWindow[1] = 0;
}
}
return false;
}
4.3 dispatchNestedScroll
子view滑动后,可调用dispatchNestedScroll将自己的滑动情况告诉父View, 父View的onNestedScroll会被回调,如果父view接受了它的滚动参数,进行了部分消费,则这个函数返回true,否则为false
ps: dispatchNestedFling 与dispatchNestedScroll类似
4.4 stopNestedScroll
子View调用stopNestedScroll结束整个滑动流程, 父view的onStopNestedScroll方法被回调结束滑动
子view | 父view |
---|---|
startNestedScroll | onStartNestedScroll、onNestedScrollAccepted |
dispatchNestedPreScroll | onNestedPreScroll |
dispatchNestedScroll | onNestedScroll |
dispatchNestedFling | onNestedFling |
stopNestedScroll | onStopNestedScroll |