NestedScrolling 机制深入解析,你真的了解Android系统启动流程吗

  • boolean dispatchNestedPreFling(float velocityX, float velocityY, boolean consumed)

调用这个方法,在scrolling child 处理 fling 动作之前,提供机会scrolling parent 先于scrolling child 处理 fling 动作。

三个参数都是输入参数,velocityX 表示水平方向的速度,velocityY 表示垂直方向感的速度,consumed 表示scrolling child 是否消费 fling 动作 。

返回值也是布尔类型的,表示scrolling parent 是否有消费了fling 动作或者对 fling 动作做出相应的 处理。true 表示有,false 表示没有。

  • boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed)

在 Scrolling child 处理 fling 动作之后,提供机会给 Scrolling Parent 处理 fling 动作。各个参数的意义这里就不再意义阐述了,跟 dispatchNestedFling 参数的意义是一样的。

  • void stopNestedScroll

当嵌套滑动的时候,会调用这个方法。

在 RecyclerView 中,当 Action_UP 或者 Actioon_cancel 或者 item 消费了 Touch 事件的时候,会调用这个方法。


NestedScrollingParent


Android 中已知的实现子类有 CoordinatorLayout, NestedScrollView, SwipeRefreshLayout。它通常是配合 NestedScrollingChild 进行嵌套滑动的。

  • boolean onStartNestedScroll(View child, View target, int nestedScrollAxes)

在 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 表示滑动方向。

  • void onNestedScrollAccepted(View child, View target, int nestedScrollAxes)

如果 Scrolling Parent 的onStartNestedScroll 返回 true, Scrolling parent 的 onNestedScrollAccepted(View child, View target, int nestedScrollAxes) 方法会被回调。

  • boolean onNestedPreScroll(View target, int dx, int dy, int[] consumed)

在 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 里面的方法。

startNestedScroll 方法

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。

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);

// 保存刚开始 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开发面试专题资料,高级进阶架构资料 ,帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习。

需要的朋友可以私信我【答案】或者点击这里免费领取

你可能感兴趣的:(程序员,面试,移动开发,android)