ListActivity和ListView源碼分析(1) 載入布局,listview載入網狀圖片
最近寫代碼總是用到ListActivity,因此看了下ListView和ListActivity的原始碼.
1 載入布局
一般來說我們使用自訂的ListActivity時,在建立時首先需要調用setContentView函數來設定這個Activity對應的view布局.
而從Activity.onContentChanged()的api文檔說明來看,setContentView方法會導致onContentChanged方法被調用.因此我們接下來來看下ListActivity的onContentChanged方法.
public void onContentChanged() { super.onContentChanged(); View emptyView = findViewById(com.android.internal.R.id.empty); mList = (ListView)findViewById(com.android.internal.R.id.list); if (mList == null) { throw new RuntimeException( "Your content must have a ListView whose id attribute is " + "'android.R.id.list'"); } if (emptyView != null) { mList.setEmptyView(emptyView); } mList.setOnItemClickListener(mOnClickListener); if (mFinishedStart) { setListAdapter(mAdapter); } mHandler.post(mRequestFocus); mFinishedStart = true; }
1.1 R.id.list 擷取ListView
我們可以看到這裡取了兩個固定ID的view,一個是R.id.empty,一個是R.id.list.
R.id.list對應Activity中的listview,這也是為什麼官方文檔(參看前一篇文檔)中寫道”確保你自訂的Layout包含一個id為”@android:id/list”的ListView”.我們也可以看到如果系統取不到R.id.list對應的view,會直接拋出異常.得到的這個view將儲存在mList中.
1.2 R.id.empty 擷取空視圖
而R.id.empty在官方文檔中也有提到,這view將會在list view為空白時顯示.
因為mList剛剛擷取得到,因此此時肯定為空白,一旦我們得到這個empty view,立刻調用ListView的setEmptyView函數:
public void setEmptyView(View emptyView) { mEmptyView = emptyView; // If not explicitly specified this view is important for accessibility. if (emptyView != null && emptyView.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { emptyView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); } final T adapter = getAdapter(); final boolean empty = ((adapter == null) || adapter.isEmpty()); updateEmptyStatus(empty); }
這個時候adapter尚未設定所以肯定為空白,所以empty為true.
updateEmptyStatus主要是設定一些可見度之類的參數.
1.3 setOnItemClickListener註冊點擊回調
接下來onContentChanged函數調用了ListView的setOnItemClickListener方法註冊每個item的點擊回調監聽類mOnClickListener.
private AdapterView.OnItemClickListener mOnClickListener = new AdapterView.OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View v, int position, long id) { onListItemClick((ListView)parent, v, position, id); } };
我們看到這裡調用了方法onListItemClick,這也就是為什麼我們希望在自訂的ListActivity中監聽每個item的點擊方法時,必須重載父類中的onListItemClick方法.
1.4 mFinishedStart
回到ListActivity的onContentChanged函數中來,mFinishedStart參數起到了這樣一個作用.
當這個Activity被最初建立時,mFinishedStart值為false.因此這個時候自然也不需要設定adapter.而在onContentChanged函數被調用過一次之後, mFinishedStart變為true.這保證了在後面已經設定了adapter之後,一旦內容變化,adapter不需要再次被使用者手動設定.
最後mHandler.post(mRequestFocus);通知父類mList可以獲得焦點.至此,setContentView方法結束.
2 setListAdapter設定adapter
後面我們使用ListActivity一般就會調用方法setListAdapter,設定對應的adapter用於給ListView提供資料.這個函數涉及到了ListView和Adapter互動的過程,我們將在下一章節詳細講述.
public void setAdapter(ListAdapter adapter) { if (mAdapter != null && mDataSetObserver != null) { mAdapter.unregisterDataSetObserver(mDataSetObserver); } resetList(); mRecycler.clear(); if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) { mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter); } else { mAdapter = adapter; } mOldSelectedPosition = INVALID_POSITION; mOldSelectedRowId = INVALID_ROW_ID; // AbsListView#setAdapter will update choice mode states. super.setAdapter(adapter); if (mAdapter != null) { mAreAllItemsSelectable = mAdapter.areAllItemsEnabled(); mOldItemCount = mItemCount; mItemCount = mAdapter.getCount(); checkFocus(); mDataSetObserver = new AdapterDataSetObserver(); mAdapter.registerDataSetObserver(mDataSetObserver); 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(); } requestLayout(); }