當ListView有Header時,onItemClick裡的position不正確
今天在做專案的時候,遇到一個問題,記錄下來。
當給ListView加了一個HeaderView後(程式碼如下),我們發現,onItemClick方法裡的position引數的值不是我們所期望的,比如點選ListView的第一行,我們期望的position是0,可是實際上卻是1,也就是說,它是從Header而不是從第一行開始計數的。
@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的程式碼,終於領會他的意思了。把其中addHeaderView和setAdapter方法貼下來
/**
* 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(當然也包括getItem, getItemId)等方法的position引數)正確返回。
分析到這裡,解決方案就出來了:在onItemClick不要直接使用我們宣告的adapter,而是用ListView裡的那個decorated adapter。獲取它的方法就是呼叫parent.getAdapter()。當然,如果ListView沒有Header和Footer,直接使用宣告的adapter也沒有問題,不過為了避免出錯,還是統一使用decorated adapter比較好。
把onItemClick改成下面這樣,就可以了
@Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
doSomething(parent.getAdapter().getItem(position));
}