编写接口movement.java_从 Android 开发到读懂源码 第02期:NestScroll 机制源码解析

从 Android 开发到读懂源码 第02期:NestScroll 机制源码解析

罗铁锤 悦专栏

cd5aaba4725754e027da4b52c9c5a51e.png

作者简介

罗铁锤,六年安卓踩坑经验,致力于底层平台、上层应用等多领域开发。文能静坐弹吉他,武能通宵写代码。

Android 提供了一个官方的嵌套滑动机制,这一节内容我们就一起来聊聊吧。

1 何为嵌套滑动

首先我们来看一看嵌套滑动的效果,上面是 TopView (一般是banner类),下面是 RecyclerView,当 recyclerView 向上滑动时,topView 跟随往上滑动至隐藏后吸顶固定,recyclerView 下拉到顶时继续下拉,则把 topView 拉回初始位置:

fdaa639d3f5c6104d405fa4546fe3658.png

2 嵌套滑动关键的两个接口

NestedScrollingChild2 和 NestedScrollingParent2,继承于 NestedScrollingChild 和 NestedScrollingParent,在回调中增加了事件类型,便于处理 fling 惯性滑动状态管理。目前最新的是 NestedScrollingChild3 和 NestedScrollingParent3,在此仅分析 2 类。

首先看下两个接口定义:

嵌套滑动的内部 View 需要实现的接口

public interface NestedScrollingChild2 extends NestedScrollingChild {

/**

* 开始滑动时应该调用

* @param axes 滑动方向 {@link ViewCompat#SCROLL_AXIS_HORIZONTAL}

* and/or {@link ViewCompat#SCROLL_AXIS_VERTICAL}.

* @param type 事件类型:ViewCompat.TYPE_TOUCH 和 TYPE_NON_TOUCH

* @return 遍历 parent 并且查找是否有 NestedScrollingParent 的父 view,如果存在切父 view 需要处理

* 嵌套滑动则返回 true

*/

boolean startNestedScroll(@ScrollAxis int axes, @NestedScrollType int type);

/**

* 滑动停止时应该调用

* @param type 事件类型:ViewCompat.TYPE_TOUCH和TYPE_NON_TOUCH

*/

void stopNestedScroll(@NestedScrollType int type);

/**

* 是否存在 NestedScrollingParent 的父 view

* @param type 事件类型:ViewCompat.TYPE_TOUCH 和 TYPE_NON_TOUCH

* @return 是否存在 NestedScrollingParent 的父 view

*/

boolean hasNestedScrollingParent(@NestedScrollType int type);

/**

* 嵌套滑动后的事件分发

*

*

Implementations of views that support nested scrolling should call this to report

* info about a scroll in progress to the current nested scrolling parent. If a nested scroll

* is not currently in progress or nested scrolling is not

* {@link #isNestedScrollingEnabled() enabled} for this view this method does nothing.

*

*

Compatible View implementations should also call

* {@link #dispatchNestedPreScroll(int, int, int[], int[], int) dispatchNestedPreScroll} before

* consuming a component of the scroll event themselves.

*

* @param dxConsumed 水平消费的距离

* @param dyConsumed 垂直消费的距离

* @param dxUnconsumed 水平消费后剩余的距离

* @param dyUnconsumed 垂直消费后剩余的距离

* @param offsetInWindow Optional. If not null, on return this will contain the offset

* in local view coordinates of this view from before this operation

* to after it completes. View implementations may use this to adjust

* expected input coordinate tracking.

* @param type 事件类型:ViewCompat.TYPE_TOUCH和TYPE_NON_TOUCH

* @return true if the event was dispatched, false if it could not be dispatched.

* @see #dispatchNestedPreScroll(int, int, int[], int[], int)

*/

boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,

int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow,

@NestedScrollType int type);

