當ListView有Header時,onItemClick裏的position不正確

當給ListView加了一個HeaderView後(代碼如下),我們發現,onItemClick方法裏的position參數的值不是我們所期望的,比如點擊ListView的第一行,我們期望的position是0,可是實際上卻是1,也就是說,它是從Header而不是從第一行開始計數的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
 
    setContentView(R.layout.home);
 
    mAdapter = new MyAdapter(this);
 
    mListView = (ListView) findViewById(R.id.list);
    mListView.addHeaderView(getLayoutInflater().inflate(R.layout.list_header));
    mListView.setAdapter(mAdapter);
    mListView.setOnClickListener(this);
}
 
@Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
    doSomething(mAdapter.getItem(position));
}

 Google了下,發現有個老外issue過一個bug,和我遇到的問題一樣,不過這個bug被RomainGuy reject掉了,理由是,你用錯了,請用getAdapter。這回答的太簡潔了,完全沒法理解,所以只好又去仔細研究ListView的代碼,終於領會他的意思了。把其中addHeaderViewsetAdapter方法貼下來

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
 * Add a fixed view to appear at the top of the list. If addHeaderView is
 * called more than once, the views will appear in the order they were
 * added. Views added using this call can take focus if they want.
 * <p>
 * NOTE: Call this before calling setAdapter. This is so ListView can wrap
 * the supplied cursor with one that that will also account for header
 * views.
 *
 * @param v The view to add.
 * @param data Data to associate with this view
 * @param isSelectable whether the item is selectable
 */
public void addHeaderView(View v, Object data, boolean isSelectable) {
    if (mAdapter != null) {
        throw new IllegalStateException(
                "Cannot add header view to list -- setAdapter has already been called.");
    }
 
    FixedViewInfo info = new FixedViewInfo();
    info.view = v;
    info.data = data;
    info.isSelectable = isSelectable;
    mHeaderViewInfos.add(info);
}
 
/**
 * 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 (null != mAdapter) {
        mAdapter.unregisterDataSetObserver(mDataSetObserver);
    }
 
    resetList();
    mRecycler.clear();
 
    if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
        mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
    else {
        mAdapter = adapter;
    }
 
    //其它的一些代碼這裏省略之...
}

 

 

從代碼和註釋裏都可以很清楚的得知,addHeaderView一定要在setAdapter之前調用,如果不這樣做,addHeaderView會拋出一個異常。Android爲什麼要這樣?這是因爲,在setAdapter的時候,會針對我遇到的這種情況(也就是添加Header後position不正確的這種情況)做些特殊的處理。setAdapter在內部判斷了當前ListView是否有Header或者Footer,如果沒有,就直接使用參數傳進來的adapter;如果有,則用一個decorated的HeaderViewListAdapter來替換參數。這個HeaderViewListAdapter的使命,就是排除Header和Footer,讓position(當然也包括getItemgetItemId)等方法的position參數)正確返回。

分析到這裏,解決方案就出來了:在onItemClick不要直接使用我們聲明的adapter,而是用ListView裏的那個decorated adapter。獲取它的方法就是調用parent.getAdapter()。當然,如果ListView沒有Header和Footer,直接使用聲明的adapter也沒有問題,不過爲了避免出錯,還是統一使用decorated adapter比較好。

把onItemClick改成下面這樣,就可以了

1
2
3
4
@Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
    doSomething(parent.getAdapter().getItem(position));
}

 本文由Roy最初發表於:http://blog.chengbo.net/2012/03/09/onitemclick-return-wrong-position-when-listview-has-headerview.html,你可以在保持文章完整和保留本聲明的情況下轉帖、分發和印刷等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章