嵌套滑动的实现
RecyclerView
结合CoordinatorLayout
可以完成一个上滑悬浮(吸顶效果)的效果,这是因为它们实现了NestedScrollingChild
和NestedScrollingParent
接口,并配合NestedScrollingChildHelper
和NestedScrollingParentHelper
完成嵌套滑动的分工;
1. NestedScrollingChild接口:
嵌套滑动的孩子必须实现这个接口,RecyclerView就实现了这个接口;
-
startNestedScroll:
开始嵌套滑动 -
dispatchNestedPreScroll(dispatchNestedPreFling):
孩子开始滑动前调用 -
dispatchNestedScroll(dispatchNestedFling):
还是滑动后调用 -
stopNestedScroll:
结束嵌套滑动
Fling是快速滑动,即手指离开后还会根据计算的加速度继续滑动
2. NestedScrollingParent接口:
嵌套滑动的父亲实现这个接口,CoordinatorLayout实现了这个接口;
public interface NestedScrollingParent {
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);
public void onStopNestedScroll(View target);
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed);
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
public boolean onNestedPreFling(View target, float velocityX, float velocityY);
public int getNestedScrollAxes();
}
在parent接口中主要是child接口的回调,名称都是类似的;比如在开始滑动时,child调用startNestedScroll
,那么在这个方法里,会回调parent的onStartNestedScroll
,根据parent的返回值,child再做进一步处理;
RV的TouchEvent
@Override
public boolean onTouchEvent(MotionEvent e) {
...
if (dispatchOnItemTouch(e)) {
cancelTouch();
return true;
}
....
}
首先调用dispatchOnItemTouch()
交给item处理,如果item不消费当前事件才会进入下面的滑动处理
Down事件:
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;
}
startNestedScroll(nestedScrollAxis, TYPE_TOUCH);
} break;
- 记录当前手指开始的坐标
mInitialTouchX , mInitialTouchY
- 调用嵌套滑动接口的
startNestedScroll()
询问父亲是否处理(即调用parent的onStartNestedScroll()
具体的传递通过childHelper
完成)
Move事件:
case MotionEvent.ACTION_MOVE: {
final int x = (int) (e.getX(index) + 0.5f);
final int y = (int) (e.getY(index) + 0.5f);
int dx = mLastTouchX - x;
int dy = mLastTouchY - y;
if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset, TYPE_TOUCH)) {
dx -= mScrollConsumed[0];
dy -= mScrollConsumed[1];
vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
// Updated the nested offsets
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
}
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
vtev)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
if (mGapWorker != null && (dx != 0 || dy != 0)) {
mGapWorker.postFromTraversal(this, dx, dy);
}
}
} break;
- 记录滑动的距离
dx , dy
- 调用嵌套滑动接口
dispatchNestedPreScroll()
通知父亲回调onNestedPreScroll()
- 调用
scrollByInternal()
实现滑动的效果,(这个在RV缓存有详细展开) - 调用
mGapWorker.postFromTraversal()
来预加载
mGapWorker:
void postFromTraversal(RecyclerView recyclerView, int prefetchDx, int prefetchDy) {
if (recyclerView.isAttachedToWindow()) {
if (RecyclerView.DEBUG && !mRecyclerViews.contains(recyclerView)) {
throw new IllegalStateException("attempting to post unregistered view!");
}
if (mPostTimeNs == 0) {
mPostTimeNs = recyclerView.getNanoTime();
recyclerView.post(this);
}
}
}
- mGapWorker是一个Runnable,看一下run():
@Override
public void run() {
...
prefetch(nextFrameNs);
...
}
void prefetch(long deadlineNs) {
flushTasksWithDeadline(deadlineNs);
}
-
flushTasksWithDeadline()
最终调用了tryGetViewHolderForPositionByDeadline()
来预取ViewHolder,这是RV缓存的核心方法,(在RV缓存有详细介绍)
UP事件:
case MotionEvent.ACTION_UP: {
if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
setScrollState(SCROLL_STATE_IDLE);
}
resetTouch();
} break;
- 在
resetTouch()
当前滑动事件的收尾工作,包括stopNestedScroll(TYPE_TOUCH)
通知父亲回调onstopNestedScroll()
- 调用
fling()
判断是否是fling快速滑动
public boolean fling(int velocityX, int velocityY) {
if (!dispatchNestedPreFling(velocityX, velocityY)) {
final boolean canScroll = canScrollHorizontal || canScrollVertical;
dispatchNestedFling(velocityX, velocityY, canScroll);
if (mOnFlingListener != null && mOnFlingListener.onFling(velocityX, velocityY)) {
return true;
}
mViewFlinger.fling(velocityX, velocityY);
return true;
}
}
return false;
}
public void fling(int velocityX, int velocityY) {
setScrollState(SCROLL_STATE_SETTLING);
mLastFlingX = mLastFlingY = 0;
mScroller.fling(0, 0, velocityX, velocityY,
Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
postOnAnimation();
}
void postOnAnimation() {
ViewCompat.postOnAnimation(RecyclerView.this, this);
}
在fling()中判断如果是fling快速滑动:
- 调用嵌套滑动接口
dispatchNestedPreFling() , dispatchNestedFling()
- 调用
OverScroller.fling()
计算快速滑动的距离,通过postOnAnimation()
运行RV的ViewFlinger.run()
(提交给Handler)完成快速滑动,下面我们看run()做的事:
@Override
public void run() {
final OverScroller scroller = mScroller;
final SmoothScroller smoothScroller = mLayout.mSmoothScroller;
if (dispatchNestedPreScroll(dx, dy, scrollConsumed, null, TYPE_NON_TOUCH)) {
dx -= scrollConsumed[0];
dy -= scrollConsumed[1];
}
if (dx != 0) {
hresult = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
overscrollX = dx - hresult;
}
if (dy != 0) {
vresult = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
overscrollY = dy - vresult;
}
if (smoothScroller != null && !smoothScroller.isPendingInitialRun()
&& smoothScroller.isRunning()) {
final int adapterSize = mState.getItemCount();
if (adapterSize == 0) {
smoothScroller.stop();
} else if (smoothScroller.getTargetPosition() >= adapterSize) {
smoothScroller.setTargetPosition(adapterSize - 1);
smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY);
} else {
smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY);
}
}
}
if (hresult != 0 || vresult != 0) {
dispatchOnScrolled(hresult, vresult);
}
if (scroller.isFinished() || (!fullyConsumedAny
&& !hasNestedScrollingParent(TYPE_NON_TOUCH))) {
// setting state to idle will stop this.
setScrollState(SCROLL_STATE_IDLE);
if (ALLOW_THREAD_GAP_WORK) {
mPrefetchRegistry.clearPrefetchPositions();
}
stopNestedScroll(TYPE_NON_TOUCH);
} else {
postOnAnimation();
if (mGapWorker != null) {
mGapWorker.postFromTraversal(RecyclerView.this, dx, dy);
}
}
}
// call this after the onAnimation is complete not to have inconsistent callbacks etc.
if (smoothScroller != null) {
if (smoothScroller.isPendingInitialRun()) {
smoothScroller.onAnimation(0, 0);
}
if (!mReSchedulePostAnimationCallback) {
smoothScroller.stop(); //stop if it does not trigger any scroll
}
}
}
- 滑动前调用嵌套滑动接口
dispatchNestedPreScroll()
通知父亲 -
mLayout.scrollHorizontallyBy()/scrollVerticallyBy()
实现滑动,并配合RV内部类SmoothScroller
实现平滑动画效果 - 滑动后调用嵌套滑动接口
dispatchOnScrolled();
通知父亲 - 在run()中递归调用
postOnAnimation()
防止没有滑动结束
网友评论