/**

* 嵌套滑动前的事件分发

*

*

Nested pre-scroll events are to nested scroll events what touch intercept is to touch.

* dispatchNestedPreScroll offers an opportunity for the parent view in a nested

* scrolling operation to consume some or all of the scroll operation before the child view

* consumes it.

*

* @param dx 该 child 水平滑动距离

* @param dy 该 child 垂直滑动距离

* @param consumed NestScrollParent 消费的距离:consumed[0] 代表水平消费距离,consumed[1] 代表垂直消费距离

* @param offsetInWindow Optional. If not null, on return this will contain the offset

* in local view coordinates of this view from before this operation

* to after it completes. View implementations may use this to adjust

* expected input coordinate tracking.

* @param type 事件类型:ViewCompat.TYPE_TOUCH和TYPE_NON_TOUCH

* @return true 表示 parent 消费了滑动距离

*/

boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,

@Nullable int[] offsetInWindow, @NestedScrollType int type);

}

嵌套滑动的外部 ViewGroup 需要实现的接口

public interface NestedScrollingParent2 extends NestedScrollingParent {

/**

*

* @param child Direct child of this ViewParent containing target

* @param target View that initiated the nested scroll

* @param axes 滑动方向: {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},

* {@link ViewCompat#SCROLL_AXIS_VERTICAL} or both

* @param type 事件类型:ViewCompat.TYPE_TOUCH和TYPE_NON_TOUCH

* @return true 表示该 parent 需要处理该次嵌套滑动,true 会立即触发下面的 onNestedScrollAccepted 方法

*/

boolean onStartNestedScroll(@NonNull View child, @NonNull View target, @ScrollAxis int axes,

@NestedScrollType int type);

/**

*

* @param child 产生滑动事件的 view 的 parent,向上递归查找到的实现了 NestScrollParent 的父 view

* @param target 产生滑动事件的 view

* @param axes 滑动方向: {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},

* {@link ViewCompat#SCROLL_AXIS_VERTICAL} or both

* @param type 事件类型:ViewCompat.TYPE_TOUCH 和 TYPE_NON_TOUCH

*/

void onNestedScrollAccepted(@NonNull View child, @NonNull View target, @ScrollAxis int axes,

@NestedScrollType int type);

/**

*

* @param target 产生滑动事件的 view

* @param type 事件类型:ViewCompat.TYPE_TOUCH和TYPE_NON_TOUCH

*/

void onStopNestedScroll(@NonNull View target, @NestedScrollType int type);

/**

*

* @param target 产生滑动事件的 view

* @param dxConsumed target 已经消费的水平距离

* @param dyConsumed target 已经消费的垂直距离

* @param dxUnconsumed target 消费后剩余的水平距离

* @param dyUnconsumed target 消费后剩余的水平距离

* @param type 事件类型:ViewCompat.TYPE_TOUCH和TYPE_NON_TOUCH

*/

void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,

int dxUnconsumed, int dyUnconsumed, @NestedScrollType int type);

/**

*

* @param target 产生滑动事件的 view

* @param dx target 水平滑动距离

* @param dy target 垂直滑动距离

* @param consumed 需要消费的距离,consumed[0] 代表水平消费距离,consumed[1] 代表垂直消费距离,默认 0

* @param type 事件类型:ViewCompat.TYPE_TOUCH 和 TYPE_NON_TOUCH

*/

void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed,

@NestedScrollType int type);

}

2.1 NestedScrollingChild2 流程分析

下面以 RecyclerView 为例:

// RecyclerView 默认已经实现了 NestedScrollingChild2 接口

public class RecyclerView extends ViewGroup implements ScrollingView,

NestedScrollingChild2, NestedScrollingChild3 {

...

}

要触发拖拽滑动,肯定是从 touch 事件开始,定位到 RecyclerView 的 onTouchEvent 方法:

@Override

