正確處理listview的position
當ListView包含有HeaderView或FooterView時,傳入getView或者onItemClick的position是怎樣的,這是個值得探討的問題
先列出錯誤的用法
定義:
- private MyAdapter mAdapter;
- /**
- * 包含資料的list
- */
- private List<String> mDataList1 = new ArrayList<String>();
錯誤用法一:
- @Override
- public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
- String item = (String) mDataList1.get(position);
- // doSomething...
- }
錯誤用法二:
- @Override
- public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
- String item = (String) mAdapter.getItem(position);
- // doSomething...
- }
當ListView沒有包含HeaderView和FooterView的時候,上面的用法沒有問題,一旦包含,那麼獲取的資料項可能不準。因為此時傳入的position是包含了HeaderView和FooterView的索引的:
- mListView.addHeaderView(headerView);
- mListView.addFooterView(footerView);
- mAdapter = new MyAdapter();
- mAdapter.setDataList1(mDataList1);
- mListView.setAdapter(mAdapter);
- mListView.setOnItemClickListener(this);
- ...
- @Override
- public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
- String item = (String) mAdapter.getItem(position);
- // 當position=1的時候,取出的item是處在索引0位置的資料
- }
如果按照上面的方式編碼,則點選列表中的任意一項,獲取的資料項始終是position-1項。即這裡的position其實是一個包含了HeaderViews和FooterViews,以及我們的DataList的大List中的索引。
那麼正確獲取資料項的方法是:
- public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
- String item = (String) adapterView.getAdapter().getItem(position);
- // doSomething...
- }
當然你可以用判斷position==0,但是如果包含有多個HeaderView或者FooterView,這樣判斷既麻煩也容易出錯。按照上面的方法做,無需關心position值是什麼,都可以正確獲取資料項,Android已經幫我們處理了所有的情況。
看起來AdapterView.getAdapter().getItem()與Adapter.getItem()沒什麼不同,但實際上,當ListView包含了HeaderView的時候,AdapterView.getAdapter()獲取的Adapter不是我們定義的Adapter。
為了避免下面各種adapter的混淆,命名我們的adapter為myAdapter。
來看下ListView.setAdapter的原始碼,看一下Android對我們的myAdapter做了什麼:
- // ListView.java
- ...
- /**
- * Sets the data behind this ListView.
- *
- * The adapter passed to this method may be wrapped by a {@link WrapperListAdapter},
- * depending on the ListView features currently in use. For instance, adding
- * headers and/or footers will cause the adapter to be wrapped.
- *
- * @param adapter The ListAdapter which is responsible for maintaining the
- * data backing this list and for producing a view to represent an
- * item in that data set.
- *
- * @see #getAdapter()
- */
- @Override
- 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;
- }
- ...
- ...
可以很清楚的看到,當呼叫ListView.setAdapter的時候,會先判斷是否已經包含了HeaderView和FooterView,如果包含,則ListView新建一個包裝類HeaderViewListAdapter,包含myAdapter,然後ListView內部的另一個adapter引用(AbsListView.mAdapter)指向這個物件,myAdapter並沒有被真的改變。
那麼當ListView包含了HeaderView的時候,呼叫的getItem方法又有什麼不同?來看看HeaderViewListAdapter.getItem(),原始碼如下:
- // HeaderViewListAdapter.java
- ...
- private final ListAdapter mAdapter;
- ...
- public HeaderViewListAdapter(ArrayList<ListView.FixedViewInfo> headerViewInfos,
- ArrayList<ListView.FixedViewInfo> footerViewInfos,
- ListAdapter adapter) {
- mAdapter = adapter;
- mIsFilterable = adapter instanceof Filterable;
- if (headerViewInfos == null) {
- mHeaderViewInfos = EMPTY_INFO_LIST;
- } else {
- mHeaderViewInfos = headerViewInfos;
- }
- if (footerViewInfos == null) {
- mFooterViewInfos = EMPTY_INFO_LIST;
- } else {
- mFooterViewInfos = footerViewInfos;
- }
- mAreAllFixedViewsSelectable =
- areAllListInfosSelectable(mHeaderViewInfos)
- && areAllListInfosSelectable(mFooterViewInfos);
- }
- ...
- public Object getItem(int position) {
- // Header (negative positions will throw an IndexOutOfBoundsException)
- int numHeaders = getHeadersCount();
- if (position < numHeaders) {
- return mHeaderViewInfos.get(position).data;
- }
- // Adapter
- final int adjPosition = position - numHeaders;
- int adapterCount = 0;
- if (mAdapter != null) {
- adapterCount = mAdapter.getCount();
- if (adjPosition < adapterCount) {
- return mAdapter.getItem(adjPosition);
- }
- }
- // Footer (off-limits positions will throw an IndexOutOfBoundsException)
- return mFooterViewInfos.get(adjPosition - adapterCount).data;
- }
- ...
該方法對position的各種情況做了判斷,如果包含有HeaderViews,則會先從position減掉HeaderView的size。看這一句:
- return mAdapter.getItem(adjPosition);
這裡的mAdapter,通過建構函式HeaderViewListAdapter賦值,結合ListView.setAdapter()原始碼可以知道就是myAdapter,所以此時的mAdapter.getItem=myAdapter.getItem,傳入的position範圍是0~DataList.size()。
需要注意的是AdapterView.getCount()返回的資料是包含有HeaderView和FooterView的個數的:
- public int getCount() {
- if (mAdapter != null) {
- return getFootersCount() + getHeadersCount() + mAdapter.getCount();
- } else {
- return getFootersCount() + getHeadersCount();
- }
- }
那麼,在myAdapter中的getView,以及getItem傳入的position為什麼沒有受到影響呢?原因是類似的。
ListView最終在渲染item佈局的時候(具體流程不在這裡解釋),會呼叫mAdapter.getView,此處的mAdapter,包含HeaderView的時候是HeaderViewListAdapter,所以還是直接看HeaderViewListAdapter.getView的原始碼:
- // HeaderViewListAdapter.java
- ...
- public View getView(int position, View convertView, ViewGroup parent) {
- // Header (negative positions will throw an IndexOutOfBoundsException)
- int numHeaders = getHeadersCount();
- if (position < numHeaders) {
- return mHeaderViewInfos.get(position).view;
- }
- // Adapter
- final int adjPosition = position - numHeaders;
- int adapterCount = 0;
- if (mAdapter != null) {
- adapterCount = mAdapter.getCount();
- if (adjPosition < adapterCount) {
- return mAdapter.getView(adjPosition, convertView, parent);
- }
- }
- // Footer (off-limits positions will throw an IndexOutOfBoundsException)
- return mFooterViewInfos.get(adjPosition - adapterCount).view;
- }
對於position的處理同getItem(),所以原因也很明瞭了。
瞭解了position與HeaderView之間的關係後,在編寫這部分程式碼的時候就應當特別注意一點:addHeaderView與addFooterView必須在setAdapter之前被呼叫。因為setAdapter中要對headers和footers做判斷的!
不過即使你粗心了,Android也拋異常會提醒你:
Caused by: java.lang.IllegalStateException: Cannot add header view to list — setAdapter has already been called.
相關文章
- 如何正確處理nonce
- background-position的正確理解方式
- Recoil 中預設值的正確處理
- Node中POST請求的正確處理方式
- Jtti:怎樣正確處理Redis中的海量資料JttiRedis
- JavaScript 如何正確處理 Unicode 編碼問題!JavaScriptUnicode
- JavaScript如何正確處理Unicode編碼問題!JavaScriptUnicode
- 使用 pymysql 的時候如何正確的處理轉義字元MySql字元
- 如何在Kubernetes部署期間正確處理DB模式模式
- 材質最佳化:如何正確處理紋理和材質的關係
- 遇到股票套牢該如何正確處理?股票套牢解套技
- 正確理解CAP理論
- MyBatis SQL xml處理小於號與大於號正確的格式MyBatisSQLXML
- 被誤刪的檔案正確處理方法,快速找回誤刪的檔案
- php cli 中的使用curl 記憶體溢位時的正確處理辦法PHP記憶體溢位
- MyBatis SQL資料庫xml處理小於號與大於號正確的格式MyBatisSQL資料庫XML
- 處理日期和時區轉換:為什麼正確的 UTC 轉換很重要
- Apache Flink 如何正確處理實時計算場景中的亂序資料Apache
- 前端工作流編譯正確操作流程和錯誤處理記錄前端編譯
- 處理一份內心煎熬的工作有兩種方法——只有一種是正確的
- 計算機伺服器中了locked勒索病毒的正確處理流程,locked勒索病毒解密計算機伺服器解密
- Objective-C 轉 Swift 的第一道坎——論如何正確的處理可選型別ObjectSwift型別
- Troubleshooting 專題 - 問正確的問題 得到正確的答案
- 正規表示式處理批量插入
- 應對網賭威尼斯人被黑不給出款的技巧如何正確處理情況
- [譯]JavaScript async / await:好處、坑和正確用法JavaScriptAI
- Java處理正則匹配卡死(正則回溯問題)Java
- 使用正規表示式處理金額
- 分散式 | 資料庫連線如何正確處理 TCP 連線三次握手失敗分散式資料庫TCP
- PHP Opcache 的正確使用PHPopcache
- 如何正確的找BUG
- Ceph的正確玩法之Ceph糾刪碼理論與實踐
- Java異常處理:如何寫出“正確”但被編譯器認為有語法錯誤的程式Java編譯
- Handler正確用法
- TiDB 的正確使用姿勢TiDB
- Redis的正確使用姿勢Redis
- node 升級的正確方法
- Android中Handler的正確使用Android
- 上帝與集合的正確用法