深入解析RecyclerView 源码——绘制流程、缓存机制、动画等

前言

本文打算对 RecyclerView 做一个详细完整的、重点突出的分析与总结,因为 RecycelrView 源码很长(光 RecyclerView 文件本身就有 13000+ 行),因此文章也会很长,但一通分析下来后会发现,RecyclerView 虽然是 ListView 的加强版,除了在使用方法上类似之外,关键源码上也是非常类似的。

RecyclerView 的使用可以参考大神的文章:

Android RecyclerView 使用完全解析 体验艺术般的控件

RecyclerView 和 ListView 使用对比分析

本文采用“自顶向下”的源码分析法——即把相关的代码按照调用关系自顶向下排列,同一个类的方法放在一起,关键的代码使用注释标注。废话不多说,下面从 RecyclerView 的绘制流程开始分析。

绘制流程

先从 onMeasure 方法看起,关键的地方都用注释标出来了:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {

    Adapter mAdapter;
    @VisibleForTesting LayoutManager mLayout;

    @Override
    protected void onMeasure(int widthSpec, int heightSpec) {
        if (mLayout == null) {
            defaultOnMeasure(widthSpec, heightSpec);
            return;
        }
        if (mLayout.isAutoMeasureEnabled()) { // 看这里就行了
            final int widthMode = MeasureSpec.getMode(widthSpec);
            final int heightMode = MeasureSpec.getMode(heightSpec);

            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

			// 关键步骤 1
            // 默认为 start
            if (mState.mLayoutStep == State.STEP_START) {
                dispatchLayoutStep1();
            }

            // 关键步骤 2
            dispatchLayoutStep2();

            // now we can get the width and height from the children.
            mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);

            // if RecyclerView has non-exact width and height and if there is at least one child
            // which also has non-exact width & height, we have to re-measure.
            if (mLayout.shouldMeasureTwice()) {
                ...
                dispatchLayoutStep2();
                // now we can get the width and height from the children.
                mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
            }
        } else {
            if (mHasFixedSize) {
                mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
                return;
            }
            // custom onMeasure
            if (mAdapterUpdateDuringMeasure) {
                ...
            } else if (mState.mRunPredictiveAnimations) {
                setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight());
                return;
            }

            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            ...
        }
    }

    void defaultOnMeasure(int widthSpec, int heightSpec) {
        // calling LayoutManager here is not pretty but that API is already public and it is better
        // than creating another method since this is internal.
        final int width = LayoutManager.chooseSize(widthSpec,
                getPaddingLeft() + getPaddingRight(),
                ViewCompat.getMinimumWidth(this));
        final int height = LayoutManager.chooseSize(heightSpec,
                getPaddingTop() + getPaddingBottom(),
                ViewCompat.getMinimumHeight(this));

        setMeasuredDimension(width, height);
    }

    public abstract static class LayoutManager {

        public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
            mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
        }

    }

}

mLayout.isAutoMeasureEnabled() 的返回值默认为 false,但 LinearLayoutManager 和 StaggeredGridLayoutManager 重写了这个方法,且默认为 true:

public class LinearLayoutManager extends RecyclerView.LayoutManager implements
        ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {

    @Override
    public boolean isAutoMeasureEnabled() {
        return true;
    }

}
public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager implements
        RecyclerView.SmoothScroller.ScrollVectorProvider {

    public static final int GAP_HANDLING_NONE = 0;
    public static final int GAP_HANDLING_LAZY = 1;
    public static final int GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS = 2;

    private int mGapStrategy = GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS;

    public void setGapStrategy(int gapStrategy) {
        ...
        mGapStrategy = gapStrategy;
        requestLayout();
    }

    @Override
    public boolean isAutoMeasureEnabled() {
        return mGapStrategy != GAP_HANDLING_NONE;
    }

}

