View中NestScroll方法如图所示。所谓嵌套滚动,是针对View与ViewGroup来说的,即二者联动。
当我们手指在一个View上滑动一定delta距离时,通过scrollTo让View实现delta距离的滚动,触屏事件完全消耗在View上面,父View无法消费滑动事件。为了让View与父View同时对某一滑屏距离做出滑动反应,View和ViewGroup的交互接口ViewParent包含NestedScroll相关方法,于是就有了嵌套滚动这个东西。
![](https://img.haomeiwen.com/i5964029/81b0fedb16a29910.png)
![](https://img.haomeiwen.com/i5964029/8a78f3635a3f57de.png)
一种是父View主动型,子View在消费事件滑动的距离时,先问问父View,父View主动决定自己消耗掉多少距离,然后给子View留多少距离。
另一种是子View主动型,子View先消耗,然后将消耗与未消耗的距离一起告诉父View。
View开始准备NestScroll嵌套滚动时,触发View#startNestedScroll方法。
View#startNestedScroll方法。
public boolean startNestedScroll(int axes) {
//已经存在mNestedScrollingParent的节点,直接返回
if (hasNestedScrollingParent()) {
return true;
}
if (isNestedScrollingEnabled()) {
ViewParent p = getParent();
View child = this;
while (p != null) {
try {
if (p.onStartNestedScroll(child, this, axes)) {
mNestedScrollingParent = p;
p.onNestedScrollAccepted(child, this, axes);
return true;
}
} catch (AbstractMethodError e) {
}
if (p instanceof View) {
child = (View) p;
}
p = p.getParent();
}
}
return false;
}
- View支持嵌套滚动时,isNestedScrollingEnabled方法是true,通过setNestedScrollingEnabled方法设置。
- 在树视图结构中,向上一层层查找ViewParent父节点,触发ViewParent#onStartNestedScroll方法,父视图支持嵌套滚动的条件是onStartNestedScroll方法返回true,ViewGroup#onStartNestedScroll默认返回值false。支持NestScroll的父容器需重写onStartNestedScroll方法。
- 没有父容器的onStartNestedScroll是true,说明均不支持嵌套滚动。
ViewGroup#onStartNestedScroll方法
@Override
public boolean onStartNestedScroll(View child, View target,
int nestedScrollAxes) {
return false;
}
- 重写ViewGroup#onStartNestedScroll方法,父容器接受嵌套滚动,将View的mNestedScrollingParent设为该父容器。
- ViewGroup#onNestedScrollAccepted方法设置axes滚动轴。
通过startNestedScroll验证同步View与父View支持NestedScroll后,下面开启滚动。
dispatchNestedPreScroll
View#dispatchNestedPreScroll,把自己的滚动机会先让给父View
View#dispatchNestedPreScroll方法。
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;
...
//先初始化为0,
consumed[0] = 0;
consumed[1] = 0;
mNestedScrollingParent.onNestedPreScroll(this, dx, dy, consumed);
...
return consumed[0] != 0 || consumed[1] != 0;
} else if (offsetInWindow != null) {
offsetInWindow[0] = 0;
offsetInWindow[1] = 0;
}
}
return false;
}
父View触发onNestedPreScroll方法时,通过父View的scrollBy方法移动父View的内部视图,将移动的(即消耗掉)的距离存储在consumed中,通过consumed数组通知子View,我已经消耗掉你应该移动的距离了,你自己就别动了,跟我一起动吧,这时处理的是子View的onTouch事件。
父View先消耗,子View紧跟着父View移动,未消耗掉的子View继续消耗,让子View与父View联动。
父View重写onNestedPreScroll方法。ViewGroup#onNestedPreScroll方法默认什么都不做。
dispatchNestedScroll
View#dispatchNestedScroll,把自己的滚动机会先让给自己。
View#dispatchNestedScroll。
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
if (dxConsumed != 0 || dyConsumed != 0 ||
dxUnconsumed != 0 || dyUnconsumed != 0) {
int startX = 0;
int startY = 0;
...
mNestedScrollingParent.onNestedScroll(this, dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed);
...
return true;
} else if (offsetInWindow != null) {
offsetInWindow[0] = 0;
offsetInWindow[1] = 0;
}
}
return false;
}
父View触发onNestedScroll方法时,子View主动将消费的距离与未消费的距离通知父View。
父View重写onNestedScroll方法。ViewGroup#onNestedScroll方法默认什么都不做。
当父View收到子View未能消费的距离后,在父View的坐标下完成剩余偏移。这种场景是子View主动先消费,剩下的交给父View。
任重而道远
网友评论