从 ListView 源码简单描述缓存复用流程

ListView 中缓使用了 RecycleBin(AbsListView 的内部类), 它内部有几个重要的成员变量:
mScrapViews : 多种 viewType 时,缓存 viewType 个 view
mCurrentScrap : 一种 viewType 时,缓存一个 view
mActiveViews : 缓存屏幕上显示的 view, 用于在 layout 过程中复用 view

首先分析 ListView 的绘制流程,onLayout 方法重写是在 AbsListView 中,这里会调用 ListView 的 layoutChildren, 主要代码如下:

@Override
protected void layoutChildren() {
    // ...
    try {
        // ...
        if (mAdapter == null) {
            // ...
            return;
        }
        // ...
        // 两次 layout, 第一次 layout 还没有子 View, childCount 是 0, 第二次才有正常的值,屏幕显示的 View 数量
        final int childCount = getChildCount();

        int index = 0;
        int delta = 0;

        View sel;
        View oldSel = null;
        View oldFirst = null;
        View newSel = null;

        // Remember stuff we will need down below
        switch (mLayoutMode) {
        // ...
        default: // LAYOUT_NORMAL 执行到这里
            // 记录之前选中,first 等
        }
        // 这里处理 notifyDataSetChanged 时的逻辑
        boolean dataChanged = mDataChanged;
        if (dataChanged) {
            handleDataChanged();
        }
        // ...
        // 开始缓存 View, 使用 RecycleBin
        final int firstPosition = mFirstPosition;
        final RecycleBin recycleBin = mRecycler;
        if (dataChanged) {
            // 更新内容时在这里处理
            for (int i = 0; i < childCount; i++) {
                recycleBin.addScrapView(getChildAt(i), firstPosition+i);
            }
        } else {
            // 初始第二次 layout 时执行到这里,fillActiveViews 时除了 header 和 footer 的 显示在屏幕上的 child 都存到 mActiveViews
            recycleBin.fillActiveViews(childCount, firstPosition);
        }

        // 清除旧的子 view
        detachAllViewsFromParent();
        recycleBin.removeSkippedScrap();

        switch (mLayoutMode) {
        // ...
        default: // LAYOUT_NORMAL 的逻辑在 default 中,layout 方法中缓存相关的也就是在这里
            if (childCount == 0) { // 两次 layout, 第一次时没有 child, 所以在这里处理,填充 View
                if (!mStackFromBottom) {
                    final int position = lookForSelectablePosition(0, true);
                    setSelectedPositionInt(position);
                    // fillDown -> makeAndAddView -> 
                    // (getActiveView, 没有的话 obtainView<这里调用了 getView 方法,传过去的 convertView 从 transientVStateView/scapeView 中取,convertView 是 null 则自己 inflate>
                    // , setupChild<layout child, 需要再测量时测量 child>)
                    sel = fillFromTop(childrenTop); 
                } else {
                    final int position = lookForSelectablePosition(mItemCount - 1, false);
                    setSelectedPositionInt(position);
                    sel = fillUp(mItemCount - 1, childrenBottom);
                }
            } else { // 第二次 layout 在这里处理,填充 View, 这里 view 不会再 inflate, 会使用 mActiveView 中缓存的
                if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
                    sel = fillSpecific(mSelectedPosition,
                            oldSel == null ? childrenTop : oldSel.getTop());
                } else if (mFirstPosition < mItemCount) {
                    sel = fillSpecific(mFirstPosition,
                            oldFirst == null ? childrenTop : oldFirst.getTop());
                } else {
                    sel = fillSpecific(0, childrenTop);
                }
            }
            break;
        }

        // 把屏幕上显示的 View 转移到废弃 View 的缓存中,由于上面调用 fillSpecific 方法时,会把屏幕显示 View item 已置空,所以这一步废弃 View 并缓存不了东西
        recycleBin.scrapActiveViews();

        // ...

        // 设置一些选中,焦点,以及恢复一些默认值

        invokeOnItemScrollListener();
    } finally {
        // ...
    }
}

layout 的流程,第一次屏幕上的 view 都是 inflate 得到的,第二次 layout 时,把子 view 存到 mActiveViews 中,取出来重新 layout

上面就是 layout 的过程,那么滑动中 view 是怎么复用的呢,这是在 onTouchEvent 中控制的,接下来看 onTouchEvent 的流程
onTouchEvent -> onTouchMove -> scrollIfNeeded -> trackMotionScroll -> fillGap -> fillDown -> makeAndAddView, 这里 obtainView 就是主要在 scapeView 得到复用 View 了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值