当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的代码。最终领会他的意思了。

把当中addHeaderViewsetAdapter方法贴下来

/**
 * 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改成以下这样,就能够了

@Override
public void onItemClick(AdapterView<?

> parent, View v, int position, long id) { doSomething(parent.getAdapter().getItem(position)); //或者 doSomething( parent.getItemAtPosition(position) ); }

错误使用方法一:
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
   String item = (String) mDataList1.get(position);
}

错误使用方法二:
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
   String item = (String) mAdapter.getItem(position);
}
posted @ 2017-08-03 09:19  jzdwajue  阅读(287)  评论(0编辑  收藏  举报