RecycleView Layout 详解

前提

我们以RecycleView 的宽高都是Match_Parent作为前提去分析,这样onMeasure方法就不会调用dispatchStep1、dispatchStep2.我们只看onLayout 方法

ViewHolder 一些属性

    public abstract static class ViewHolder {
        @NonNull
        public final View itemView;
        WeakReference<RecyclerView> mNestedRecyclerView;
        int mPosition = NO_POSITION;
        int mOldPosition = NO_POSITION;
        long mItemId = NO_ID;
        int mItemViewType = INVALID_TYPE;
        //之前
        int mPreLayoutPosition = NO_POSITION;

mPreLayoutPosition 用于dispatchLayoutStep1,表示当前ViewHolder的位置,mPosition 用于dispatchLayoutStep2 时表示ViewHolder的位置。

getLayoutPosition

	
        public final int getLayoutPosition() {
            return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition;
        }

getLayoutPosition 如果当前是PreLayout阶段(dispatchLayoutStep1方法),那么该方法返回的就是mPreLayoutPosition,如果不是PreLayout阶段(dispatchLayoutStep2方法),就会返回mPosition.

首次进去RecycleView 页面

androidx.recyclerview.widget.RecyclerView#onLayout

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
           dispatchLayout();
        mFirstLayoutComplete = true;
    }

里面调用了dispatchLayout 方法

androidx.recyclerview.widget.RecyclerView#dispatchLayout

    void dispatchLayout() {

        if (mLayout == null) {
            Log.e(TAG, "No layout manager attached; skipping layout");
            // leave the state in START
            return;
        }
        mState.mIsMeasuring = false;
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else if (mAdapterHelper.hasUpdates() || 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.
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else {
            // always make sure we sync them (to ensure mode is exact)
            mLayout.setExactMeasureSpecsFrom(this);
        }
        dispatchLayoutStep3();
    }

里面layout 会分成三步。

dispatchLayoutStep1

    private void dispatchLayoutStep1() {
        mState.assertLayoutStep(State.STEP_START);
       mState.mIsMeasuring = false;
       //用于控制在layout期间 requestLayout不会生效
        startInterceptRequestLayout();
		//首次进去页面不会有任何处理  当我们调用notifyItemRemove 会修改oldposition
        processAdapterUpdatesAndSetAnimationFlags();
        saveFocusInfo();
        mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
        mItemsAddedOrRemoved = mItemsChanged = false;
        mState.mInPreLayout = mState.mRunPredictiveAnimations;
        mState.mItemCount = mAdapter.getItemCount();
       
        if (mState.mRunPredictiveAnimations) {
            clearOldPositions();
        } else {
        //清楚OldPosition PreLayoutPosition
            clearOldPositions();
        }
        onExitLayoutOrScroll();
        stopInterceptRequestLayout(false);
        mState.mLayoutStep = State.STEP_LAYOUT;
    }

第一步主要进行ViewHolder的一些数据更新操作,比如ViewHolder 某个元素被移除,那么后面的ViewHolder的Position 都会被减一,同时如果RecycleView 有动画,那么会进行PreLayout,拿到动画位置的信息。

dispatchLayoutStep2

    private void dispatchLayoutStep2() {
        // Step 2: Run layout
        mState.mInPreLayout = false;
        mLayout.onLayoutChildren(mRecycler, mState);
        mState.mStructureChanged = false;
        mPendingSavedState = null;
    }

真正的进行布局,如果在dispatchLayoutStep1进行过PreLayout,那么本次的布局所有的ViewHolder都是存在的,因为之前PreLayout 都已经进行了创建。
dispatchLayoutStep3

    private void dispatchLayoutStep3() {
        mState.assertLayoutStep(State.STEP_ANIMATIONS);
       onEnterLayoutOrScroll();
        mState.mLayoutStep = State.STEP_START;
        if (mState.mRunSimpleAnimations) {
       		//执行动画
        }

        mLayout.removeAndRecycleScrapInt(mRecycler);
        mState.mPreviousLayoutItemCount = mState.mItemCount;
          }

主要进行动画的处理,回收Scrap的ViewHolder到RecycleViewPool中,比如被移除掉的ViewHolder.

调用adapter.notifyItemRemoved()方法 onLayout执行分析

注意,本篇我们以有动画的RecyclerView 进行分析,如果RecyclerView 没有动画,那么不会执行PreLayout,也就是在dispatchLayoutStep1里面并不会进行布局的填充。

第一步,更新Position

	  at androidx.recyclerview.widget.RecyclerView$ViewHolder.offsetPosition(RecyclerView.java:11106)
	  at androidx.recyclerview.widget.RecyclerView$ViewHolder.flagRemovedAndOffsetPosition(RecyclerView.java:11095)
	  at androidx.recyclerview.widget.RecyclerView.offsetPositionRecordsForRemove(RecyclerView.java:4635)
	  at androidx.recyclerview.widget.RecyclerView$6.offsetPositionsForRemovingLaidOutOrNewView(RecyclerView.java:996)
	  at androidx.recyclerview.widget.AdapterHelper.postponeAndUpdateViewHolders(AdapterHelper.java:448)
	  at androidx.recyclerview.widget.AdapterHelper.applyRemove(AdapterHelper.java:183)
	  at androidx.recyclerview.widget.AdapterHelper.preProcess(AdapterHelper.java:102)
	  at androidx.recyclerview.widget.RecyclerView.processAdapterUpdatesAndSetAnimationFlags(RecyclerView.java:3793)
	  at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep1(RecyclerView.java:4039)
	  at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3849)
	  at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4404)