public boolean onTouchEvent(MotionEvent e) {

...

// 是水平还是垂直滚动

final boolean canScrollHorizontally = mLayout.canScrollHorizontally();

final boolean canScrollVertically = mLayout.canScrollVertically();

if (mVelocityTracker == null) {

mVelocityTracker = VelocityTracker.obtain();

}

boolean eventAddedToVelocityTracker = false;

final int action = e.getActionMasked();

final int actionIndex = e.getActionIndex();

// 重置

if (action == MotionEvent.ACTION_DOWN) {

mNestedOffsets[0] = mNestedOffsets[1] = 0;

}

final MotionEvent vtev = MotionEvent.obtain(e);

vtev.offsetLocation(mNestedOffsets[0], mNestedOffsets[1]);

switch (action) {

// dwon

case MotionEvent.ACTION_DOWN: {

mScrollPointerId = e.getPointerId(0);

// 初始 down 坐标

mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);

mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);

// nestedScrollAxis 滑动方向标记

int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;

if (canScrollHorizontally) {

nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;

}

if (canScrollVertically) {

nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;

}

// 1.开始嵌套滑动:touch 拖拽类型

startNestedScroll(nestedScrollAxis, TYPE_TOUCH);

} break;

case MotionEvent.ACTION_POINTER_DOWN: {

// 多指场景,多指按下后重置初始坐标和触发滑动手指

mScrollPointerId = e.getPointerId(actionIndex);

mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f);

mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f);

} break;

// move

case MotionEvent.ACTION_MOVE: {

// 获取触发滑动的那个手指

final int index = e.findPointerIndex(mScrollPointerId);

if (index < 0) {

Log.e(TAG, "Error processing scroll; pointer index for id "

+ mScrollPointerId + " not found. Did any MotionEvents get skipped?");

return false;

}

// move 坐标

final int x = (int) (e.getX(index) + 0.5f);

final int y = (int) (e.getY(index) + 0.5f);

// 计算 move 距离

int dx = mLastTouchX - x;

int dy = mLastTouchY - y;

// fling 状态,手指按下并移动距离大于临界值,会标记为拖拽状态

if (mScrollState != SCROLL_STATE_DRAGGING) {

boolean startScroll = false;

if (canScrollHorizontally) {

if (dx > 0) {

dx = Math.max(0, dx - mTouchSlop);

} else {

dx = Math.min(0, dx + mTouchSlop);

}

if (dx != 0) {

startScroll = true;

}

}

...

if (startScroll) {

setScrollState(SCROLL_STATE_DRAGGING);

}

}

// 拖拽状态

if (mScrollState == SCROLL_STATE_DRAGGING) {

// mReusableIntPair 这个数组保存了 NestScrollParent 消费的距离

mReusableIntPair[0] = 0;

mReusableIntPair[1] = 0;

// 开始分发嵌套滑动的距离

if (dispatchNestedPreScroll(

canScrollHorizontally ? dx : 0,

canScrollVertically ? dy : 0,

mReusableIntPair, mScrollOffset, TYPE_TOUCH

)) {

// 计算 NestScrollParent 消费后剩余的距离,供自己消费

dx -= mReusableIntPair[0];

dy -= mReusableIntPair[1];

// Updated the nested offsets

mNestedOffsets[0] += mScrollOffset[0];

mNestedOffsets[1] += mScrollOffset[1];

// 要求 parent 不要拦截该 touch 事件,由自己处理

// Scroll has initiated, prevent parents from intercepting

getParent().requestDisallowInterceptTouchEvent(true);

}

mLastTouchX = x - mScrollOffset[0];

mLastTouchY = y - mScrollOffset[1];

// 自己处理剩余的滑动距离,并触发 NestScrollParent 的 onNestedPreScroll

if (scrollByInternal(

canScrollHorizontally ? dx : 0,

canScrollVertically ? dy : 0,

e)) {

getParent().requestDisallowInterceptTouchEvent(true);

}

if (mGapWorker != null && (dx != 0 || dy != 0)) {

mGapWorker.postFromTraversal(this, dx, dy);

}

}

} break;

...

// up

case MotionEvent.ACTION_UP: {

mVelocityTracker.addMovement(vtev);

eventAddedToVelocityTracker = true;

mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);

final float xvel = canScrollHorizontally

? -mVelocityTracker.getXVelocity(mScrollPointerId) : 0;

final float yvel = canScrollVertically

? -mVelocityTracker.getYVelocity(mScrollPointerId) : 0;

if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {

setScrollState(SCROLL_STATE_IDLE);

}

// 重置状态,通知嵌套滑动结束

resetScroll();

} break;

...

return true;

}

// 自己处理滑动

