这几天把ListView源码看了下,基本整理下思路并写了这篇博客,也是对学习源码的一个记录。
首先看ListView的构造方法干了些什么
public ListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle);//父类AbsListview中初始化一些属性,比如焦点setFocusableInTouchMode(true),绘制setWillNotDraw(false)等 TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ListView, defStyle, 0); CharSequence[] entries = a.getTextArray( com.android.internal.R.styleable.ListView_entries); if (entries != null) { setAdapter(new ArrayAdapter<CharSequence>(context, com.android.internal.R.layout.simple_list_item_1, entries)); } //获取item分割线 drawable 可以自定义 final Drawable d = a.getDrawable(com.android.internal.R.styleable.ListView_divider); if (d != null) { // If a divider is specified use its intrinsic height for divider height setDivider(d); } //头部样式 final Drawable osHeader = a.getDrawable( com.android.internal.R.styleable.ListView_overScrollHeader); if (osHeader != null) { setOverscrollHeader(osHeader); } //尾部样式 final Drawable osFooter = a.getDrawable( com.android.internal.R.styleable.ListView_overScrollFooter); if (osFooter != null) { setOverscrollFooter(osFooter); } // Use the height specified, zero being the default final int dividerHeight = a.getDimensionPixelSize( com.android.internal.R.styleable.ListView_dividerHeight, 0); if (dividerHeight != 0) { setDividerHeight(dividerHeight); } mHeaderDividersEnabled = a.getBoolean(R.styleable.ListView_headerDividersEnabled, true); mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true); a.recycle(); }上面构造方法做了一些初始化工作,并设置了默认样式
上述操作对ListView的测量并赋值给成员变量 ,注意ListView第一次创建的时候并没有mAdapter的存在,只有在setAdapter被我们调用过后才会执行这些方法,也就是说在setAdapter中一定会调用requestLayout方法重新走一遍流程。
ListView中并没有onLayout方法,那也就是说一定是在他的父类AbsListView中,我们可以看到它调用了layoutChildren(),从方法名看应该是对子view进行布局,这个layoutChildren是一个空实现方法,也就是说应该是通过AbsListView的子类ListVIew和GridView进行实现
protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); mInLayout = true; //拿到view 数量 final int childCount = getChildCount(); if (changed) { for (int i = 0; i < childCount; i++) { getChildAt(i).forceLayout(); } mRecycler.markChildrenDirty(); } // 由子类ListView 和 GridView实现,是核心布局方法代码,也是listview与adapter交互数据的主要入口函数 layoutChildren(); mInLayout = false; mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR; // TODO: Move somewhere sane. This doesn't belong in onLayout(). if (mFastScroll != null) { mFastScroll.onItemCountChanged(getChildCount(), mItemCount); } }这里注意两个对象:
View[] mActiveViews:存放的是当前ListView可以使用的待激活的子item viewArrayList[] mScrapViews:存放的是在ListView滑动过程中滑出屏幕来回收以便下次利用的子item view@Override protected void layoutChildren() { ... final int firstPosition = mFirstPosition; final RecycleBin recycleBin = mRecycler; // 只有在调用adapter.notifyDatasetChanged()方法一直到layout()布局结束, //dataChanged为true,默认为false,这里如果调用notifyDatasetChanged,就会将Item添加到ReyclerBin当中,这个 //ReyclerBin封装了这两个集合用来存放对应的符合条件的item,用来实现复用机制 //1.View[] mActiveViews : 存放的是当前ListView可以使用的待激活的子item view //2.ArrayList<View>[] mScrapViews : 存放的是在ListView滑动过程中滑出屏幕来回收以便下次利用的子item view if (dataChanged) { // dataChanged为true,说明当前listview是有数据的了,把当前所有的item view // 存放到RecycleBin对象的mScrapViews中保存 for (int i = 0; i < childCount; i++) { recycleBin.addScrapView(getChildAt(i), firstPosition+i); } } else { // dataChanged默认为false,第一次执行此方法走这里 //将view添加到 activeViews[] 中 recycleBin.fillActiveViews(childCount, firstPosition); } ... switch (mLayoutMode) { ... default: //一般情况下走这里 if (childCount == 0) { // 第一次布局的时候,因为还没有setAdapter,没有走mAdpate.getCount方法,所以childCount必然为0 if (!mStackFromBottom) { final int position = lookForSelectablePosition(0, true); setSelectedPositionInt(position); // 从上到上布局listview能显示得下的子view,具体的填充view的方法,下面讲到 sel = fillFromTop(childrenTop); } else { final int position = lookForSelectablePosition(mItemCount - 1, false); setSelectedPositionInt(position); sel = fillUp(mItemCount - 1, childrenBottom); } } else { // 非第一次layout,也就说执行了nitifyDatasetChanged方法之后 if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) { sel = fillSpecific(mSelectedPosition, oldSel == null ? childrenTop : oldSel.getTop()); } else if (mFirstPosition < mItemCount) { // 通常情况走这里,fillSpecific()会调用fillUp()和fillDown()布局子view sel = fillSpecific(mFirstPosition, oldFirst == null ? childrenTop : oldFirst.getTop()); } else { sel = fillSpecific(0, childrenTop); } } break; } //到这里,ListView中的view就被填充完毕. ... //布局完成之后记录状态 mLayoutMode = LAYOUT_NORMAL; mDataChanged = false; }Item布局的填充 在layoutChidren中有几个以fill开头的方法就是具体的Item的填充方法。fillSpecific()会根据mStackFromBottom参数判断填充方向,通过fillUp,fillDown进行填充
private View fillSpecific(int position, int top) { boolean tempIsSelected = position == mSelectedPosition; View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected); // Possibly changed again in fillUp if we add rows above this one. mFirstPosition = position; View above; View below; final int dividerHeight = mDividerHeight; //根据填充方向,如果mStackFromBottom为false,表示从顶部向底部填充,true反之 //mStackFromBottom 可以通过 xml文件android:stackFromBottom="false"设置,默认为false if (!mStackFromBottom) { //具体填充方法 above = fillUp(position - 1, temp.getTop() - dividerHeight); // This will correct for the top of the first view not touching the top of the list adjustViewsUpOrDown(); //具体填充方法 below = fillDown(position + 1, temp.getBottom() + dividerHeight); int childCount = getChildCount(); if (childCount > 0) { correctTooHigh(childCount); } } else { below = fillDown(position + 1, temp.getBottom() + dividerHeight); // This will correct for the bottom of the last view not touching the bottom of the list adjustViewsUpOrDown(); above = fillUp(position - 1, temp.getTop() - dividerHeight); int childCount = getChildCount(); if (childCount > 0) { correctTooLow(childCount); } } if (tempIsSelected) { return temp; } else if (above != null) { return above; } else { return below; } }具体填充布局可以看我的另一篇博客ListView源码分析(二)
setAdapter中通过mAdapter.registerDataSetObserver(mDataSetObserver)注册一个AdapterDataSetObserver订阅者,每当调用notifyDataSetChange的时候,就会触发AdapterDataSetObserver的onChanged的方法,这个是观察者模式,这个方法最终调用requestLayout方法,也就是说我们每次setAdapter之后就会重新布局,这时候mAdapter不为空。
@Override public void setAdapter(ListAdapter adapter) { if (mAdapter != null && mDataSetObserver != null) { mAdapter.unregisterDataSetObserver(mDataSetObserver); //将一些成员变量还原设置为初始默认值 //mLayoutMode = LAYOUT_NORMAL resetList(); // mRecycler的mScrapViews清空并执行listview.removeDetachedView //mScrapViews 存放边界之外的view mRecycler.clear(); if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) { // 如果listview有headerView或者FooterView则会生成包装adapter,生成一个含有HeaderView 和footerView的adapter mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter); } else { mAdapter = adapter; } mOldSelectedPosition = INVALID_POSITION;//-1 mOldSelectedRowId = INVALID_ROW_ID;//Long.MIN_VALUE // AbsListView#setAdapter will update choice mode states. //给父亲 adblistView 设置 adapter super.setAdapter(adapter); if (mAdapter != null) { mAreAllItemsSelectable = mAdapter.areAllItemsEnabled(); mOldItemCount = mItemCount; //调用adapter的getCount 得到条目个数 mItemCount = mAdapter.getCount(); checkFocus(); //注册观察者,这个观察者每当调用notifyDataSetChange的时候就会触发 mDataSetObserver = new AdapterDataSetObserver(); mAdapter.registerDataSetObserver(mDataSetObserver); // 设置listview的数据源类型,并在mRecycler中初始化对应个数的scrapViews list mRecycler.setViewTypeCount(mAdapter.getViewTypeCount()); int position; if (mStackFromBottom) { position = lookForSelectablePosition(mItemCount - 1, false); } else { position = lookForSelectablePosition(0, true); } setSelectedPositionInt(position); setNextSelectedPositionInt(position); if (mItemCount == 0) { // Nothing selected checkSelectionChanged(); } } else { mAreAllItemsSelectable = true; checkFocus(); // Nothing selected checkSelectionChanged(); } // 会调用顶层viewRootImpl.performTraversals(),导致视图重绘,listview刷新 requestLayout(); }这个方法在BaseAdapter中
public void notifyDataSetChanged() { mDataSetObservable.notifyChanged();}这时候根据观察者模式,会调用订阅者AdapterDataSetObserver的onChanged方法,上面提到过,最终还是会调用requestLayout进行重新布局
@Override public void onChanged() { mDataChanged = true; mOldItemCount = mItemCount; mItemCount = getAdapter().getCount(); // 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(); // 同样,最终调用viewRootImpl.performTraversals(),导致视图重绘,执行listview的 // measure layout 方法等 requestLayout(); }新闻热点
疑难解答