而 GridLayoutManager 继承自 LinearLayoutManager,即 Android 提供的 LayoutManager 的三个子类,都会走 if 分支里的代码,里面最重要的两个调用是 dispatchLayoutStep1 和 dispatchLayoutStep2,在开始分析这两个方法之前,先看一下 onLayout 方法:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {

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

    /**
     * Wrapper around layoutChildren() that handles animating changes caused by layout.
     */
    void dispatchLayout() {
        mState.mIsMeasuring = false;
        if (mState.mLayoutStep == State.STEP_START) { 
            // onMeasure 已经执行了 step1 和 step2,正常情况下不会走这个分支
            dispatchLayoutStep1();
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
                || mLayout.getHeight() != getHeight()) {
            // adpter 更新了,或者 width 或 height 改变了,重新走一遍 step2
            // First 2 steps are done in onMeasure but looks like we have to run again due to
            // changed size.
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else {
            // 设置 MeasureSpec 为 EXACTLY
            // always make sure we sync them (to ensure mode is exact)
            mLayout.setExactMeasureSpecsFrom(this);
        }
        // 关键步骤 3
        dispatchLayoutStep3();
    }

    public abstract static class LayoutManager {

        void setExactMeasureSpecsFrom(RecyclerView recyclerView) {
            setMeasureSpecs(
                    MeasureSpec.makeMeasureSpec(recyclerView.getWidth(), MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(recyclerView.getHeight(), MeasureSpec.EXACTLY)
            );
        }
    }

}

可以发现,RecyclerView 的 layout 流程分为三步,关键调用分别为 dispatchLayoutStep1、dispatchLayoutStep2、dispatchLayoutStep3,其中 onMeasure 方法执行前两部,onLayout 方法执行最后一步,下面开始分析这三个方法。

dispatchLayoutStep

dispatchLayoutStep1

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {

    /**
     * The first step of a layout where we;
     * - process adapter updates
     * - decide which animation should run
     * - save information about current views
     * - If necessary, run predictive layout and save its information
     */
    private void dispatchLayoutStep1() {
        ...
        processAdapterUpdatesAndSetAnimationFlags();

        if (mState.mRunSimpleAnimations) {
            // Step 0: 找出所有未删除的 item 的位置, 准备执行预布局
            int count = mChildHelper.getChildCount();
            for (int i = 0; i < count; ++i) {
                final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
                final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(...);
                mViewInfoStore.addToPreLayout(holder, animationInfo);
                ...
            }
        }

        if (mState.mRunPredictiveAnimations) {
            // Step 1: 运行预布局

            // Save old positions so that LayoutManager can run its mapping logic.
            saveOldPositions();
            final boolean didStructureChange = mState.mStructureChanged;
            mState.mStructureChanged = false;
            // 借助 LayoutManager 完成 layout 流程
            // temporarily disable flag because we are asking for previous layout
            mLayout.onLayoutChildren(mRecycler, mState);
            mState.mStructureChanged = didStructureChange;

            for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
                final View child = mChildHelper.getChildAt(i);
                final ViewHolder viewHolder = getChildViewHolderInt(child);
                if (!mViewInfoStore.isInPreLayout(viewHolder)) {
                    ...
                    final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(...);
                    if (wasHidden) {
                        recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
                    } else {
                        mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
                    }
                }
            }
            // we don't process disappearing list because they may re-appear in post layout pass.
            clearOldPositions();
        } else {
            clearOldPositions();
        }

        mState.mLayoutStep = State.STEP_LAYOUT;
    }

    private void processAdapterUpdatesAndSetAnimationFlags() {
        // 默认为 false,第一次 measure 时不会执行
        if (mDataSetHasChangedAfterLayout) {
            mAdapterHelper.reset();
            if (mDispatchItemsChangedEvent) {
                mLayout.onItemsChanged(this);
            }
        }

        // 两个分支里的代码最后调用的都是 AdapterHelper 内部的接口 Callback 的方法
        // Callback 的实现可以查看 RecyclerView 的 initAdapterManager 方法
        // 最后可能会调用 requestLayout 走一遍绘制流程以实现动画的效果
        if (predictiveItemAnimationsEnabled()) {
            mAdapterHelper.preProcess();
        } else {
            mAdapterHelper.consumeUpdatesInOnePass();
        }

        mState.mRunSimpleAnimations = mFirstLayoutComplete && ...;
        mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
                && animationTypeSupported
                && !mDataSetHasChangedAfterLayout
                && predictiveItemAnimationsEnabled();
    }

    static ViewHolder getChildViewHolderInt(View child) {
        if (child == null) {
            return null;
        }
        return ((LayoutParams) child.getLayoutParams()).mViewHolder;
    }

}

根据 dispatchLayoutStep1 的注释及代码,可以得知这个方法主要负责:

  1. 执行 adapter 更新,最终可能会调用 requestLayout
  2. 决定哪些动画应该被执行(暂时不会被执行)
  3. 保存子 View 的相关信息
  4. 如果有必要,执行预布局

而如果是第一次执行 measure 流程,adapter 正常来说是没有可以更新的元素的,即此时 dispatchLayoutStep1 的作用主要是计算并保存子 View 和动画的相关信息。

dispatchLayoutStep2

预布局是否执行涉及的变量比较多,先忽略。现在继续看 dispatchLayoutStep2:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {

    /**
     * The second layout step where we do the actual layout of the views for the final state.
     * This step might be run multiple times if necessary (e.g. measure).
     */
    private void dispatchLayoutStep2() {
        ...
        // 借助 LayoutManager 完成实际上的布局
        // Step 2: Run layout
        mState.mInPreLayout = false;
        mLayout.onLayoutChildren(mRecycler, mState);
        mState.mStructureChanged = false;
        ...
    }

}

可以看到,相比 dispatchLayoutStep1,dispatchLayoutStep2 简单得多,作用是执行真正的布局,调用的方法是 mLayout.onLayoutChildren,这个方法在 LayoutManager 中是一个空实现,它的三个子类 LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager 都有自己的实现。下面以 LinearLayoutManager 为例(关键部分,比如缓存机制,三个 LayoutManager 都是一样的):

public class LinearLayoutManager extends RecyclerView.LayoutManager implements
        ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        // layout algorithm:
        // 1) by checking children and other variables, find an anchor coordinate and an anchor item position.
        // 2) fill towards start, stacking from bottom
        // 3) fill towards end, stacking from top
        // 4) scroll to fulfill requirements like stack from bottom.
        // create layout state

        if (mPendingSavedState != null || mPendingScrollPosition != NO_POSITION) {
            if (state.getItemCount() == 0) {
                removeAndRecycleAllViews(recycler);
                return;
            }
        }
        ...

		// 寻找子 View 的锚点坐标及位置
        final View focused = getFocusedChild();
        if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION || mPendingSavedState != null) {
            updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
        } else if (...) {
            mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
        }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity3D格斗游戏源码是一种让开发者能够开发自己的格斗游戏的一种资源,而“仿最终幻想”是模仿最终幻想系列游戏来设计和开发的游戏。这种源码提供了许多基本的游戏元素和功能,开发者可以根据自己的需求来创建自己想要的游戏。 在Unity3D格斗游戏源码中,主要包含了以下几个方面的内容: 1. 角色控制:开发者可以通过源码来实现角色的移动、攻击、防御等基本动作。游戏中的角色可以使用键盘、鼠标或者手柄进行操控,使得玩家能够与游戏世界进行交互。 2. 动画系统:为了增强游戏的流畅性和真实感,该源码还提供了动画系统。开发者可以根据需要创建角色的各种动画,例如攻击动画、受伤动画和死亡动画等,使得游戏体验更加逼真。 3. AI系统:为了让游戏增加一定的挑战性,该源码还提供了AI系统。开发者可以通过代码设置敌方角色的行为和策略,使得游戏中的敌人具有一定的智能和反应能力。 4. 特效和音效:为了提升游戏的视听效果,该源码还包括了一些特效和音效资源。开发者可以根据自己的需要添加各种特效和音效,增强游戏的氛围和乐趣。 5. 可定制性:该源码还提供了一些可配置的参数和选项,开发者可以根据自己的需求来调整游戏的各种设置,包括角色属性、技能系统和游戏难度等,以便创造出不同的游戏体验。 总之,Unity3D格斗游戏源码可以帮助开发者快速搭建一个仿照最终幻想系列的格斗游戏。通过使用该源码,开发者可以省下许多开发时间和精力,同时也能够在这个基础上进行二次开发,实现自己的创意和想法。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值