boolean scrollByInternal(int x, int y, MotionEvent ev) {

...

// 回调 NestScrollParent 的 onNestedScroll 方法

dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset,

TYPE_TOUCH, mReusableIntPair);

...

return consumedNestedScroll || consumedX != 0 || consumedY != 0;

}

private void resetScroll() {

if (mVelocityTracker != null) {

mVelocityTracker.clear();

}

// 通知嵌套滑动结束

stopNestedScroll(TYPE_TOUCH);

releaseGlows();

}

在 onTouchEvent 方法中处理嵌套滑动的流程很清晰:

ACTION_DOWN 开始嵌套滑动,传入滑动方向和滑动类型: startNestedScroll(nestedScrollAxis, TYPE_TOUCH); ->NestScrollParent的onStartNestedScroll

ACTION_MOVE 开始分发嵌套滑动距离: dispatchNestedPreScroll(canScrollHorizontally ? dx : 0,

canScrollVertically ? dy : 0, mReusableIntPair, mScrollOffset, TYPE_TOUCH ) -> NestScrollParent的onNestedPreScroll scrollByInternal() -> NestScrollParent的onNestedScroll

ACTION_UP 开始通知嵌套滑动结束: resetScroll(); ->onStopNestedScroll

// 其内部都是调用 NestedScrollingChildHelper 的具体实现

@Override

public boolean startNestedScroll(int axes, int type) {

return getScrollingChildHelper().startNestedScroll(axes, type);

}

@Override

public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow,

int type) {

return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow,

type);

}

...

所以,直接看 NestedScrollingChildHelper 中这几个方法对应的实现就行了,首先在 down 事件中开始嵌套滑动的一些准备工作,里面会触发 NestScrollParent 的 onStartNestedScroll 和 onNestedScrollAccepted 方法:

// 开始嵌套滑动,一些准备工作

public boolean startNestedScroll(@ScrollAxis int axes, @NestedScrollType int type) {

// 之前已经确认过存在 NestScrollParent 需要消费事件,直接返回 true

if (hasNestedScrollingParent(type)) {

// Already in progress

return true;

}

// 支持嵌套滑动

if (isNestedScrollingEnabled()) {

ViewParent p = mView.getParent();// 父 ViewParent

View child = mView;// 当前 recyclerView

// 遍历查找父 ViewParent,看是否存在需要消费事件的 NestScrollParent

while (p != null) {

// 这里会调用 NestScrollParent 的 onStartNestedScroll 方法

if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes, type)) {

// 缓存当前 NestScrollParent,下次进来就直接上面 return true 了,避免多余查找操作

setNestedScrollingParentForType(type, p);

// 如 onStartNestedScroll 返回 true,立即回调 NestScrollParent 的 onNestedScrollAccepted方法

ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes, type);

return true;

}

// 递归查找

if (p instanceof View) {

child = (View) p;

}

p = p.getParent();

}

}

// 不存在说明 parent 不需要处理嵌套滑动,返回 false

return false;

}

接着 move 事件中会计算出滑动距离,并调用 dispatchNestedPreScroll

public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,

@Nullable int[] offsetInWindow, @NestedScrollType int type) {

// 支持嵌套滑动

if (isNestedScrollingEnabled()) {

final ViewParent parent = getNestedScrollingParentForType(type);

if (parent == null) {

return false;

}

// 有滑动

if (dx != 0 || dy != 0) {

int startX = 0;

int startY = 0;

// view 在 window 的坐标

if (offsetInWindow != null) {

mView.getLocationInWindow(offsetInWindow);

startX = offsetInWindow[0];

startY = offsetInWindow[1];

}

// 如果为 null,构造存储消费滑动距离数据的数组

if (consumed == null) {

consumed = getTempNestedScrollConsumed();

}

consumed[0] = 0;

consumed[1] = 0;

// 回调 NestScrollParent的onNestedPreScroll 方法

ViewParentCompat.onNestedPreScroll(parent, mView, dx, dy, consumed, type);

if (offsetInWindow != null) {

mView.getLocationInWindow(offsetInWindow);

offsetInWindow[0] -= startX;

offsetInWindow[1] -= startY;

}

// 返回值是 NestScrollParent 是否消费了距离

return consumed[0] != 0 || consumed[1] != 0;

} else if (offsetInWindow != null) {

offsetInWindow[0] = 0;

offsetInWindow[1] = 0;

}

}

return false;

}

