调用这个方法,在scrolling child 处理 fling 动作之前,提供机会scrolling parent 先于scrolling child 处理 fling 动作。
三个参数都是输入参数,velocityX 表示水平方向的速度,velocityY 表示垂直方向感的速度,consumed 表示scrolling child 是否消费 fling 动作 。
返回值也是布尔类型的,表示scrolling parent 是否有消费了fling 动作或者对 fling 动作做出相应的 处理。true 表示有,false 表示没有。
在 Scrolling child 处理 fling 动作之后,提供机会给 Scrolling Parent 处理 fling 动作。各个参数的意义这里就不再意义阐述了,跟 dispatchNestedFling 参数的意义是一样的。
当嵌套滑动的时候,会调用这个方法。
在 RecyclerView 中,当 Action_UP 或者 Actioon_cancel 或者 item 消费了 Touch 事件的时候,会调用这个方法。
NestedScrollingParent
Android 中已知的实现子类有 CoordinatorLayout, NestedScrollView, SwipeRefreshLayout。它通常是配合 NestedScrollingChild 进行嵌套滑动的。
在 Scrolling Child 开始滑动的时候会调用这个方法
当 Scrolling Child 调用 onStartNestedScroll 方法的时候,通过 NestedScrollingChildHelper 会回调 Scrolling parent 的 onStartNestedScroll 方法,如果返回 true, Scrolling parent 的 onNestedScrollAccepted(View child, View target, int nestedScrollAxes) 方法会被回调。
target 表示发起滑动事件的 View,Child 是 ViewParent 的直接子View,包含 target,nestedScrollAxes 表示滑动方向。
如果 Scrolling Parent 的onStartNestedScroll 返回 true, Scrolling parent 的 onNestedScrollAccepted(View child, View target, int nestedScrollAxes) 方法会被回调。
在 Scrolling Child 进行滑动之前,Scrolling Parent 可以先于Scrolling Child 进行相应的处理
如果 Scrolling Child 调用 dispatchNestedPreFling(float velocityX, float velocityY) ,通过 NestedScrollingChildHelper 会回调 Scrolling parent 的 onNestedPreScroll 方法
接下来的几个方法,我们不一一介绍了。与 Scrolling Child 方法几乎是一一对应的。
NetsedScrollingchildHelper 与 NestedScrollingParentHelper
我们知道 RecyclerView 是实现了 NestedScrollingChild 接口,下面我们一起来看一下RecyclerView 是怎样将事件传递给 Scrolling Parent 的。
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild
private NestedScrollingChildHelper getScrollingChildHelper() {
if (mScrollingChildHelper == null) {
mScrollingChildHelper = new NestedScrollingChildHelper(this);
}
return mScrollingChildHelper;
}
@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);
}
从代码中可以看到,它的很多逻辑都是交给 ChildHelper 去帮助 其完成的,下面我们一起来看一下 ChildHelper 里面的方法。
public boolean startNestedScroll(int axes) {
if (hasNestedScrollingParent()) {
// Already in progress
return true;
}
// 判断是否支持嵌套滑动,默认是支持的
if (isNestedScrollingEnabled()) {
ViewParent p = mView.getParent();
View child = mView;
// 从直接父 View 找起,看是否支持嵌套滑动
while (p != null) {
// //回调了父View的onStartNestedScroll方法
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()
p = p.getParent();
}
}
return false;
}
第一步,判断 P 是否为空,不为空, 从 P (初始值是RecyclerView 的直接父 View) 开始找起,判断其是否支持嵌套滑动,若支持,返回true,
第二步:若 P 不支持嵌套滑动,再将 p 指向 p.getParent(); 循环第一步
第三步:若循环了所有的 P ,都找不到支持嵌套滑动的 View,返回 false。
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);
// 保存刚开始 x 在 window 坐标系的偏移量
startX = offsetInWindow[0];
// 保存刚开始 y 方向在 window 坐标系的偏移量
startY = offsetInWindow[1];
}
// 调用 mNestedScrollingParent 的 onNestedScroll 方法
ViewParentCompat.onNestedScroll(mNestedScrollingParent, mView, dxConsumed,
dyConsumed, dxUnconsumed, dyUnconsumed);
// offsetInWindow 不为空
if (offsetInWindow != null) {
mView.getLocationInWindow(offsetInWindow);
// 得到 x 方向在 Window 坐标系的偏移量
offsetInWindow[0] -= startX;
// 得到 y 方向在 Window 坐标系的偏移量
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;
}
简单来说就是根据上一步在 startScrolled 方法中得到的 mNestedScrollingParent,调用 ViewParentCompat.onNestedScroll(mNestedScrollingParent, mView, dxConsumed,
dyConsumed, dxUnconsumed,dyUnconsumed);再根据是否有位移,做相应的处理。
看完了上面的两个主要方法,我们可以得出这样的一个结论:当我们调用 Scrolling Child 的 onStartNested 方法的时候,会通过 ChildHelper 去寻找是否有相应的 Scrolling Parent,如果有的话,会 回调相应的方法。同理 dispatchNestedPreScroll,dispatchNestedScroll,dispatchNestedPreFling 也是如此,这里不再一一带大家去看里面是怎样实现的,有兴趣的可以自己去阅读。
startNestedScroll ,dispatchNestedPreScroll 等方法的调用时机
这里我们同样以 RecyclerView 为例讲解:在 OnTouchEvent 方法里面,可以看到会根据不同的 Action 回调不同的方法,这里就不一一阐述了,回调方法的 事件请看代码。
public boolean onTouchEvent(MotionEvent e) {
// 如果 Item 处理了 Touch 事件,直接返回 true ,在在处理
if (dispatchOnItemTouch(e)) {
cancelTouch();
return true;
}
if (mLayout == null) {
return false;
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
boolean eventAddedToVelocityTracker = false;
switch (action) {
case MotionEvent.ACTION_DOWN: {
mScrollPointerId = e.getPointerId(0);
mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
if (canScrollHorizontally) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
}
if (canScrollVertically) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
}
// 在 Action_Down 的时候 调用 startNestedScroll
startNestedScroll(nestedScrollAxis);
} break;
case MotionEvent.ACTION_MOVE: {
// 在 Action_move 的时候,回调 dispatchNestedPreScroll 方法
if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {
// 减去 Scrolling Parent 的消费的值
dx -= mScrollConsumed[0];
dy -= mScrollConsumed[1];
vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
// Updated the nested offsets
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
}
由于文章篇幅原因,我只把面试题列了出来,详细的答案,我整理成了一份PDF文档,这份文档还包括了还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 ,帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习。
需要的朋友可以私信我【答案】或者点击这里免费领取