今天我们来看下NestedScrolling机制的源码。我们分析源码的目的是看看帮助类到底帮助了我们什么,以及父容器和子元素是怎么样配合的。
我们直接点,直接从NestedScrollingChildHelper开始:
构造方法:
public NestedScrollingChildHelper(View view) {
mView = view;
}
构造方法接收一个View然后赋值给mView,可见mView就是我们的子元素。
startNestedScroll:
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;
}
这个函数会先执行hasNestedScrollingParent(),根据字面意思我们可以大概猜到这个函数是用来判断父容器是否已经存在了,我们看下这个方法内部是啥:
public boolean hasNestedScrollingParent() {
return mNestedScrollingParent != null;
}
方法内部很简单,就是判断mNestedScrollingParent这个变量是否被赋值了,是的话就返回true,不是就返回fasle。那么mNestedScrollingParent是什么呢,根据名字我们可以猜到应该是实现了NestedScrolling机制的父容器。那么它是什么时候被赋值的呢,我们继续看startNestedScroll函数的代码。
首先最开始的时候,mNestedScrollingParent是空的,那么就会执行第二个if语句,然后isNestedScrollingEnabled()就是判断子元素是不是开启了NestedScrolling机制,如果开启了,就开始遍历子元素的父容器了,寻找实现了NestedScrolling机制的父容器。
我们跟进ViewParentCompat.onStartNestedScroll这个方法:
public static boolean onStartNestedScroll(ViewParent parent, View child, View target,
int nestedScrollAxes) {
return IMPL.onStartNestedScroll(parent, child, target, nestedScrollAxes);
}
发现它返回了IMPL的onStartNestedScroll方法。我们继续跟进:
@Override
public boolean onStartNestedScroll(ViewParent parent, View child, View target,
int nestedScrollAxes) {
if (parent instanceof NestedScrollingParent) {
return ((NestedScrollingParent) parent).onStartNestedScroll(child, target,
nestedScrollAxes);
}
return false;
}
发现它返回的是NestedScrollingParent的onStartNestedScroll,也就是这个时刻,完成了对父元素的onStartNestedScroll方法的回调。
那也就是说ViewParentCompat这个类就是帮助我们调用父容器的对应方法的,具体的实现方法没细看,用的应该是代理。
我们继续看startNestedScroll的代码,当找到了实现了机制的父容器后,就把这个父容器赋值给mNestedScrollingParent,然后回调onNestedScrollAccepted方法,然后返回true。
那么总结一下,startNestedScroll方法其实就是去找实现了机制的父容器,找到了就返回true,没找到就返回fasle。
stopNestedScroll:
public void stopNestedScroll() {
if (mNestedScrollingParent != null) {
ViewParentCompat.onStopNestedScroll(mNestedScrollingParent, mView);
mNestedScrollingParent = null;
}
}
stopNestedScroll方法就是调用父容器的方法,然后把mNestedScrollingParent置空。
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;
if (offsetInWindow != null) {
mView.getLocationInWindow(offsetInWindow);
startX = offsetInWindow[0];
startY = offsetInWindow[1];
}
//调用父容器的方法
ViewParentCompat.onNestedScroll(mNestedScrollingParent, mView, dxConsumed,
dyConsumed, dxUnconsumed, dyUnconsumed);
if (offsetInWindow != null) {
mView.getLocationInWindow(offsetInWindow);
//这里用父容器的终止位置减去起始位置,然后得到偏移量在赋值给offsetInWindow
offsetInWindow[0] -= startX;
offsetInWindow[1] -= startY;
}
return true;
} else if (offsetInWindow != null) {
// No motion, no dispatch. Keep offsetInWindow up to date.
offsetInWindow[0] = 0;
offsetInWindow[1] = 0;
}
}
return false;
}
这个方法主要做的就是调用父容器的方法,还有计算出偏移量来,我已经写上了注释,应该不难看懂。
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;
if (offsetInWindow != null) {
mView.getLocationInWindow(offsetInWindow);
startX = offsetInWindow[0];
startY = offsetInWindow[1];
}
//初始化consumed
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;
}
这个方法和上面那个差不多,就多了初始化consumed的步骤。
余下四个方法:
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
return ViewParentCompat.onNestedFling(mNestedScrollingParent, mView, velocityX,
velocityY, consumed);
}
return false;
}
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
return ViewParentCompat.onNestedPreFling(mNestedScrollingParent, mView, velocityX,
velocityY);
}
return false;
}
public void onDetachedFromWindow() {
ViewCompat.stopNestedScroll(mView);
}
public void onStopNestedScroll(View child) {
ViewCompat.stopNestedScroll(mView);
}
剩下的四个方法,就是调用父容器的方法而已。
我们总结一下NestedScrollingChildHelper做的事情,其实就两件事:
1:找到实现机制的父容器
2:调用父容器里对应的方法
我们接下来看下NestedScrollingParentHelper里的方法:
public NestedScrollingParentHelper(ViewGroup viewGroup) {
mViewGroup = viewGroup;
}
public void onNestedScrollAccepted(View child, View target, int axes) {
mNestedScrollAxes = axes;
}
public int getNestedScrollAxes() {
return mNestedScrollAxes;
}
public void onStopNestedScroll(View target) {
mNestedScrollAxes = 0;
}
很少,除去构造方法,剩下三个方法就是对axes的保存,取出和清零。
总结:
我们发现,两个接口之间方法的互相调用,主要是通过NestedScrollingChildHelper来实现的。我们在之后的项目中,可以学习借鉴这种思路:
1:用两个接口来解耦需要进行交互的view
2:提供封装了接口之间交互逻辑的helper类以方便用户使用接口
好了,这篇文章结束了,之后我会写一个更加复杂的实例来学习以下这个机制。