// 自己处理滑动

boolean scrollByInternal(int x, int y, MotionEvent ev) {

int unconsumedX = 0;

int unconsumedY = 0;

int consumedX = 0;

int consumedY = 0;

consumePendingUpdateOperations();

if (mAdapter != null) {

// mReusableIntPair 这里会重置并且储存自身滑动的距离值

mReusableIntPair[0] = 0;

mReusableIntPair[1] = 0;

// 内部计算出自己需要滑动的距离

scrollStep(x, y, mReusableIntPair);

// 自身滑动,消费的距离

consumedX = mReusableIntPair[0];

consumedY = mReusableIntPair[1];

// 自己滑动后剩余的未消费的距离,这里留个问题:为什么自己不全部消费掉?

unconsumedX = x - consumedX;

unconsumedY = y - consumedY;

}

if (!mItemDecorations.isEmpty()) {

invalidate();

}

// 重置数组,这个数组只是用来存储数据而已,多处重置复用

mReusableIntPair[0] = 0;

mReusableIntPair[1] = 0;

// 跟进去最终调用 dispatchNestedScrollInternal,回调 NestScrollParent 的 onNestedScroll 方法

dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset,

TYPE_TOUCH, mReusableIntPair);

unconsumedX -= mReusableIntPair[0];// 最终剩余的距离

unconsumedY -= mReusableIntPair[1];// 最终剩余的距离

// 看下 NestScrollParent 是否有消费剩余的滑动距离

boolean consumedNestedScroll = mReusableIntPair[0] != 0 || mReusableIntPair[1] != 0;

// Update the last touch co-ords, taking any scroll offset into account

mLastTouchX -= mScrollOffset[0];

mLastTouchY -= mScrollOffset[1];

mNestedOffsets[0] += mScrollOffset[0];

mNestedOffsets[1] += mScrollOffset[1];

// 处理 OVER_SCROLL 场景

if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {

if (ev != null && !MotionEventCompat.isFromSource(ev, InputDevice.SOURCE_MOUSE)) {

pullGlows(ev.getX(), unconsumedX, ev.getY(), unconsumedY);

}

considerReleasingGlowsOnScroll(x, y);

}

// 自身滑动

if (consumedX != 0 || consumedY != 0) {

dispatchOnScrolled(consumedX, consumedY);

}

if (!awakenScrollBars()) {

invalidate();

}

return consumedNestedScroll || consumedX != 0 || consumedY != 0;

}

