zoukankan      html  css  js  c++  java
  • Listview部分源码分析

    Listview在android开发中算是最常用的几个控件之一,由于要应付各种不同的需求,甚至有时候是奇怪且独特的需要,使用Listview就总会遇到些奇怪的问题,但是其实都没有什么捷径,看源码是最好的办法。而与Listview相关的源码至少有一万行,涉及到AbsListview、AdapterView、ListAdapter等,这里对部分的源码做分析。下面由于代码数量比较多,只要着重看有中文注释的部分就可以了。

     1.与数据adapter相关的

    setAdapter()

    setAdapter source code
     1 /**
     2      * Sets the data behind this ListView.
     3      *
     4      * The adapter passed to this method may be wrapped by a {@link WrapperListAdapter},
     5      * depending on the ListView features currently in use. For instance, adding
     6      * headers and/or footers will cause the adapter to be wrapped.
     7      *
     8      * @param adapter The ListAdapter which is responsible for maintaining the
     9      *        data backing this list and for producing a view to represent an
    10      *        item in that data set.
    11      *
    12      * @see #getAdapter() 
    13      */
    14     @Override
    15     public void setAdapter(ListAdapter adapter) {
    16         if (null != mAdapter) {
    17             mAdapter.unregisterDataSetObserver(mDataSetObserver);    //移除了与当前listview的adapter绑定数据集观察者DataSetObserver
    18         }
    19 
    20         resetList();   //重置listview,主要是清除所有的view,改变header、footer的状态
    21         mRecycler.clear();  //清除掉RecycleBin对象mRecycler中所有缓存的view,RecycleBin后面着重介绍,主要是关系到Listview中item的重用机制,它是AbsListview的一个内部类
    22 
    23         if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {   //判断是否有header和footer
    24             mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);//假如有,用HeaderViewListAdapter包装一下adapter
    25         } else {
    26             mAdapter = adapter;
    27         }
    28 
    29         mOldSelectedPosition = INVALID_POSITION;
    30         mOldSelectedRowId = INVALID_ROW_ID;
    31         if (mAdapter != null) {
    32             mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
    33             mOldItemCount = mItemCount;
    34             mItemCount = mAdapter.getCount();
    35             checkFocus();
    36 
    37             mDataSetObserver = new AdapterDataSetObserver();
    38             //mAdapter注册一个数据集观察者DataSetObserver,DataSetObserver主要是负责侦听adapter中数据的变化,从而实现更新UI
    39             mAdapter.registerDataSetObserver(mDataSetObserver);
    40 
    41             /**mAdapter.getViewTypeCount()的作用其实是有判断item有多少种类型,
    42              **一般情况下,item都是一样的只有一种,某些特殊需求中可能就需要多种,然后RecycleBin对象mRecycler记录下item类型的数量*/
    43             mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
    44 
    45             int position;
    46             if (mStackFromBottom) { //根据mStackFromBottom变量,设置position究竟是顶部第一个还是底部最后一个是selected
    47                 position = lookForSelectablePosition(mItemCount - 1, false);
    48             } else {
    49                 position = lookForSelectablePosition(0, true);
    50             }
    51             setSelectedPositionInt(position);//AdapterView中的方法,记录当前的position
    52             setNextSelectedPositionInt(position);//AdapterView中的方法,记录下一个position
    53 
    54             if (mItemCount == 0) {
    55                 // Nothing selected
    56                 checkSelectionChanged();
    57             }
    58 
    59         } else {
    60             mAreAllItemsSelectable = true;
    61             checkFocus();
    62             // Nothing selected
    63             checkSelectionChanged();
    64         }
    65 
    66         if (mCheckStates != null) {
    67             mCheckStates.clear();//mCheckStates是一个SparseBooleanArray对象,用于保存item的checked状态,performItemClick()、setItemChecked()时,改变内部的值
    68         }
    69         
    70         //就英文吧,Called when something has changed which has invalidated the layout of a child of this view parent. This will schedule a layout pass of the view tree.
    71         requestLayout();
    72 
    73 
    74     }

     ListAdapter是通过notifyDataSetChanged实现UI更新的,具体是怎样的呢?

    先看registerDataSetObserver,因为registerDataSetObserver是接口Adapter定义的,所以需要实现该接口的类来实现,那么这里以BaseAdapter为例:

    registerDataSetObserver()

    registerDataSetObserver
    1     public void registerDataSetObserver(DataSetObserver observer) {
    2         mDataSetObservable.registerObserver(observer);
    3     }

    它作用就是调用DataSetObservable的registerDataSetObserver,而该方法是在Observable定义的:

    registerObserver
     1         /**
     2      * The list of observers.  An observer can be in the list at most
     3      * once and will never be null.
     4      */
     5     protected final ArrayList<T> mObservers = new ArrayList<T>();
     6 
     7     /**
     8      * Adds an observer to the list. The observer cannot be null and it must not already
     9      * be registered.
    10      * @param observer the observer to register
    11      * @throws IllegalArgumentException the observer is null
    12      * @throws IllegalStateException the observer is already registered
    13      */
    14     public void registerObserver(T observer) {
    15         if (observer == null) {
    16             throw new IllegalArgumentException("The observer is null.");
    17         }
    18         synchronized(mObservers) {
    19             if (mObservers.contains(observer)) {
    20                 throw new IllegalStateException("Observer " + observer + " is already registered.");
    21             }
    22             mObservers.add(observer);    //把observer添加到mObservers数组中
    23         }
    24     }

    可见registerDataSetObserver的作用其实就是简单的把传进去的mDataSetObserver存储起来。

    当调用BaseAdapter的notifyDataSetChanged时,干了什么?

    notifyDataSetChanged
    1     /**
    2      * Notifies the attached View that the underlying data has been changed
    3      * and it should refresh itself.
    4      */
    5     public void notifyDataSetChanged() {
    6         mDataSetObservable.notifyChanged();
    7     }

    看mDataSetObservable.notifyChanged()方法:

    notifyChanged
     1         /**
     2      * Invokes onChanged on each observer. Called when the data set being observed has
     3      * changed, and which when read contains the new state of the data.
     4      */
     5     public void notifyChanged() {
     6         synchronized(mObservers) {
     7             for (DataSetObserver observer : mObservers) {
     8                 observer.onChanged();    //遍历所有的DataSetObserver调用它们的onChanged方法
     9             }
    10         }
    11     }

    DataSetObserver是一个接口,所以onChanged方法干什么要看具体实现该接口的类,在Listview中,关联的observer是AdapterDataSetObserver类型的,它实现了DataSetObserver,看它的onChanged方法:

    onChanged
     1             @Override
     2         public void onChanged() {
     3             mDataChanged = true;
     4             mOldItemCount = mItemCount;
     5             mItemCount = getAdapter().getCount();
     6 
     7             // Detect the case where a cursor that was previously invalidated has
     8             // been repopulated with new data.
     9             if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
    10                     && mOldItemCount == 0 && mItemCount > 0) {
    11                 AdapterView.this.onRestoreInstanceState(mInstanceState);
    12                 mInstanceState = null;
    13             } else {
    14                 rememberSyncState();
    15             }
    16             checkFocus();
    17             requestLayout();    //onChanged的主要目的就是重布局
    18         }

    总结一下,上面看出,notifyDataSetChanged方法的目的就是要求Listview重布局,从而实现UI的更新。

    既然说到布局,那么接下来就介绍Listview的布局相关的东西。

    2.Listview的布局

    说到布局,毫无疑问要从onLayout这个方法开始看:

    onLayout()

    onLayout
     1         /**
     2      * Subclasses should NOT override this method but子类不要重写这个方法,而要重写layoutChildren
     3      *  {@link #layoutChildren()} instead.
     4      */
     5     @Override
     6     protected void onLayout(boolean changed, int l, int t, int r, int b) {
     7         super.onLayout(changed, l, t, r, b);
     8         mInLayout = true;
     9         if (changed) {
    10             int childCount = getChildCount();
    11             for (int i = 0; i < childCount; i++) {
    12                 getChildAt(i).forceLayout();    //保证下一次布局事,子view要重新layout
    13             }
    14             mRecycler.markChildrenDirty();    //该方法作用时,保证mRecycler中scrapview在下一次布局时要重新布局(mRecycler后面介绍,可以先不管)
    15         }
    16 
    17         layoutChildren();    //这是关键的方法
    18         mInLayout = false;
    19 
    20         mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
    21     }

    onLayout的关键在于layoutChildren(),所以接下来关键要看layoutChildren:

    layoutChildren()

    layoutChildren
      1         @Override
      2     protected void layoutChildren() {
      3         final boolean blockLayoutRequests = mBlockLayoutRequests;
      4         if (!blockLayoutRequests) {
      5             mBlockLayoutRequests = true;
      6         } else {
      7             return;
      8         }
      9 
     10         try {
     11             super.layoutChildren();
     12 
     13             invalidate();    //重绘自己,onDraw()会被调用
     14 
     15             if (mAdapter == null) {    //adapter空的话,直接返回,置空listview
     16                 resetList();    
     17                 invokeOnItemScrollListener();
     18                 return;
     19             }
     20 
     21             int childrenTop = mListPadding.top;
     22             int childrenBottom = mBottom - mTop - mListPadding.bottom;
     23 
     24             int childCount = getChildCount();
     25             int index = 0;
     26             int delta = 0;
     27 
     28             View sel;
     29             View oldSel = null;
     30             View oldFirst = null;
     31             View newSel = null;
     32 
     33             View focusLayoutRestoreView = null;
     34 
     35             // Remember stuff we will need down below
     36             switch (mLayoutMode) {    //判断布局模式,根据不同模式,使用不同的布局方法,初始值为LAYOUT_NORMAL
     37             case LAYOUT_SET_SELECTION:
     38                 index = mNextSelectedPosition - mFirstPosition;
     39                 if (index >= 0 && index < childCount) {
     40                     newSel = getChildAt(index);
     41                 }
     42                 break;
     43             case LAYOUT_FORCE_TOP:
     44             case LAYOUT_FORCE_BOTTOM:
     45             case LAYOUT_SPECIFIC:
     46             case LAYOUT_SYNC:
     47                 break;
     48             case LAYOUT_MOVE_SELECTION:
     49             default:
     50                 // Remember the previously selected view
     51                 index = mSelectedPosition - mFirstPosition;
     52                 if (index >= 0 && index < childCount) {    //保存被select的view
     53                     oldSel = getChildAt(index);
     54                 }
     55 
     56                 // Remember the previous first child
     57                 oldFirst = getChildAt(0);    //保存第一个子view
     58 
     59                 if (mNextSelectedPosition >= 0) {
     60                     delta = mNextSelectedPosition - mSelectedPosition;
     61                 }
     62 
     63                 // Caution: newSel might be null
     64                 newSel = getChildAt(index + delta);
     65             }
     66 
     67 
     68             boolean dataChanged = mDataChanged;
     69             if (dataChanged) {
     70                 handleDataChanged();
     71             }
     72 
     73             // Handle the empty set by removing all views that are visible
     74             // and calling it a day
     75             if (mItemCount == 0) {    //如果没有item直接返回
     76                 resetList();
     77                 invokeOnItemScrollListener();
     78                 return;
     79             } else if (mItemCount != mAdapter.getCount()) {
     80                 throw new IllegalStateException("The content of the adapter has changed but "
     81                         + "ListView did not receive a notification. Make sure the content of "
     82                         + "your adapter is not modified from a background thread, but only "
     83                         + "from the UI thread. [in ListView(" + getId() + ", " + getClass() 
     84                         + ") with Adapter(" + mAdapter.getClass() + ")]");
     85             }
     86 
     87             setSelectedPositionInt(mNextSelectedPosition);
     88 
     89             // Pull all children into the RecycleBin.
     90             // These views will be reused if possible
     91             final int firstPosition = mFirstPosition;
     92             final RecycleBin recycleBin = mRecycler;
     93 
     94             // reset the focus restoration
     95             View focusLayoutRestoreDirectChild = null;
     96 
     97 
     98             // Don't put header or footer views into the Recycler. Those are
     99             // already cached in mHeaderViews;
    100             if (dataChanged) {    //如果数据改变了,把所有的子view放进recycleBin的Scrapview中(不包括foot和head)
    101                 for (int i = 0; i < childCount; i++) {
    102                     recycleBin.addScrapView(getChildAt(i));
    103                     if (ViewDebug.TRACE_RECYCLER) {
    104                         ViewDebug.trace(getChildAt(i),
    105                                 ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, index, i);
    106                     }
    107                 }
    108             } else {    //如果没有改变,把所有子view,放进recycleBin的AcitveView(不包括foot和head)
    109                 recycleBin.fillActiveViews(childCount, firstPosition);
    110             }
    111 
    112             // take focus back to us temporarily to avoid the eventual
    113             // call to clear focus when removing the focused child below
    114             // from messing things up when ViewRoot assigns focus back
    115             // to someone else
    116             final View focusedChild = getFocusedChild();
    117             if (focusedChild != null) {
    118                 // TODO: in some cases focusedChild.getParent() == null
    119 
    120                 // we can remember the focused view to restore after relayout if the
    121                 // data hasn't changed, or if the focused position is a header or footer
    122                 if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)) {
    123                     focusLayoutRestoreDirectChild = focusedChild;
    124                     // remember the specific view that had focus
    125                     focusLayoutRestoreView = findFocus();
    126                     if (focusLayoutRestoreView != null) {
    127                         // tell it we are going to mess with it
    128                         focusLayoutRestoreView.onStartTemporaryDetach();
    129                     }
    130                 }
    131                 requestFocus();
    132             }
    133 
    134             // Clear out old views
    135             detachAllViewsFromParent();//清楚掉Listview中所有旧的view,等待attach新的view
    136 
    137             //根据mLayoutMode不同,使用不同的布局item view的方式
    138             switch (mLayoutMode) {
    139             case LAYOUT_SET_SELECTION:
    140                 if (newSel != null) {
    141                     sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
    142                 } else {
    143                     sel = fillFromMiddle(childrenTop, childrenBottom);
    144                 }
    145                 break;
    146             case LAYOUT_SYNC:
    147                 sel = fillSpecific(mSyncPosition, mSpecificTop);
    148                 break;
    149             case LAYOUT_FORCE_BOTTOM:
    150                 sel = fillUp(mItemCount - 1, childrenBottom);
    151                 adjustViewsUpOrDown();
    152                 break;
    153             case LAYOUT_FORCE_TOP:
    154                 mFirstPosition = 0;
    155                 sel = fillFromTop(childrenTop);
    156                 adjustViewsUpOrDown();
    157                 break;
    158             case LAYOUT_SPECIFIC:
    159                 sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
    160                 break;
    161             case LAYOUT_MOVE_SELECTION:
    162                 sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
    163                 break;
    164             default:
    165                 if (childCount == 0) {
    166                     if (!mStackFromBottom) {
    167                         final int position = lookForSelectablePosition(0, true);
    168                         setSelectedPositionInt(position);
    169                         sel = fillFromTop(childrenTop);
    170                     } else {
    171                         final int position = lookForSelectablePosition(mItemCount - 1, false);
    172                         setSelectedPositionInt(position);
    173                         sel = fillUp(mItemCount - 1, childrenBottom);
    174                     }
    175                 } else {
    176                     if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
    177                         sel = fillSpecific(mSelectedPosition,
    178                                 oldSel == null ? childrenTop : oldSel.getTop());
    179                     } else if (mFirstPosition < mItemCount) {
    180                         sel = fillSpecific(mFirstPosition,
    181                                 oldFirst == null ? childrenTop : oldFirst.getTop());
    182                     } else {
    183                         sel = fillSpecific(0, childrenTop);
    184                     }
    185                 }
    186                 break;
    187             }
    188 
    189             // Flush any cached views that did not get reused above
    190             recycleBin.scrapActiveViews();    //清除掉recycleBin在上面没有被重用的缓存views
    191 
    192             if (sel != null) {
    193                 // the current selected item should get focus if items
    194                 // are focusable
    195                 if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {
    196                     final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&
    197                             focusLayoutRestoreView.requestFocus()) || sel.requestFocus();
    198                     if (!focusWasTaken) {
    199                         // selected item didn't take focus, fine, but still want
    200                         // to make sure something else outside of the selected view
    201                         // has focus
    202                         final View focused = getFocusedChild();
    203                         if (focused != null) {
    204                             focused.clearFocus();
    205                         }
    206                         positionSelector(sel);
    207                     } else {
    208                         sel.setSelected(false);
    209                         mSelectorRect.setEmpty();
    210                     }
    211                 } else {
    212                     positionSelector(sel);
    213                 }
    214                 mSelectedTop = sel.getTop();
    215             } else {
    216                 if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) {
    217                     View child = getChildAt(mMotionPosition - mFirstPosition);
    218                     if (child != null) positionSelector(child);
    219                 } else {
    220                     mSelectedTop = 0;
    221                     mSelectorRect.setEmpty();
    222                 }
    223 
    224                 // even if there is not selected position, we may need to restore
    225                 // focus (i.e. something focusable in touch mode)
    226                 if (hasFocus() && focusLayoutRestoreView != null) {
    227                     focusLayoutRestoreView.requestFocus();
    228                 }
    229             }
    230 
    231             // tell focus view we are done mucking with it, if it is still in
    232             // our view hierarchy.
    233             if (focusLayoutRestoreView != null
    234                     && focusLayoutRestoreView.getWindowToken() != null) {
    235                 focusLayoutRestoreView.onFinishTemporaryDetach();
    236             }
    237             
    238             mLayoutMode = LAYOUT_NORMAL;
    239             mDataChanged = false;
    240             mNeedSync = false;
    241             setNextSelectedPositionInt(mSelectedPosition);
    242 
    243             updateScrollIndicators();
    244 
    245             if (mItemCount > 0) {
    246                 checkSelectionChanged();
    247             }
    248 
    249             invokeOnItemScrollListener();
    250         } finally {
    251             if (!blockLayoutRequests) {
    252                 mBlockLayoutRequests = false;
    253             }
    254         }
    255     }

    layoutChildren会根据mLayoutMode的值选用不同的方式返回一个item view,选择其中一种去介绍,这里选择fillDown,其他的其实差不多,大家可以自行研究:

    fillDown()

    fillDown
     1         /**
     2      * Fills the list from pos down to the end of the list view.从上往下渲染Listview
     3      *
     4      * @param pos The first position to put in the list
     5      *
     6      * @param nextTop The location where the top of the item associated with pos
     7      *        should be drawn
     8      *
     9      * @return The view that is currently selected, if it happens to be in the
    10      *         range that we draw.
    11      */
    12     private View fillDown(int pos, int nextTop) {
    13         View selectedView = null;
    14 
    15         int end = (mBottom - mTop) - mListPadding.bottom;
    16 
    17         while (nextTop < end && pos < mItemCount) {
    18             // is this the selected item?
    19             boolean selected = pos == mSelectedPosition;
    20             View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);    //通过makeAndAddView返回一个view
    21 
    22             nextTop = child.getBottom() + mDividerHeight;
    23             if (selected) {
    24                 selectedView = child;
    25             }
    26             pos++;
    27         }
    28 
    29         return selectedView;
    30     }

    其实fillDown中极其简单,就是调用了一下makeAndAddView返回一个view,makeAndAddView实现的是得到一个view并把它添加到Listview中去

    那么,接下来介绍makeAndAddView()方法,它的作用是获得每一个item view对象,即Listview的每一行数据

    makeAndAddView
     1         /**
     2      * Obtain the view and add it to our list of children. The view can be made
     3      * fresh, converted from an unused view, or used as is if it was in the
     4      * recycle bin.获得view并把它们添加到存放子view的list中去。这个view可以是新构造出来的,可以通过从没有用过的view转过来(翻译不怎么合适)或者如果recycle bin中存在可以重用view
     5      *
     6      * @param position Logical position in the list
     7      * @param y Top or bottom edge of the view to add
     8      * @param flow If flow is true, align top edge to y. If false, align bottom
     9      *        edge to y.
    10      * @param childrenLeft Left edge where children should be positioned
    11      * @param selected Is this position selected?
    12      * @return View that was added
    13      */
    14     private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
    15             boolean selected) {
    16         View child;
    17 
    18         //mDataChanged表示的是最后一次layout是否有“数据”更新,数据的意思包含AdapterDataSetObserver中监测的数据、size的变化、view的重绘和重建、click,checked事件的发生。
    19         //只有layoutChildren后mDataChanged会置为false
    20         if (!mDataChanged) {    
    21             // Try to use an exsiting view for this position
    22             child = mRecycler.getActiveView(position);//从mRecycler中获取一个当前活动的view来重用,mRecycler(AbsListView.RecycleBin对象)会后面介绍
    23             if (child != null) {
    24                 if (ViewDebug.TRACE_RECYCLER) {//debug时候跟踪输出信息(可忽略)
    25                     ViewDebug.trace(child, ViewDebug.RecyclerTraceType.RECYCLE_FROM_ACTIVE_HEAP,
    26                             position, getChildCount());
    27                 }
    28 
    29                 // Found it -- we're using an existing child
    30                 // This just needs to be positioned
    31                 setupChild(child, position, y, flow, childrenLeft, selected, true);//添加该view到Listview中去,这里只需要找到位置而不需要measure  view的大小
    32 
    33                 return child;
    34             }
    35         }
    36 
    37         // Make a new view for this position, or convert an unused view if possible
    38         child = obtainView(position, mIsScrap);//获取一个新的view或者从一个没有用的view转换过来,AbsListview的方法,涉及到Listview中item的重用,和RecycleBin相关
    39 
    40         // This needs to be positioned and measured
    41         setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);//添加该view到Listview中去,这里只需要找到位置而且需要measure  view的大小
    42 
    43         return child;//返回该view对象
    44     }

    在makeAndAddView中,我们看到mRecycler对象,究竟该对象是什么,为什么可以从这个对象中获得一个item view去渲染Listview本身?

    mRecycler是一个RecycleBin对象,从mRecycler的命名可以略知一二,它是一个回收器,回收什么?既然可以从里面获得view,那毫无疑问,回收的肯定有item view,那进一步看看该对象,它是Listview的item重用机制的重要对象,它是一个AbsListview的一个内部类:

      1 RecycleBin 
      2      /**
      3      * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
      4      * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
      5      * start of a layout. By construction, they are displaying current information. At the end of
      6      * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
      7      * could potentially be used by the adapter to avoid allocating views unnecessarily.
      8      *
      9      *解释:Recyclebin实现跨layout的view重用。在RecycleBin中,存储两种不同的view,ActiveViews 和 ScrapViews。
     10      *ActiveViews保存布局开始时屏幕中显示的views。在布局结束时,所有的ActiveViews变成ScrapViews。
     11      *ScrapViews是那些可以被adapter使用,避免重新分配新的view的旧views。
     12      *
     13      * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
     14      * @see android.widget.AbsListView.RecyclerListener
     15      */
     16     class RecycleBin {
     17         private RecyclerListener mRecyclerListener;
     18 
     19         /**
     20          * The position of the first view stored in mActiveViews.
     21          */
     22         private int mFirstActivePosition;
     23 
     24         /**
     25          * Views that were on screen at the start of layout. This array is populated at the start of
     26          * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
     27          * Views in mActiveViews represent a contiguous range of Views, with position of the first
     28          * view store in mFirstActivePosition.
     29          *
     30          *mActiveViews 包含当前屏幕中的item views,它是一个数组,在一开始布局时会被填充,在布局结束时,它里面的view
     31          *会移动到mScrapViews中。在mActiveViews 中的views代表连续范围内的views。
     32          */
     33         private View[] mActiveViews = new View[0];
     34 
     35         /**
     36          * Unsorted views that can be used by the adapter as a convert view.
     37          */
     38         private ArrayList<View>[] mScrapViews;    //存储scrapviews的地方,一个ArrayList数组,一个元素对应一种类型的scrapviews,它可以被adapter作为一个convert view使用
     39 
     40         private int mViewTypeCount;    //item view类型的总数(listview可以包含不同类型的item views)
     41 
     42         private ArrayList<View> mCurrentScrap;
     43 
     44         public void setViewTypeCount(int viewTypeCount) {
     45             if (viewTypeCount < 1) {
     46                 throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
     47             }
     48             //noinspection unchecked
     49             //根据view类型数量,设置scrapviews的存储结构,每一种类型的scrapviews用一个ArrayList去存储,多个类型狗就有多个ArrayList
     50             ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];    
     51             for (int i = 0; i < viewTypeCount; i++) {
     52                 scrapViews[i] = new ArrayList<View>();
     53             }
     54             mViewTypeCount = viewTypeCount;
     55             mCurrentScrap = scrapViews[0];
     56             mScrapViews = scrapViews;
     57         }
     58         
     59         //设置mCurrentScrap中存储的views在下一次layout时必须重新layout
     60         public void markChildrenDirty() {
     61             if (mViewTypeCount == 1) {    //如果只有一种类型的item
     62                 final ArrayList<View> scrap = mCurrentScrap;
     63                 final int scrapCount = scrap.size();
     64                 for (int i = 0; i < scrapCount; i++) {
     65                     scrap.get(i).forceLayout();
     66                 }
     67             } else {
     68                 final int typeCount = mViewTypeCount;
     69                 for (int i = 0; i < typeCount; i++) {
     70                     final ArrayList<View> scrap = mScrapViews[i];
     71                     final int scrapCount = scrap.size();
     72                     for (int j = 0; j < scrapCount; j++) {
     73                         scrap.get(j).forceLayout();
     74                     }
     75                 }
     76             }
     77         }
     78 
     79         public boolean shouldRecycleViewType(int viewType) {
     80             return viewType >= 0;
     81         }
     82 
     83         /**
     84          * Clears the scrap heap.清楚掉mScrapViews中的views
     85          */
     86         void clear() {
     87             if (mViewTypeCount == 1) {
     88                 final ArrayList<View> scrap = mCurrentScrap;
     89                 final int scrapCount = scrap.size();
     90                 for (int i = 0; i < scrapCount; i++) {
     91                     removeDetachedView(scrap.remove(scrapCount - 1 - i), false);
     92                 }
     93             } else {
     94                 final int typeCount = mViewTypeCount;
     95                 for (int i = 0; i < typeCount; i++) {
     96                     final ArrayList<View> scrap = mScrapViews[i];
     97                     final int scrapCount = scrap.size();
     98                     for (int j = 0; j < scrapCount; j++) {
     99                         removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
    100                     }
    101                 }
    102             }
    103         }
    104 
    105         /**
    106          * Fill ActiveViews with all of the children of the AbsListView.把AbsListview中的所以子view添加到ActiveViews中
    107          *
    108          * @param childCount The minimum number of views mActiveViews should hold
    109          * @param firstActivePosition The position of the first view that will be stored in
    110          *        mActiveViews
    111          */
    112         void fillActiveViews(int childCount, int firstActivePosition) {
    113             if (mActiveViews.length < childCount) {
    114                 mActiveViews = new View[childCount];
    115             }
    116             mFirstActivePosition = firstActivePosition;
    117 
    118             final View[] activeViews = mActiveViews;
    119             for (int i = 0; i < childCount; i++) {
    120                 View child = getChildAt(i);//这里注意是把当前listview的item作为active view,也就是当前的active view会作为重用的view,第一次加载的时候,没有子view,所以第一次时,listview的所有view都不是重用的
    121                 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
    122                 // Don't put header or footer views into the scrap heap
    123                 if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
    124                     // Note:  We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
    125                     //        However, we will NOT place them into scrap views.
    126                     activeViews[i] = child;
    127                 }
    128             }
    129         }
    130 
    131         /**
    132          * Get the view corresponding to the specified position. The view will be removed from
    133          * mActiveViews if it is found.从ActiveViews指定位置获得一个view,获得后,这个view会从ActiveViews中去掉
    134          *
    135          * @param position The position to look up in mActiveViews
    136          * @return The view if it is found, null otherwise
    137          */
    138         View getActiveView(int position) {
    139             int index = position - mFirstActivePosition;
    140             final View[] activeViews = mActiveViews;
    141             if (index >=0 && index < activeViews.length) {
    142                 final View match = activeViews[index];
    143                 activeViews[index] = null;
    144                 return match;
    145             }
    146             return null;
    147         }
    148 
    149         /**
    150          * @return A view from the ScrapViews collection. These are unordered.从ScrapViews中获得一个view
    151          */
    152         View getScrapView(int position) {
    153             ArrayList<View> scrapViews;
    154             if (mViewTypeCount == 1) {
    155                 scrapViews = mCurrentScrap;
    156                 int size = scrapViews.size();
    157                 if (size > 0) {
    158                     return scrapViews.remove(size - 1);
    159                 } else {
    160                     return null;
    161                 }
    162             } else {
    163                 int whichScrap = mAdapter.getItemViewType(position);
    164                 if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
    165                     scrapViews = mScrapViews[whichScrap];
    166                     int size = scrapViews.size();
    167                     if (size > 0) {
    168                         return scrapViews.remove(size - 1);
    169                     }
    170                 }
    171             }
    172             return null;
    173         }
    174 
    175         /**
    176          * Put a view into the ScapViews list. These views are unordered.把一个view存储到ScrapViews中
    177          *
    178          * @param scrap The view to add
    179          */
    180         void addScrapView(View scrap) {
    181             AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
    182             if (lp == null) {
    183                 return;
    184             }
    185 
    186             // Don't put header or footer views or views that should be ignored
    187             // into the scrap heap
    188             int viewType = lp.viewType;
    189             if (!shouldRecycleViewType(viewType)) {
    190                 if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
    191                     removeDetachedView(scrap, false);
    192                 }
    193                 return;
    194             }
    195 
    196             if (mViewTypeCount == 1) {
    197                 scrap.dispatchStartTemporaryDetach();
    198                 mCurrentScrap.add(scrap);
    199             } else {
    200                 scrap.dispatchStartTemporaryDetach();
    201                 mScrapViews[viewType].add(scrap);
    202             }
    203 
    204             if (mRecyclerListener != null) {
    205                 mRecyclerListener.onMovedToScrapHeap(scrap);
    206             }
    207         }
    208 
    209         /**
    210          * Move all views remaining in mActiveViews to mScrapViews.把所以的activeviews变成scrapviews
    211          */
    212         void scrapActiveViews() {
    213             final View[] activeViews = mActiveViews;
    214             final boolean hasListener = mRecyclerListener != null;
    215             final boolean multipleScraps = mViewTypeCount > 1;
    216 
    217             ArrayList<View> scrapViews = mCurrentScrap;
    218             final int count = activeViews.length;
    219             for (int i = count - 1; i >= 0; i--) {
    220                 final View victim = activeViews[i];
    221                 if (victim != null) {
    222                     int whichScrap = ((AbsListView.LayoutParams) victim.getLayoutParams()).viewType;
    223 
    224                     activeViews[i] = null;
    225 
    226                     if (!shouldRecycleViewType(whichScrap)) {
    227                         // Do not move views that should be ignored
    228                         if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
    229                             removeDetachedView(victim, false);
    230                         }
    231                         continue;
    232                     }
    233 
    234                     if (multipleScraps) {
    235                         scrapViews = mScrapViews[whichScrap];
    236                     }
    237                     victim.dispatchStartTemporaryDetach();
    238                     scrapViews.add(victim);
    239 
    240                     if (hasListener) {
    241                         mRecyclerListener.onMovedToScrapHeap(victim);
    242                     }
    243 
    244                     if (ViewDebug.TRACE_RECYCLER) {
    245                         ViewDebug.trace(victim,
    246                                 ViewDebug.RecyclerTraceType.MOVE_FROM_ACTIVE_TO_SCRAP_HEAP,
    247                                 mFirstActivePosition + i, -1);
    248                     }
    249                 }
    250             }
    251 
    252             pruneScrapViews();
    253         }
    254 
    255         /**
    256          * Makes sure that the size of mScrapViews does not exceed the size of mActiveViews.
    257          * (This can happen if an adapter does not recycle its views).保证mScrapViews中view数量始终小于等于mActiveViews中view的数量
    258          */
    259         private void pruneScrapViews() {
    260             final int maxViews = mActiveViews.length;
    261             final int viewTypeCount = mViewTypeCount;
    262             final ArrayList<View>[] scrapViews = mScrapViews;
    263             for (int i = 0; i < viewTypeCount; ++i) {
    264                 final ArrayList<View> scrapPile = scrapViews[i];
    265                 int size = scrapPile.size();
    266                 final int extras = size - maxViews;
    267                 size--;
    268                 for (int j = 0; j < extras; j++) {
    269                     removeDetachedView(scrapPile.remove(size--), false);
    270                 }
    271             }
    272         }
    273 
    274         /**
    275          * Puts all views in the scrap heap into the supplied list.把所有的mScrapViews发到自己传进去的List中
    276          */
    277         void reclaimScrapViews(List<View> views) {
    278             if (mViewTypeCount == 1) {
    279                 views.addAll(mCurrentScrap);
    280             } else {
    281                 final int viewTypeCount = mViewTypeCount;
    282                 final ArrayList<View>[] scrapViews = mScrapViews;
    283                 for (int i = 0; i < viewTypeCount; ++i) {
    284                     final ArrayList<View> scrapPile = scrapViews[i];
    285                     views.addAll(scrapPile);
    286                 }
    287             }
    288         }
    289 
    290         /**
    291          * Updates the cache color hint of all known views.
    292          *
    293          * @param color The new cache color hint.
    294          */
    295         void setCacheColorHint(int color) {
    296             if (mViewTypeCount == 1) {
    297                 final ArrayList<View> scrap = mCurrentScrap;
    298                 final int scrapCount = scrap.size();
    299                 for (int i = 0; i < scrapCount; i++) {
    300                     scrap.get(i).setDrawingCacheBackgroundColor(color);
    301                 }
    302             } else {
    303                 final int typeCount = mViewTypeCount;
    304                 for (int i = 0; i < typeCount; i++) {
    305                     final ArrayList<View> scrap = mScrapViews[i];
    306                     final int scrapCount = scrap.size();
    307                     for (int j = 0; j < scrapCount; j++) {
    308                         scrap.get(i).setDrawingCacheBackgroundColor(color);
    309                     }
    310                 }
    311             }
    312             // Just in case this is called during a layout pass
    313             final View[] activeViews = mActiveViews;
    314             final int count = activeViews.length;
    315             for (int i = 0; i < count; ++i) {
    316                 final View victim = activeViews[i];
    317                 if (victim != null) {
    318                     victim.setDrawingCacheBackgroundColor(color);
    319                 }
    320             }
    321         }
    322     }
    RecycleBin

    这个类很简单,就是一些相关的add、get之类的方法。ListView是通过该类的对象实例mRecycler缓存view,当滚动Listview时,即不可视view变成可视时,Listview就有可能使用mRecycler里面的view实现重用,而不需要重新创建一个view,避免资源的浪费。

    回头看一下makeAndAddView两种获得view的方式,一个是mRecycler.getActiveView(position),一个是obtainView(position, mIsScrap),前者已经介绍,看完RecycleBin就知道,它就是重用了ActiveViews中的view,后者是怎么样的呢?

    obtainView
     1     --->AbsListview.java
     2         /**
     3      * Get a view and have it show the data associated with the specified
     4      * position. This is called when we have already discovered that the view is
     5      * not available for reuse in the recycle bin. The only choices left are
     6      * converting an old view or making a new one.这个方法会什么时候调用?就是当view不能通过recycle bin(其实就是mRecycler对象)
     7      *重用的时候调用。那么这个时候的唯一方法就是转换一个旧的view或者创建一个新的view
     8      *
     9      * @param position The position to display
    10      * @param isScrap Array of at least 1 boolean, the first entry will become true if
    11      *                the returned view was taken from the scrap heap, false if otherwise.如果返回的view是从scrap heap中获得的,isScrap第一个元素将会是true,如果是自己创建的新view,它就是true
    12      * 
    13      * @return A view displaying the data associated with the specified position
    14      */
    15     View obtainView(int position, boolean[] isScrap) {
    16         isScrap[0] = false;
    17         View scrapView;
    18 
    19         scrapView = mRecycler.getScrapView(position);    //在Scrapview中获取view
    20 
    21         View child;
    22         if (scrapView != null) {    //如果有这个view,我们就通过old view去转换
    23             if (ViewDebug.TRACE_RECYCLER) {
    24                 ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.RECYCLE_FROM_SCRAP_HEAP,
    25                         position, -1);
    26             }
    27             
    28             //通过把scrapView作为参数传进去getView方法,这样就可以实现通过mAdapter转换成一个新view(推荐mAdapter要使用holder的那种写法)
    29             child = mAdapter.getView(position, scrapView, this);    
    30 
    31             if (ViewDebug.TRACE_RECYCLER) {
    32                 ViewDebug.trace(child, ViewDebug.RecyclerTraceType.BIND_VIEW,
    33                         position, getChildCount());
    34             }
    35 
    36             if (child != scrapView) {    //如果你的getView方法返回的view是自己重新创建的view,那么scrapView就与它不是相等的
    37                 mRecycler.addScrapView(scrapView);    //mRecycler存储这个scrapView
    38                 if (mCacheColorHint != 0) {
    39                     child.setDrawingCacheBackgroundColor(mCacheColorHint);
    40                 }
    41                 if (ViewDebug.TRACE_RECYCLER) {
    42                     ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
    43                             position, -1);
    44                 }
    45             } else {
    46                 isScrap[0] = true;    //设置isScrap的第一个元素为true,表明是通过old view转换获取view的
    47                 child.dispatchFinishTemporaryDetach();
    48             }
    49         } else {
    50             child = mAdapter.getView(position, null, this);    //第二个参数为null,那么只能创建一个新的view了
    51             if (mCacheColorHint != 0) {
    52                 child.setDrawingCacheBackgroundColor(mCacheColorHint);
    53             }
    54             if (ViewDebug.TRACE_RECYCLER) {
    55                 ViewDebug.trace(child, ViewDebug.RecyclerTraceType.NEW_VIEW,
    56                         position, getChildCount());
    57             }
    58         }
    59 
    60         return child;
    61     }

     介绍到这里,可以总结一下Listview是怎么重用item view的了:

    如果数据没有变化,那么直接从ActiveViews中取到view,然后去布局,ActiveViews保存的就是当前可视的views,这种情况下相当于,缓存下来的view还是被布局会原来的位置,什么也没有变化;

    如果数据变化了,那么可能会从SrapViews中去到view去完成布局,ScrapVews其实就缓存的是上一次可视的views,这种情况的好处是,即使数据已经变化了,但依然不需要去创建一个新的view,避免耗费移动设备可怜少的资源。

    这里说的数据变化其中的“数据”并不是仅仅是adapter中的真实数据,而是包括:AdapterDataSetObserver中监测的数据、size的变化、view的重绘和重建、click,checked等事件的发生。

  • 相关阅读:
    电商网站秒杀与抢购的系统架构[转]
    解决sublime无法安装软件的问题
    oracel中decode的使用
    使用Spring进行远程访问与Web服务[转]
    解决maven传递依赖中的版本冲突
    Linux下rz,sz
    spring bean 使用继承
    Java14-ListIterator
    Java13-Iterator的应用
    Java11-ArrayList常用的方法
  • 原文地址:https://www.cnblogs.com/chiefhsing/p/2813753.html
Copyright © 2011-2022 走看看