ListView缓存机制源码学习

文章参考:https://blog.csdn.net/guolin_blog/article/details/44996879

ListView缓存机制所需的数据结构

 class RecycleBin {
         
        private View[] mActiveViews = new View[0];

        private ArrayList<View>[] mScrapViews;
        private ArrayList<View> mCurrentScrap;

ListView有两层缓存: mActiveViews  mScrapViews

mActiveViews:View类型数组,保存ListView当前整个屏幕的所有itemView,mActiveViews只用于在第二次layout时使itemView快速显示到屏幕中,使用完毕后就会将数组元素置为null

mScrapViews:ArrayList<View>类型数组,用来保存ListView移出屏幕外的itemView

 ListView缓存数组的初始化

1.mActiveViews的初始化:在 RecycleBin的fillActiveViews()方法里完成mActiveViews的填充,相关源码如下

        void fillActiveViews(int childCount, int firstActivePosition) {
            if (mActiveViews.length < childCount) {
                mActiveViews = new View[childCount];
            }
            mFirstActivePosition = firstActivePosition;
            final View[] activeViews = mActiveViews;
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                //子view的布局参数中会保存该子view类型、位置等信息
                AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
                //Header和Footer类型的子view将不被添加到 mActiveViews中
                if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                    activeViews[i] = child;
                    lp.scrappedFromPosition = firstActivePosition + i;
                }
            }
        }

接下来看一下 mActiveViews是何时完成初始化的,这里要特别注意一个点就是:mActiveViews的初始化是在ListView的第二次onLayout()执行的时候进行的初始化,因为第一次layout将会调Adapter的getView方法,在getView方法里最终通过 LayoutInflater的inflate方法去创建view,创建完成后再通过ViewGroup的 addViewInLayout方法添加到父容器ListView中,所以第一次onLayout执行时 childCount=0,第一次layout完成后 childCount才不等于0,接下来看一下第二次layout 初始化 mActiveViews的关键代码

public class ListView extends AbsListView {

    @Override
    protected void layoutChildren() {
            ......
            // Pull all children into the RecycleBin.
            // These views will be reused if possible
            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时就是在这里完成 mActiveViews的初始化
                recycleBin.fillActiveViews(childCount, firstPosition);
            }
            //将ListView所有子view detatch是为了避免加载重复数据
            detachAllViewsFromParent();
            //将 mActiveViews 中的每个元素置为null
            //注意:置为null之前会调用makeAndAddView()
            recycleBin.scrapActiveViews();
            ......
    }

    private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {
        if (!mDataChanged) {
            //第二次layout时activeView将不为空,因为在这之前已经调用了layoutChildren
            //layoutChildren里已经调用了 fillActiveViews
            final View activeView = mRecycler.getActiveView(position);
            if (activeView != null) {
                setupChild(activeView, position, y, flow, childrenLeft, selected, true);
                return activeView;
            }
        }
        //这里obtainView返回的就是第一次layout时通过LayoutInflater创建的View
        //obtainView是父View AbsListView的方法
        final View child = obtainView(position, mIsScrap);
        //setUpChild里完成子view的测量和布局
        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
        return child;
    }

}

注意:第二次layout调用完 makeAndAddView 之后便会调用 RecycleBin的scrapActiveViews()方法将 mActiveViews置为null 

所以 mActiveViews的初始化是在第二次layout时在 ListView的 layoutChildren方法里,通过调用RecycleBin的fillActiveViews()方法完成的,并且 mActiveViews 仅仅用于在第二次layout时快速显示itemView

2. mScrapViews的初始化:在 RecycleBin的addScrapView()方法里完成 mScrapViews 的填充,相关源码如下

      void addScrapView(View scrap, int position) {
            final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
            if (lp == null) {
                return;
            }
            final boolean scrapHasTransientState = scrap.hasTransientState();
            if (scrapHasTransientState) {
               ......
            } else {
                clearScrapForRebind(scrap);
                if (mViewTypeCount == 1) {
                    mCurrentScrap.add(scrap);
                } else {
                    mScrapViews[viewType].add(scrap);
                }
                if (mRecyclerListener != null) {
                    mRecyclerListener.onMovedToScrapHeap(scrap);
                }
            }
        }