在dispatchLayoutStep1 方法里面,会进行更新oldposition、preLayoutPostion,preLayoutPosition 有了之后,就可以执行dispatchLayoutStep1 里面的布局.

offsetPosition() 更新oldposition、preLayoutPostion

androidx.recyclerview.widget.RecyclerView.ViewHolder#offsetPosition



        void offsetPosition(int offset, boolean applyToPreLayout) {
            if (mOldPosition == NO_POSITION) {
                mOldPosition = mPosition;
            }
            if (mPreLayoutPosition == NO_POSITION) {
                mPreLayoutPosition = mPosition;
            }
            if (applyToPreLayout) {
                mPreLayoutPosition += offset;
            }
            mPosition += offset;
            if (itemView.getLayoutParams() != null) {
                ((LayoutParams) itemView.getLayoutParams()).mInsetsDirty = true;
            }
        }

比如当一个item 移除掉,如果RecycleView 里面还有多余的元素,prelayout会加载没有下一个ViewHolder.

在布局的时候,移除的view 不会被计算占用的空间

androidx.recyclerview.widget.LinearLayoutManager#layoutChunk

        // Consume the available space if the view is not removed OR changed
        if (params.isItemRemoved() || params.isItemChanged()) {
            result.mIgnoreConsumed = true;
        }
			androidx.recyclerview.widget.LinearLayoutManager#fill
			
            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;
            }

当dispatchLayoutStep1 执行完毕,清除preLayoutPosition,进行dispatchLayoutStep2

androidx.recyclerview.widget.RecyclerView#dispatchLayoutStep1
androidx.recyclerview.widget.RecyclerView.ViewHolder#clearOldPosition

            // we don't process disappearing list because they may re-appear in post layout pass.
            clearOldPositions();

dispatchLayoutStep3 回收被移除的ViewHolder

	androidx.recyclerview.widget.RecyclerView.Recycler#recycleViewHolderInternal
	  at androidx.recyclerview.widget.RecyclerView$Recycler.recycleViewHolderInternal(RecyclerView.java:6429)
	  at androidx.recyclerview.widget.RecyclerView$Recycler.quickRecycleScrapView(RecyclerView.java:6554)
	  at androidx.recyclerview.widget.RecyclerView$LayoutManager.removeAndRecycleScrapInt(RecyclerView.java:9249)
	  at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep3(RecyclerView.java:4207)

总结:

首页进去RecycleView,只会在dispatchLayoutStep2 方法里面,对RecycleView 的布局进行填充。
如果我们调用了notifyItemRemoved 方法,在dispatchLayoutStep1 方法里面,会先更新OldPosition、PreLayoutPosition. 然后执行layout,layout之前会先调用mLayout.onLayoutChildren(mRecycler, mState); 在这个方法里面,会根据PreLayoutPosition 进行布局,因为有item 被移除,那么肯定会多出来空间,这时候,就会调用androidx.recyclerview.widget.LinearLayoutManager#fill 方法就会填充View,就会创建新的ViewHolder,在dispatchLayoutStep1 的最后,会把PreLayoutPosition 置为-1,这样就不影响dispatchLayoutStep2的布局。dispatchLayoutStep2 布局的时候,就会使用diapatchLayoutStep1创建的ViewHolder. 在dispatchStep3中,进行动画的处理,动画结束后,会把移除的ViewHolder 放到RecycleViewPool中重复利用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值