ListView caching mechanism source code learning

Article reference: https://blog.csdn.net/guolin_blog/article/details/44996879

Data structure required by ListView caching mechanism

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

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

ListView has two layers of cache: activeviews and mScrapViews

M activeviews: View type array, which saves all itemviews of the current entire screen of the ListView. M activeviews is only used to quickly display itemviews to the screen during the second layout. After use, the array elements will be set to null

mScrapViews: ArrayList < View > type array, which is used to save itemView when ListView moves out of the screen

Initialization of ListView cache array

1. Initialization of M activeviews: complete the filling of M activeviews in the fillactivviews() method of RecycleBin. The relevant source code is as follows

        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);
                //The layout parameters of the sub view will save the type, location and other information of the sub view
                AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
                //Child views of Header and Footer types will not be added to m activeviews
                if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                    activeViews[i] = child;
                    lp.scrappedFromPosition = firstActivePosition + i;
                }
            }
        }

Next, let's take a look at when the initialization of M activeviews is completed. One thing to pay special attention to here is that the initialization of M activeviews is carried out during the second execution of onLayout() of ListView, because the first layout will call the getView method of the Adapter, and in the getView method, the view will be created through the expand method of layouteinflator, After the creation is completed, it is added to the parent container ListView through the addViewInLayout method of ViewGroup. Therefore, childCount=0 when the first onLayout is executed, and childCount is not equal to 0 after the first layout is completed. Next, let's take a look at the key code of initializing m activeviews in the second layout

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 {
                //The initialization of M activeviews is completed here during the second layout
                recycleBin.fillActiveViews(childCount, firstPosition);
            }
            //All child views of ListView are detached to avoid loading duplicate data
            detachAllViewsFromParent();
            //Set each element in the activeviews to null
            //Note: makeAndAddView() will be called before null
            recycleBin.scrapActiveViews();
            ......
    }

    private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {
        if (!mDataChanged) {
            //During the second layout, the activeView will not be empty because layoutChildren has been called before
            //fillActiveViews has been called in layoutChildren
            final View activeView = mRecycler.getActiveView(position);
            if (activeView != null) {
                setupChild(activeView, position, y, flow, childrenLeft, selected, true);
                return activeView;
            }
        }
        //Here, obtainView returns the View created through layouteinflator during the first layout
        //obtainView is the method of the parent View AbsListView
        final View child = obtainView(position, mIsScrap);
        //Complete the measurement and layout of sub view s in setUpChild
        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
        return child;
    }

}

Note: after calling makeAndAddView for the second time, the layout will call the scrapeactiveviews () method of RecycleBin to set M activeviews to null

Therefore, the initialization of M activeviews is completed by calling the fillactivviews() method of RecycleBin in the layoutChildren method of ListView during the second layout, and m activeviews is only used to quickly display itemviews during the second layout

2. Initialization of mScrapViews: complete the filling of mScrapViews in the addScrapView() method of RecycleBin. The relevant source code is as follows

      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);
                }
            }
        }

It can be known from the source code that when there is only one sub view type of ListView, mcurrentscratch will be used to store the abandoned sub views (such as the sub views sliding out of the screen). When there are more than one sub view type, the viewType of the sub view will be used as the subscript, and mScrapViews[viewType] will return a view list of ArrayList < View > type, and then directly add the abandoned sub views to mScrapViews, The viewType is obtained through the LayoutParams of the child view. Next, let's see when the mScrapViews are initialized. In the onTouchEvent() method of AblistView, the action is processed_ During the move event, the trackMotionScroll() method will be called to track the finger sliding event

    boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
        final int childCount = getChildCount();
        if (childCount == 0) {
            return true;
        }
        ......
        final boolean down = incrementalDeltaY < 0;
        ......
        if (down) {//Slide your fingers up
            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();
                        //When the child view slides out of the screen, it will be recycled into mScrapViews
                        mRecycler.addScrapView(child, position);
                    }
                }
            }
        } else {//Finger slide
            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();
                        //When the child view slides out of the screen, it will be recycled into mScrapViews
                        mRecycler.addScrapView(child, position);
                    }
                }
            }
        }
        ......
        return false;
    }

Recycling and reuse of mScrapView

Finally, let's take a look at how msrapview is recycled. In the middle of the finger sliding process, the molecular View is slid out of the screen, so ListView also needs a new sub View to fill in the blank. This operation is realized by calling the makeAndAddView() method of ListView

    private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {
        if (!mDataChanged) {
            //Note: the activeviews obtained here must be null because they have been cleared during the second layout
            final View activeView = mRecycler.getActiveView(position);
            if (activeView != null) {
                setupChild(activeView, position, y, flow, childrenLeft, selected, true);
                return activeView;
            }
        }
        //Since activeView is null, the ontainView method will be called to get a new View
        final View child = obtainView(position, mIsScrap);
        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
        return child;
    }

obtainView is a method in the parent class AblistView of ListView

    View obtainView(int position, boolean[] outMetadata) {
        ......
        //Gets the discarded child view in the mScrapView
        final View scrapView = mRecycler.getScrapView(position);
        //Note that the scrapView is passed into the getView method as a parameter
        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;
    }

Next, let's look at an example of overriding the getView method

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);
  /*
    The convertView here is passed in from the above scrapView. It can be used when the convertView is not null
    It can be used directly without loading through layoutinflator, which greatly saves efficiency
  */
  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 ;
 }
}

 

Posted by houssam_ballout on Thu, 14 Apr 2022 21:41:23 +0930