private boolean dispatchNestedScrollInternal(int dxConsumed, int dyConsumed,

int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow,

@NestedScrollType int type, @Nullable int[] consumed) {

// 可嵌套滑动

if (isNestedScrollingEnabled()) {

final ViewParent parent = getNestedScrollingParentForType(type);

if (parent == null) {

return false;

}

// 存在 child 需要消费或者还剩余未消费距离

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

}

if (consumed == null) {

consumed = getTempNestedScrollConsumed();

consumed[0] = 0;

consumed[1] = 0;

}

// 回调 NestScrollParent 的 onNestedScroll 方法

ViewParentCompat.onNestedScroll(parent, mView,

dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed);

if (offsetInWindow != null) {

mView.getLocationInWindow(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;

}

最后 up 事件中会重置嵌套滑动状态,并调用 stopNestedScroll ,至此,NestScrollChild 的整个流程结束。

public void stopNestedScroll(@NestedScrollType int type) {

ViewParent parent = getNestedScrollingParentForType(type);

if (parent != null) {

// 回调 NestScrollParent 的 onStopNestedScroll,本次嵌套滑动结束

ViewParentCompat.onStopNestedScroll(parent, mView, type);

setNestedScrollingParentForType(type, null);

}

}

2.2 NestScrollingParent2 接口实现

接下去,以实现上面示例图常见的 TopView + RecyclerView 的界面为例,分析下 NestScrollParent 的流程。首先自定义一个 LinearLayout 布局去实现 NestScrollingParent2 接口:

public class NestScrollLinearLayout extends LinearLayout implements NestedScrollingParent2 {

private View mTopView;// 顶部布局

private View mRecyclerView;// 下面的 RecyclerView

private int mTopViewHeight;// 顶部布局高度

public NestScrollLinearLayout(Context context) {

this(context, null);

}

public NestScrollLinearLayout(Context context, @Nullable AttributeSet attrs) {

this(context, attrs, 0);

}

public NestScrollLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

setOrientation(VERTICAL);// 设置为垂直方向

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

ViewGroup.LayoutParams layoutParams = mRecyclerView.getLayoutParams();

// 将 recyclerView 的高度设置为当前 parent 高度

layoutParams.height = getMeasuredHeight();

mRecyclerView.setLayoutParams(layoutParams);

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

}

@Override

protected void onFinishInflate() {

super.onFinishInflate();

// xml 解析完成

if (getChildCount() > 0) {

mTopView = getChildAt(0);

}

if (getChildCount() > 1) {

mRecyclerView = getChildAt(1);

}

}

@Override

protected void onSizeChanged(int w, int h, int oldw, int oldh) {

super.onSizeChanged(w, h, oldw, oldh);

if (mTopView != null) {

// 获取 topView 的高度

mTopViewHeight = mTopView.getMeasuredHeight();

}

}

/**

* 即将开始嵌套滑动,由子 view 的 startNestedScroll 方法调用

*

* @param child 实现 NestScrollChild 的 View,不一定 =target

* @param target 产生滑动事件具体的view

* @param axes 滑动方向

* @param type 上面分析所说的类型

* @return true 表示自身需要处理嵌套滑动

*/

@Override

public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes, int type) {

// 垂直方向需要处理嵌套滑动

return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;

}

/**

* 当 onStartNestedScroll 返回为 true 时调用

*

* @param child 实现 NestScrollChild 的 View,不一定 =target

* @param target 产生滑动事件具体的 view

* @param axes 滑动方向

* @param type 上面分析所说的滑动类型

*/

@Override

public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes, int type) {

}

/**

* 在子 view 滑动之前,会先回调此方法,由父 View 决定消耗滑动距离并将消耗的距离赋值给 consumed

*

* @param target 产生滑动事件具体的 view

* @param dx target 水平方向滑动距离

* @param dy target 垂直方向滑动距离

* @param consumed 返回给 target,parent 消耗的距离

* @param type 滑动类型

*/

@Override

public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {

// 需要隐藏 topView,topView 优先向上滑动

boolean hideTop = dy > 0 && getScrollY() < mTopViewHeight;

// 需要显示 topView,topView 优先向下滑动(一般是 recycelerView 滑动到顶部时继续下拉,展示 topView)

boolean showTop = dy < 0 && getScrollY() >= 0 && !target.canScrollVertically(-1);

if (hideTop || showTop) {

// topView 需要优先处理滑动

scrollBy(0, dy);

consumed[1] = dy;

}

}

/**

* 子 view 消耗剩余距离后,如果还有剩余,则把剩余的距离再次回调给 parent

*

* @param target 产生滑动事件具体的 view

* @param dxConsumed target 消耗的水平滑动距离

* @param dyConsumed target 消耗的垂直滑动距离

* @param dxUnconsumed target 消耗后剩余的水平滑动距离

* @param dyUnconsumed target 消耗后剩余的垂直滑动距离

*/

@Override

public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {

}

/**

* 停止滑动

*

* @param target 产生滑动事件具体的 view

* @param type 滑动类型

*/

@Override

public void onStopNestedScroll(@NonNull View target, int type) {

}

@Override

public void scrollTo(int x, int y) {

// 内容滑动不能为负,为负的话 topView 则继续往下滑

if (y < 0) {

y = 0;// topView 初始状态

}

// y 大于 topView 的高度了,则 topView 不应该继续往上滑动

if (y > mTopViewHeight) {

// 内容滑动最大为 topView 的高度

y = mTopViewHeight;

}

super.scrollTo(x, y);

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值