-
何為嵌套滑動,這里不多說,列張效果圖,上面是TopView(一般是banner類),下面是RecyclerView,當recyclerView向上滑動時,topView跟隨往上滑動至隱藏后吸頂固定,recyclerView下拉到頂時繼續(xù)下拉,則把topView拉回初始位置:
nestscroll.gif
- 嵌套滑動關鍵的兩個接口:
NestedScrollingChild2和NestedScrollingParent2,繼承于NestedScrollingChild和NestedScrollingParent,在回調中增加了事件類型,便于處理fling慣性滑動狀態(tài)管理。目前最新的是NestedScrollingChild3和NestedScrollingParent3,在此僅分析2類。
- 首先看下兩個接口定義:
<NestedScrollingChild2.java>
嵌套滑動的內部View需要實現(xiàn)的接口
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);
/**
* 嵌套滑動后的事件分發(fā)
*
* <p>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.</p>
*
* <p>Compatible View implementations should also call
* {@link #dispatchNestedPreScroll(int, int, int[], int[], int) dispatchNestedPreScroll} before
* consuming a component of the scroll event themselves.</p>
*
* @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);
/**
* 嵌套滑動前的事件分發(fā)
*
* <p>Nested pre-scroll events are to nested scroll events what touch intercept is to touch.
* <code>dispatchNestedPreScroll</code> 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.</p>
*
* @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);
}
<NestedScrollingParent2.java>
嵌套滑動的外部ViewGroup需要實現(xiàn)的接口
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會立即觸發(fā)下面的onNestedScrollAccepted方法
*/
boolean onStartNestedScroll(@NonNull View child, @NonNull View target, @ScrollAxis int axes,
@NestedScrollType int type);
/**
*
* @param child 產生滑動事件的view的parent,向上遞歸查找到的實現(xiàn)了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);
}
- 下面以
RecyclerView為例,分析下NestedScrollingChild2的流程。
<RecyclerView.java>
// RecyclerView默認已經實現(xiàn)了NestedScrollingChild2接口
public class RecyclerView extends ViewGroup implements ScrollingView,
NestedScrollingChild2, NestedScrollingChild3 {
...
}
- 要觸發(fā)拖拽滑動,肯定是從
touch事件開始,定位到RecyclerView的onTouchEvent方法:
<RecyclerView.java>
@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: {
// 多指場景,多指按下后重置初始坐標和觸發(fā)滑動手指
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: {
// 獲取觸發(fā)滑動的那個手指
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狀態(tài),手指按下并移動距離大于臨界值,會標記為拖拽狀態(tài)
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);
}
}
// 拖拽狀態(tài)
if (mScrollState == SCROLL_STATE_DRAGGING) {
// mReusableIntPair這個數(shù)組保存了NestScrollParent消費的距離
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
// 開始分發(fā)嵌套滑動的距離
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];
// 自己處理剩余的滑動距離,并觸發(fā)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);
}
// 重置狀態(tài),通知嵌套滑動結束
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方法中處理嵌套滑動的流程很清晰:
<RecyclerView.java>
1.ACTION_DOWN 開始嵌套滑動,傳入滑動方向和滑動類型:
startNestedScroll(nestedScrollAxis, TYPE_TOUCH); ->NestScrollParent的onStartNestedScroll
2.ACTION_MOVE 開始分發(fā)嵌套滑動距離:
dispatchNestedPreScroll(canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
mReusableIntPair, mScrollOffset, TYPE_TOUCH
) -> NestScrollParent的onNestedPreScroll
scrollByInternal() -> NestScrollParent的onNestedScroll
3.ACTION_UP 開始通知嵌套滑動結束:
resetScroll(); ->onStopNestedScroll
// 其內部都是調用NestedScrollingChildHelper的具體實現(xiàn)
@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中這幾個方法對應的實現(xiàn)就行了,首先在down事件中開始嵌套滑動的一些準備工作,里面會觸發(fā)NestScrollParent的onStartNestedScroll和onNestedScrollAccepted方法:
<NestedScrollingChildHelper.java>
// 開始嵌套滑動,一些準備工作
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
<NestedScrollingChildHelper.java>
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,構造存儲消費滑動距離數(shù)據(jù)的數(shù)組
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;
}
<RecyclerView.java>
// 自己處理滑動
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();
}
// 重置數(shù)組,這個數(shù)組只是用來存儲數(shù)據(jù)而已,多處重置復用
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;
}
<NestedScrollingChildHelper.java>
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事件中會重置嵌套滑動狀態(tài),并調用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);
}
}
- 接下去,以實現(xiàn)上面示例圖常見的
TopView+RecyclerView的界面為例,分析下NestScrollParent的流程。首先自定義一個LinearLayout布局去實現(xiàn)NestScrollingParent2接口:
<NestScrollLinearLayout.java>
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 實現(xiàn)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 實現(xiàn)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優(yōu)先向上滑動
boolean hideTop = dy > 0 && getScrollY() < mTopViewHeight;
// 需要顯示topView,topView優(yōu)先向下滑動(一般是recyclerView滑動到頂部時繼續(xù)下拉,展示topView)
boolean showTop = dy < 0 && getScrollY() >= 0 && !target.canScrollVertically(-1);
if (hideTop || showTop) {
// topView需要優(yōu)先處理滑動
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則繼續(xù)往下滑
if (y < 0) {
y = 0;// topView初始狀態(tài)
}
// y大于topView的高度了,則topView不應該繼續(xù)往上滑動
if (y > mTopViewHeight) {
// 內容滑動最大為topView的高度
y = mTopViewHeight;
}
super.scrollTo(x, y);
}
}
