ListView解析
ListView通过一个Adapter来完成数据和组件的绑定。以ListActivity为例,它集成自Activity,里面包含有一个ListAdapter和一个ListView。绑定的操作通过setListAdapter来完成。本文主要通过源码,来说明,具体的绑定过程究竟是如何进行的,以及convertView(Adapter的getView的第二个参数)缓存实现机制。
如下是ListActivity的代码片段:
/** * This field should be made private, so it is hidden from the SDK. * {@hide} */ protected ListAdapter mAdapter; /** * This field should be made private, so it is hidden from the SDK. * {@hide} */ protected ListView mList;
最关键的绑定动作由下:
public void setListAdapter(ListAdapter adapter) { synchronized (this) { ensureList(); mAdapter = adapter; mList.setAdapter(adapter); } }
其中调用了ListView的setAdapter完成,传入的参数类型为ListAdapter。
public void setAdapter(ListAdapter adapter) { if (mAdapter != null && mDataSetObserver != null) { mAdapter.unregisterDataSetObserver(mDataSetObserver); } resetList(); mRecycler.clear(); ...
由上述代码,resetList主要是用来重置ListView的headerView和footView的。关键的一个方法是mRecycler.clear(),成员变量mRecycler是从AbsListView继承而来的,它的类型是RecycleBin,从名字看起来好像和“回收”有关。查看源码,有这样一段描述RecycleBin的话:
* The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the * start of a layout. By construction, they are displaying current information. At the end of * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that * could potentially be used by the adapter to avoid allocating views unnecessarily.
总而言之,RcycleBin是用来缓存View,以避免不必要的回收View的——设想一下,如果向下滚动ListView后,再回滚到原来位置,如果要重新把View都生成一遍,那要消耗一定的时间。如果缓存起来,对View直接填充数据即可,这也是现在通用的办法。
class RecycleBin { private RecyclerListener mRecyclerListener; /** * The position of the first view stored in mActiveViews. */ private int mFirstActivePosition; /** * Views that were on screen at the start of layout. This array is populated at the start of * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews. * Views in mActiveViews represent a contiguous range of Views, with position of the first * view store in mFirstActivePosition. */ private View[] mActiveViews = new View[0]; /** * Unsorted views that can be used by the adapter as a convert view. */ private ArrayList<View>[] mScrapViews; private int mViewTypeCount; //代表需要显示的View的类型的个数:ListView中不是所有的View的类型都一样,不过在BaseAdapter里,默认是1 private ArrayList<View> mCurrentScrap; //表示当前类型的scrap的View
根据解释,mFirstActivePosition是存储在mActiveViews的第一个View的position。什么意思?待会解释。其中mActiveViews是一个View数组,保存的是当前屏幕可见的所有View。不可见的View都“移动到”mScrapViews(scrap的含义是废弃的,因此mScrapViews保存的应该是所有“废弃”的View)——把不可见的View都当做废弃的View保存起来,并没有直接释放,这就是缓存。在这里可以解释一下mFirstActivePosition的含义:比如现在屏幕上显示的是3,4,5三个View,则mFirstActivePosition为3,即第一个处于Active状态的View是第三个。
保存“废弃”的View的mScrapViews是ArrayList<View>[]类型,即它是一个数组,每个数组保存的是View的链表。不像mActiveViews,mScrapViews是可以动态改变的,结合实际情况,每个屏幕可以显示的View的数量是一定的,但是不可见的View可就太多太多了,所以这符合实际需求。其中“Unsorted views that can be used by the adapter as a convert view”,表明,convertView就是mScrapViews中的某个View。
在setAdapter里面调用了mRecycler.clear(),下面来看看这个方法:
/** * Clears the scrap heap. */ void clear() { if (mViewTypeCount == 1) { final ArrayList<View> scrap = mCurrentScrap; final int scrapCount = scrap.size(); for (int i = 0; i < scrapCount; i++) { removeDetachedView(scrap.remove(scrapCount - 1 - i), false); } } else { final int typeCount = mViewTypeCount; for (int i = 0; i < typeCount; i++) { final ArrayList<View> scrap = mScrapViews[i]; final int scrapCount = scrap.size(); for (int j = 0; j < scrapCount; j++) { removeDetachedView(scrap.remove(scrapCount - 1 - j), false); } } } }
从clear()方法中可以看到,如果mViewTypeCount == 1,则只需清楚mCurrentScrap的内容即可;否则按照不同的类型,都统统清除掉。这里的清除是调用ViewGroup的removeDetachedView将View从View树中去掉。
分析到这里,还是在setListAdapter方法里面,它首先作了清除View的动作。
public void setAdapter(ListAdapter adapter) { if (mAdapter != null && mDataSetObserver != null) { mAdapter.unregisterDataSetObserver(mDataSetObserver); } resetList(); mRecycler.clear(); ... // AbsListView#setAdapter will update choice mode states. super.setAdapter(adapter); if (mAdapter != null) { mAreAllItemsSelectable = mAdapter.areAllItemsEnabled(); mOldItemCount = mItemCount; mItemCount = mAdapter.getCount(); //获取Adapter的getCount值,这个值是根据数据源的个数来设定的 checkFocus(); mDataSetObserver = new AdapterDataSetObserver(); mAdapter.registerDataSetObserver(mDataSetObserver); //注册数据集观察者,待数据源大小有变时,需要更新ListView mRecycler.setViewTypeCount(mAdapter.getViewTypeCount()); //默认是1(BaseAdapter里实现) ... } else { mAreAllItemsSelectable = true; checkFocus(); // Nothing selected checkSelectionChanged(); } requestLayout(); //布局 }
分析到这里,View就加载完成了。下面分析数据源有变化时,如何利用NotifyDataSetChanged来更新View。
public void notifyDataSetChanged() { mDataSetObservable.notifyChanged(); }
实际调用的是DataSetObservable的方法notifyChanged(),经过多次调用,最终调用到的是AdapterView的内部类AdapterDataSetObserver的onChanged——重写了DataSetObserver:
class AdapterDataSetObserver extends DataSetObserver { private Parcelable mInstanceState = null; @Override public void onChanged() { mDataChanged = true; mOldItemCount = mItemCount; mItemCount = getAdapter().getCount(); //再次获取到View的个数 // Detect the case where a cursor that was previously invalidated has // been repopulated with new data. if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null && mOldItemCount == 0 && mItemCount > 0) { AdapterView.this.onRestoreInstanceState(mInstanceState); mInstanceState = null; } else { rememberSyncState(); } checkFocus(); requestLayout(); //布局 }
在ListView滚动时,只需要通知数据源发生了变化即可自动更新View。在ListView中有一个makeAndAddView方法,该方法根据需要重新生成一个View,或者使用(reuse)缓存起来的View。ListView通过AbsListView的obtainView调用getView——这个getView就是我们需要重载的那个getView。
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft, boolean selected) { View child; if (!mDataChanged) { // Try to use an existing view for this position child = mRecycler.getActiveView(position); //判断特定position的View是否存在,如果存在,则选出来。 if (child != null) { if (ViewDebug.TRACE_RECYCLER) { ViewDebug.trace(child, ViewDebug.RecyclerTraceType.RECYCLE_FROM_ACTIVE_HEAP, position, getChildCount()); } // Found it -- we're using an existing child // This just needs to be positioned setupChild(child, position, y, flow, childrenLeft, selected, true); return child; } } // Make a new view for this position, or convert an unused view if possible child = obtainView(position, mIsScrap); // This needs to be positioned and measured setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]); return child; }
至此,基本上就分析完了。
小结:
1、可以加深为什么在继承BaseAdapter时,需要重写几个方法那几个方法。
2、数据源通知更新。