1.第一条调用时机
初始化加载时会摆放时会调用onLayout,可是这个onLayout具体是什么时候被调用的?
recyclerView的onLayout的方法如下:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
mFirstLayoutComplete = true;
}
摆放时会调用这个方法,继续调用dispatchLayout,dispatchLayout的代码如下:
void dispatchLayout() {
if (mAdapter == null) {
Log.w(TAG, "No adapter attached; skipping layout");
// leave the state in START
return;
}
if (mLayout == null) {
Log.e(TAG, "No layout manager attached; skipping layout");
// leave the state in START
return;
}
mState.mIsMeasuring = false;
// If the last time we measured children in onMeasure, we skipped the measurement and layout
// of RV children because the MeasureSpec in both dimensions was EXACTLY, and current
// dimensions of the RV are not equal to the last measured dimensions of RV, we need to
// measure and layout children one last time.
boolean needsRemeasureDueToExactSkip = mLastAutoMeasureSkippedDueToExact
&& (mLastAutoMeasureNonExactMeasuredWidth != getWidth()
|| mLastAutoMeasureNonExactMeasuredHeight != getHeight());
mLastAutoMeasureNonExactMeasuredWidth = 0;
mLastAutoMeasureNonExactMeasuredHeight = 0;
mLastAutoMeasureSkippedDueToExact = false;
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates()
|| needsRemeasureDueToExactSkip
|| mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {
// First 2 steps are done in onMeasure but looks like we have to run again due to
// changed size.
// TODO(shepshapard): Worth a note that I believe
// "mLayout.getWidth() != getWidth() || mLayout.getHeight() != getHeight()" above is
// not actually correct, causes unnecessary work to be done, and should be
// removed. Removing causes many tests to fail and I didn't have the time to
// investigate. Just a note for the a future reader or bug fixer.
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}
这个方法从方法名上面很好理解,就是把layout分发出去,其中关键性的代码如下:
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates()
|| needsRemeasureDueToExactSkip
|| mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {
// First 2 steps are done in onMeasure but looks like we have to run again due to
// changed size.
// TODO(shepshapard): Worth a note that I believe
// "mLayout.getWidth() != getWidth() || mLayout.getHeight() != getHeight()" above is
// not actually correct, causes unnecessary work to be done, and should be
// removed. Removing causes many tests to fail and I didn't have the time to
// investigate. Just a note for the a future reader or bug fixer.
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
解释一下,几种状态:
(1)这个State的Step是STEP_START这个状态的时候,执行dispatchLayoutStep1(),之后执行dispatchLayoutStep2(),(问题:这个状态是干嘛的?为什么这个状态才执行?)
(2)如果有更新,宽高不等时会执行dispatchLayoutStep2(),
(3)其他情况下执行dispatchLayoutStep3(),(问题:这个方法里面又是做了什么?)
dispatchLayoutStep2的代码如下:
private void dispatchLayoutStep2() {
startInterceptRequestLayout();
onEnterLayoutOrScroll();
mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
mAdapterHelper.consumeUpdatesInOnePass();
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
if (mPendingSavedState != null && mAdapter.canRestoreState()) {
if (mPendingSavedState.mLayoutState != null) {
mLayout.onRestoreInstanceState(mPendingSavedState.mLayoutState);
}
mPendingSavedState = null;
}
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
// onLayoutChildren may have caused client code to disable item animations; re-check
mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
mState.mLayoutStep = State.STEP_ANIMATIONS;
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);
}
这个方法也是一定会调用mLayout.onLayoutChildren(mRecycler, mState),调用mLayout的方法,因为这个变量是实际设置进去的,就是LayoutManager,LayoutManager是抽象类,是需要具体的实现的,这就是为什么RecyclerView在初始化时需要设置一个LayoutManager,不设置的时候是跑不通的,这只是一个原因,应该还有很多其他原因的代码调用,可以先随便找一个他的实现类,比如LinearLayoutManager这个类,就满足要求,看一下他的onLayoutChildren()方法里面执行了什么具体内容,这个方法由于过长,只截取部分内容,其余部分省略掉,
/**
* {@inheritDoc}
*/
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state){
……
if (mAnchorInfo.mLayoutFromEnd) {
……
fill(recycler, mLayoutState, state, false);
……
} else {
……
fill(recycler, mLayoutState, state, false);
……
}
……
}
这个里面都会走到这个fill方法,
/**
* The magic functions :). Fills the given layout, defined by the layoutState. This is fairly
* independent from the rest of the {@link LinearLayoutManager}
* and with little change, can be made publicly available as a helper class.
*
* @param recycler Current recycler that is attached to RecyclerView
* @param layoutState Configuration on how we should fill out the available space.
* @param state Context passed by the RecyclerView to control scroll steps.
* @param stopOnFocusable If true, filling stops in the first focusable new child
* @return Number of pixels that it added. Useful for scroll functions.
*/
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable){
}
翻译如下:神奇的方法,填充给定的layout,根据layoutState设定的,这个方法可以设置成独立的静态方法,但是需要做一点点变动,可以用作一个help类。
fill方法的具体内容如下:
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
// max offset we should set is mFastScroll + available
final int start = layoutState.mAvailable;
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
// TODO ugly bug fix. should not happen
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
if (RecyclerView.VERBOSE_TRACING) {
TraceCompat.beginSection("LLM LayoutChunk");
}
layoutChunk(recycler, state, layoutState, layoutChunkResult);
if (RecyclerView.VERBOSE_TRACING) {
TraceCompat.endSection();
}
if (layoutChunkResult.mFinished) {
break;
}
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
/**
* Consume the available space if:
* * layoutChunk did not request to be ignored
* * OR we are laying out scrap children
* * OR we are not doing pre-layout
*/
if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null
|| !state.isPreLayout()) {
layoutState.mAvailable -= layoutChunkResult.mConsumed;
// we keep a separate remaining space because mAvailable is important for recycling
remainingSpace -= layoutChunkResult.mConsumed;
}
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
if (stopOnFocusable && layoutChunkResult.mFocusable) {
break;
}
}
if (DEBUG) {
validateChildOrder();
}
return start - layoutState.mAvailable;
}
这里面有个while循环,判断条件是这个:
(layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)
第一个条件是或的关系,当前的layoutManager的属性是无限的情况下,这个mInfinite其实就是当前linearLayout的属性,因为他是从上面的方法一层一层传过来的,参数头其实就是当前这个类,或者剩余的空间>0,这个剩余空间是怎么计算的?
int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
就是当前的可用的空间和剩余填充的空间之和,可用空间还好理解,剩余空间是干啥的呢?
/**
* Used if you want to pre-layout items that are not yet visible.
* The difference with {@link #mAvailable} is that, when recycling, distance laid out for
* {@link #mExtraFillSpace} is not considered to avoid recycling visible children.
*/
int mExtraFillSpace = 0;
这个属性是这样说的,他和mAvailable的区别就是:当回收的时候,这个空间填充的子项目是不会被回收的,因为这样可能会导致把可见的某些子项目回收掉。
那问题又来了:这个可用空间,和剩余填充的空间具体在屏幕上指的是啥?具体指的是哪块区域?我也不清楚,就当是挖坑吧,以后有机会再看。
后面&&的条件就比较好理解了,点开后发现是这样的:
/**
* @return true if there are more items in the data adapter
*/
boolean hasMore(RecyclerView.State state) {
return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount();
}
这个方法比较好理解,就是这里还有更多的数据没加载完的时候,
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler);
if (view == null) {
if (DEBUG && layoutState.mScrapList == null) {
throw new RuntimeException("received null view when unexpected");
}
// if we are laying out views in scrap, this may return null which means there is
// no more items to layout.
result.mFinished = true;
return;
}
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
} else {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
measureChildWithMargins(view, 0, 0);
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
int left, top, right, bottom;
if (mOrientation == VERTICAL) {
if (isLayoutRTL()) {
right = getWidth() - getPaddingRight();
left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
} else {
left = getPaddingLeft();
right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
}
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
bottom = layoutState.mOffset;
top = layoutState.mOffset - result.mConsumed;
} else {
top = layoutState.mOffset;
bottom = layoutState.mOffset + result.mConsumed;
}
} else {
top = getPaddingTop();
bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
right = layoutState.mOffset;
left = layoutState.mOffset - result.mConsumed;
} else {
left = layoutState.mOffset;
right = layoutState.mOffset + result.mConsumed;
}
}
// We calculate everything with View's bounding box (which includes decor and margins)
// To calculate correct layout position, we subtract margins.
layoutDecoratedWithMargins(view, left, top, right, bottom);
if (DEBUG) {
Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
+ (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
+ (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
}
// Consume the available space if the view is not removed OR changed
if (params.isItemRemoved() || params.isItemChanged()) {
result.mIgnoreConsumed = true;
}
result.mFocusable = view.hasFocusable();
}
这个方法内容也不少,大概也是计算空间的,但是他刚开始时有个next方法,这个next方法内容如下:
/**
* Gets the view for the next element that we should layout.
* Also updates current item index to the next item, based on {@link #mItemDirection}
*
* @return The next element that we should layout.
*/
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
这个里面就直接调用到getViewForPosition(mCurrentPosition)里面了,就回跳到recyclerView里面了。
@NonNull
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
这个是recyclerView里面的方法了,然后接着跳:
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
接着跳到里面的deadline方法,
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs){
……
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
// do not update unless we absolutely have to.
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
if (DEBUG && holder.isRemoved()) {
throw new IllegalStateException("Removed holder should be bound and it should"
+ " come here only in pre-layout. Holder: " + holder
+ exceptionLabel());
}
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
……
}
这里面一定会走到tryBindViewHolderByDeadline这个方法,大概的意思就是最后要彻底的绑定view了,
private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition, int position, long deadlineNs) {
holder.mBindingAdapter = null;
holder.mOwnerRecyclerView = RecyclerView.this;
final int viewType = holder.getItemViewType();
long startBindNs = getNanoTime();
if (deadlineNs != FOREVER_NS
&& !mRecyclerPool.willBindInTime(viewType, startBindNs, deadlineNs)) {
// abort - we have a deadline we can't meet
return false;
}
mAdapter.bindViewHolder(holder, offsetPosition);
long endBindNs = getNanoTime();
mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs);
attachAccessibilityDelegateOnBind(holder);
if (mState.isPreLayout()) {
holder.mPreLayoutPosition = position;
}
return true;
}
再然后后面的方法就比较常见了,先是走到mAdapter.bindViewHolder(holder, offsetPosition)这个里面,依次只看代码就大概知道了,因为都是一定会执行的,没有判断条件,
* @param holder The view holder whose contents should be updated
* @param position The position of the holder with respect to this adapter
* @see #onBindViewHolder(ViewHolder, int)
*/
public final void bindViewHolder(@NonNull VH holder, int position) {
boolean rootBind = holder.mBindingAdapter == null;
if (rootBind) {
holder.mPosition = position;
if (hasStableIds()) {
holder.mItemId = getItemId(position);
}
holder.setFlags(ViewHolder.FLAG_BOUND,
ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
TraceCompat.beginSection(TRACE_BIND_VIEW_TAG);
}
holder.mBindingAdapter = this;
onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
if (rootBind) {
holder.clearPayload();
final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
if (layoutParams instanceof RecyclerView.LayoutParams) {
((LayoutParams) layoutParams).mInsetsDirty = true;
}
TraceCompat.endSection();
}
}
后面再接着走到onBindViewHolder(holder, position, holder.getUnmodifiedPayloads()里面,再然后就是
public void onBindViewHolder(@NonNull VH holder, int position,
@NonNull List<Object> payloads) {
onBindViewHolder(holder, position);
}
再然后走到onBindViewHolder里面,
* @param holder The ViewHolder which should be updated to represent the contents of the
* item at the given position in the data set.
* @param position The position of the item within the adapter's data set.
*/
public abstract void onBindViewHolder(@NonNull VH holder, int position);
走到这里的似乎后就是日常写代码时需要重写的方法了。
2.第二条调用时机
这个是从onTouchEvent里面滑动开始调用的,
@Override
public boolean onTouchEvent(MotionEvent e) {
……
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;
}
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;
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;
}
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 (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 (canScrollVertically) {
if (dy > 0) {
dy = Math.max(0, dy - mTouchSlop);
} else {
dy = Math.min(0, dy + mTouchSlop);
}
if (dy != 0) {
startScroll = true;
}
}
if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING);
}
}
if (mScrollState == SCROLL_STATE_DRAGGING) {
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
if (dispatchNestedPreScroll(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
mReusableIntPair, mScrollOffset, TYPE_TOUCH
)) {
dx -= mReusableIntPair[0];
dy -= mReusableIntPair[1];
// Updated the nested offsets
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
// Scroll has initiated, prevent parents from intercepting
getParent().requestDisallowInterceptTouchEvent(true);
}
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
e, TYPE_TOUCH)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
if (mGapWorker != null && (dx != 0 || dy != 0)) {
mGapWorker.postFromTraversal(this, dx, dy);
}
}
}
break;
……
}
……
}
这个里面走到MotionEvent.ACTION_MOVE这个状态的时候,有个判断,if (mScrollState ==SCROLL_STATE_DRAGGING),当当前的状态是拖动的时候,里面会走到scrollByInternal()这个方法,这个方法再往里面跳,
boolean scrollByInternal(int x, int y, MotionEvent ev, int type) {
int unconsumedX = 0;
int unconsumedY = 0;
int consumedX = 0;
int consumedY = 0;
consumePendingUpdateOperations();
if (mAdapter != null) {
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;
dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset,
type, mReusableIntPair);
unconsumedX -= mReusableIntPair[0];
unconsumedY -= mReusableIntPair[1];
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];
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;
}
从这个方法里面再调用到scrollStep(x, y, mReusableIntPair)这个方法,再继续走,
void scrollStep(int dx, int dy, @Nullable int[] consumed) {
startInterceptRequestLayout();
onEnterLayoutOrScroll();
TraceCompat.beginSection(TRACE_SCROLL_TAG);
fillRemainingScrollValues(mState);
int consumedX = 0;
int consumedY = 0;
if (dx != 0) {
consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
}
if (dy != 0) {
consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
}
TraceCompat.endSection();
repositionShadowingViews();
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);
if (consumed != null) {
consumed[0] = consumedX;
consumed[1] = consumedY;
}
}
这个方法里面的调用根据dx和dy区分是上下滑动还是左右滑动,dx和dy比较常见,就是正常滑动时的那几个参数,dy不等于0的话,那当前的recyclerView应该是垂直方向的拖动状态,fx不等于0的话,当前的recyclerView应该是水平方向的拖动状态。
接下来就是调用layoutManager里面的scrollVerticallyBy这个方法,这个layoutManager很多处用到,如果是vertical的方向,还是以linearLayoutManager举例,因为实际的情况下这个manager可能是别的,
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mOrientation == HORIZONTAL) {
return 0;
}
return scrollBy(dy, recycler, state);
}
这个方法是linearLayoutManager重写父类的实现方法,再接着调用到scrollBy方法,
int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (getChildCount() == 0 || delta == 0) {
return 0;
}
ensureLayoutState();
mLayoutState.mRecycle = true;
final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
final int absDelta = Math.abs(delta);
updateLayoutState(layoutDirection, absDelta, true, state);
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);
if (consumed < 0) {
if (DEBUG) {
Log.d(TAG, "Don't have any more elements to scroll");
}
return 0;
}
final int scrolled = absDelta > consumed ? layoutDirection * consumed : delta;
mOrientationHelper.offsetChildren(-scrolled);
if (DEBUG) {
Log.d(TAG, "scroll req: " + delta + " scrolled: " + scrolled);
}
mLayoutState.mLastScrollDelta = scrolled;
return scrolled;
}
这个里面还是会调用到fill方法,final int consumed = mLayoutState.mScrollingOffset+fill(recycler, mLayoutState, state, false);
但是这个计算具体计算的是什么?字面意思是已经消费的,但是具体是消费的哪些地方?暂时还不清楚。
走到fill方法之后再往后就是跟之前的方法类似了,之前的方法里面也都差不多一样,感觉像是拖动的时候会触发这个onBindViewHolder,但是里面具体是怎么实际的计算的,还不清楚。
3.第三条调用时机
还是回到之前的说的onTouchEvent里面,拖动的状态下,
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;
}
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 (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 (canScrollVertically) {
if (dy > 0) {
dy = Math.max(0, dy - mTouchSlop);
} else {
dy = Math.min(0, dy + mTouchSlop);
}
if (dy != 0) {
startScroll = true;
}
}
if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING);
}
}
if (mScrollState == SCROLL_STATE_DRAGGING) {
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
if (dispatchNestedPreScroll(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
mReusableIntPair, mScrollOffset, TYPE_TOUCH
)) {
dx -= mReusableIntPair[0];
dy -= mReusableIntPair[1];
// Updated the nested offsets
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
// Scroll has initiated, prevent parents from intercepting
getParent().requestDisallowInterceptTouchEvent(true);
}
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
e, TYPE_TOUCH)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
if (mGapWorker != null && (dx != 0 || dy != 0)) {
mGapWorker.postFromTraversal(this, dx, dy);
}
}
}
break;
这里面有个mGapWorker,这个mGapWorker是干啥的?可以大概看下里面的东西,
final class GapWorker implements Runnable
这么着就比较容易理解了,他是个runnable业务的封装,那就基本上可以只看里面的run方法了,因为run方法里面应该是比较核心的内容,
@Override
public void run() {
try {
TraceCompat.beginSection(RecyclerView.TRACE_PREFETCH_TAG);
if (mRecyclerViews.isEmpty()) {
// abort - no work to do
return;
}
// Query most recent vsync so we can predict next one. Note that drawing time not yet
// valid in animation/input callbacks, so query it here to be safe.
//这里会遍历一个recyclerView的集合,拿到最新的一帧的毫秒时间差,
final int size = mRecyclerViews.size();
long latestFrameVsyncMs = 0;
for (int i = 0; i < size; i++) {
RecyclerView view = mRecyclerViews.get(i);
if (view.getWindowVisibility() == View.VISIBLE) {
latestFrameVsyncMs = Math.max(view.getDrawingTime(), latestFrameVsyncMs);
}
}
//如果本身就是最新的不做处理,直接返回
if (latestFrameVsyncMs == 0) {
// abort - either no views visible, or couldn't get last vsync for estimating next
return;
}
//这个是拿下一次的时间值,他会添加一个时间间隔,这个间隔是根据帧率算出来的,
long nextFrameNs = TimeUnit.MILLISECONDS.toNanos(latestFrameVsyncMs) + mFrameIntervalNs;
prefetch(nextFrameNs);
// TODO: consider rescheduling self, if there's more work to do
} finally {
mPostTimeNs = 0;
TraceCompat.endSection();
}
}
里面说的帧率间隔是相加出来的,mFrameIntervalNs他的赋值的代码在这里,
float refreshRate = 60.0f;
mGapWorker.mFrameIntervalNs = (long) (1000000000 / refreshRate);
这是截取的代码都在RecyclerView里面能找到,那这么着解释的话,就是说这个其实是一个定时检测的机制,因为到下一帧的时候他才会走到prefetch(nextFrameNs)这个方法里面,注意那个单位是纳秒级别的,所以说这个其实还是1s60帧,所以说结果应该是一帧走了多少纳秒,所以就感觉源码里面计算量级还是很强的。
prefetch(nextFrameNs)的方法内容如下:
void prefetch(long deadlineNs) {
buildTaskList();
flushTasksWithDeadline(deadlineNs);
}
再接着往里面走,
private void flushTasksWithDeadline(long deadlineNs) {
for (int i = 0; i < mTasks.size(); i++) {
final Task task = mTasks.get(i);
if (task.view == null) {
break; // done with populated tasks
}
flushTaskWithDeadline(task, deadlineNs);
task.clear();
}
}
这里面从从每个task把里面的东西取出来,大概的意思就是可以定义很多个task每个task都能和view关联起来吧?跟着这方法再往里面走,
private void flushTaskWithDeadline(Task task, long deadlineNs) {
long taskDeadlineNs = task.immediate ? RecyclerView.FOREVER_NS : deadlineNs;
RecyclerView.ViewHolder holder = prefetchPositionWithDeadline(task.view,
task.position, taskDeadlineNs);
if (holder != null
&& holder.mNestedRecyclerView != null
&& holder.isBound()
&& !holder.isInvalid()) {
prefetchInnerRecyclerViewWithDeadline(holder.mNestedRecyclerView.get(), deadlineNs);
}
}
这个大概的意思就是从holder里面反向取出来recyclerView,然后再接着调用,
private void prefetchInnerRecyclerViewWithDeadline(@Nullable RecyclerView innerView,
long deadlineNs) {
if (innerView == null) {
return;
}
if (innerView.mDataSetHasChangedAfterLayout
&& innerView.mChildHelper.getUnfilteredChildCount() != 0) {
// RecyclerView has new data, but old attached views. Clear everything, so that
// we can prefetch without partially stale data.
innerView.removeAndRecycleViews();
}
// do nested prefetch!
final LayoutPrefetchRegistryImpl innerPrefetchRegistry = innerView.mPrefetchRegistry;
innerPrefetchRegistry.collectPrefetchPositionsFromView(innerView, true);
if (innerPrefetchRegistry.mCount != 0) {
try {
TraceCompat.beginSection(RecyclerView.TRACE_NESTED_PREFETCH_TAG);
innerView.mState.prepareForNestedPrefetch(innerView.mAdapter);
for (int i = 0; i < innerPrefetchRegistry.mCount * 2; i += 2) {
// Note that we ignore immediate flag for inner items because
// we have lower confidence they're needed next frame.
final int innerPosition = innerPrefetchRegistry.mPrefetchArray[i];
prefetchPositionWithDeadline(innerView, innerPosition, deadlineNs);
}
} finally {
TraceCompat.endSection();
}
}
}
这里面会接着继续调用prefetchPositionWithDeadline(innerView, innerPosition, deadlineNs);
private RecyclerView.ViewHolder prefetchPositionWithDeadline(RecyclerView view,
int position, long deadlineNs) {
if (isPrefetchPositionAttached(view, position)) {
// don't attempt to prefetch attached views
return null;
}
RecyclerView.Recycler recycler = view.mRecycler;
RecyclerView.ViewHolder holder;
try {
view.onEnterLayoutOrScroll();
//调到这里的时候就又重新回到之前说的地方了,等于是从另外的入口调用的方法,后面东西就重复了
holder = recycler.tryGetViewHolderForPositionByDeadline(
position, false, deadlineNs);
if (holder != null) {
if (holder.isBound() && !holder.isInvalid()) {
// Only give the view a chance to go into the cache if binding succeeded
// Note that we must use public method, since item may need cleanup
recycler.recycleView(holder.itemView);
} else {
// Didn't bind, so we can't cache the view, but it will stay in the pool until
// next prefetch/traversal. If a View fails to bind, it means we didn't have
// enough time prior to the deadline (and won't for other instances of this
// type, during this GapWorker prefetch pass).
recycler.addViewHolderToRecycledViewPool(holder, false);
}
}
} finally {
view.onExitLayoutOrScroll(false);
}
return holder;
}
4.结论
所以其实recyclerView滑动的时候,
1.初始化的时候是会走到onBindViewHolder的。
2.触摸时onTouch也是会触发onBindViewHolder的,但是是有限制的,是根据计算得出来的。
3.onAttachedToWindow方法里面创建出来的GapWorker也是会走到onBindViewHolder的,这个东西是内置嵌套的