通过源码可以知道,当ListView的子view类型只有一种时,将会使用 mCurrentScrap 来存储废弃的子view(比如滑出屏幕的子view),当子view类型不止一种时将会以子view的viewType作为下标,mScrapViews[viewType]将会返回一个 ArrayList<View>类型的view列表,然后直接将废弃的子view添加到 mScrapViews 中,其中viewType是通过子view的LayoutParams来获取的,接下来看一下mScrapViews 是何时初始化的在AblistView的onTouchEvent()方法里,在处理ACTION_MOVE事件时会去调用trackMotionScroll()方法来跟踪手指滑动事件

    boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
        final int childCount = getChildCount();
        if (childCount == 0) {
            return true;
        }
        ......
        final boolean down = incrementalDeltaY < 0;
        ......
        if (down) {//手指上滑
            int top = -incrementalDeltaY;
            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
                top += listPadding.top;
            }
            for (int i = 0; i < childCount; i++) {
                final View child = getChildAt(i);
                if (child.getBottom() >= top) {
                    break;
                } else {
                    count++;
                    int position = firstPosition + i;
                    if (position >= headerViewsCount && position < footerViewsStart) {
                        child.clearAccessibilityFocus();
                        //子view滑出屏幕时会被回收进mScrapViews
                        mRecycler.addScrapView(child, position);
                    }
                }
            }
        } else {//手指下滑
            int bottom = getHeight() - incrementalDeltaY;
            if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
                bottom -= listPadding.bottom;
            }
            for (int i = childCount - 1; i >= 0; i--) {
                final View child = getChildAt(i);
                if (child.getTop() <= bottom) {
                    break;
                } else {
                    start = i;
                    count++;
                    int position = firstPosition + i;
                    if (position >= headerViewsCount && position < footerViewsStart) {
                        child.clearAccessibilityFocus();
                        //子view滑出屏幕时会被回收进mScrapViews
                        mRecycler.addScrapView(child, position);
                    }
                }
            }
        }
        ......
        return false;
    }

mScrapView的回收复用

最后看一下 mScrapView是怎么回收再利用的,在手指滑动过程中部分子View被滑出屏幕之外,所以ListView还需要新的子View来填充空白,这个操作又是通过调用ListView的makeAndAddView()方法来实现的

    private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {
        if (!mDataChanged) {
            //注意:因为在第二次layout时mActiveViews已被清空,所以这里获取到的activeView肯定为null
            final View activeView = mRecycler.getActiveView(position);
            if (activeView != null) {
                setupChild(activeView, position, y, flow, childrenLeft, selected, true);
                return activeView;
            }
        }
        //由于activeView为null,将会调用ontainView方法获取新的View
        final View child = obtainView(position, mIsScrap);
        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
        return child;
    }

obtainView是ListView的父类AblistView中的方法

    View obtainView(int position, boolean[] outMetadata) {
        ......
        //获取mScrapView中的废弃子view
        final View scrapView = mRecycler.getScrapView(position);
        //注意这里将scrapView作为参数传入了getView方法
        final View child = mAdapter.getView(position, scrapView, this);
        if (scrapView != null) {
            if (child != scrapView) {
                mRecycler.addScrapView(scrapView, position);
            } else if (child.isTemporarilyDetached()) {
                outMetadata[0] = true;
                child.dispatchFinishTemporaryDetach();
            }
        }
        if (mCacheColorHint != 0) {
            child.setDrawingCacheBackgroundColor(mCacheColorHint);
        }
        ......
        return child;
    }

接下来看一个重写 getView方法的例子

public class FruitAdapter extends ArrayAdapter<Fruit> {
 private int resourceId;
 public FruitAdapter(Context context, int textViewResourceId, List<Fruit> objects){
  super(context,textViewResourceId,objects);
  resourceId=textViewResourceId;
 }
 @Override
 public View getView(int position,View convertView,ViewGroup parent){
  Fruit fruit=getItem(position);
  /*
    这里的convertView就是上面的scrapView传进来的,当convertView不为null时就可以
    直接拿来用,而不需要再通过LayoutInflater加载,大大节省效率
  */
  if(convertView == null){
      convertView = LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
  }
  ImageView fruitImage=(ImageView)convertView .findViewById(R.id.fruit_image);
  TextView fruitName=(TextView) convertView .findViewById(R.id.fruit_name);
  fruitImage.setImageResource(fruit.getImageId());
  fruitName.setText(fruit.getName());
  return convertView ;
 }
}

 

Guess you like

Origin blog.csdn.net/lollo01/article/details/114192854