当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.


正确处理listview的position的更多相关文章

  1. ListView.setSelection(position)不起作用

    选择同事列表页面,在Adapter里设置复选框背景时调用了notifyDataSetChanged(),阻碍了UI线程,因此在设置ListView.setSelection(position)时不起作 ...

  2. ListView OnItemClickListener position 索引不正确

    在使用ListView添加如下代码时 listview.setOnItemClickListener(new OnItemClickListener() { @Override public void ...

  3. ListView 的position和id的区别

    我们在使用ListView的时候,一般都会为ListView添加一个响应事件android.widget.AdapterView.OnItemClickListener.本文主要在于对OnItemCl ...

  4. 【转】android中ListView的定位:使用setSelectionFromTop实现ListView的position的保持

    如果一个ListView太长,有时我们希望ListView在从其他界面返回的时候能够恢复上次查看的位置,这就涉及到ListView的定位问题: 解决的办法如下: 1 2 3 4 5 6 7 // 保存 ...

  5. ListView的position的保持

    需求场景: 一个ListView页面,滑动阅读到某一位置,然后退出页面,下次再进入页面的时候,想要直接滑动到上次阅读的位置. 方案1: 页面退出的时候,ListView.getFirstVisible ...

  6. ScrollView与ListView合用(正确计算Listview的高度)的问题解决

    最近做项目中用到ScrollView和ListView一起使用的问题,显示的时候ListView不能完全正确的显示,查了好多资料终于成功解决:   首先,ListView不能直接用,要自定义一个,然后 ...

  7. listview 模仿用户点击事件。

    正确的方法 gvFlow.post(new Runnable() { @Override public void run() { gvFlow.performItemClick(gvFlow.getC ...

  8. listview优化 汇总

    1,listview加载性能优化ViewHolder 转自: http://blog.csdn.net/jacman/article/details/7087995 在android开发中Listvi ...

  9. ListView原理

    表明转载自http://blog.csdn.net/iispring/article/details/50967445 在自己定义Adapter时,我们经常会重写Adapter的getView方法,该 ...

随机推荐

  1. Django配置websocket请求接口

    1.settings.py INSTALLED_APPS = [ '...', 'channels', '...', ] ASGI_APPLICATION = 'server.routing.appl ...

  2. 在浏览器输入URL回车后发生了什么?

    本文由 简悦 SimpRead 转码, 原文地址 https://4ark.me/post/b6c7c0a2.html 这个问题已经是老生常谈了,更是经常被作为面试的压轴题出现,网上也有很多文章,但最 ...

  3. Android NDK 学习之Application.mk

    Application.mk file syntax specification Introduction: This document describes the syntax of Applica ...

  4. MongoDB 无法启动

    提示 mongodb Mongod complains that there is no /data/db folder!! 解决方法:http://stackoverflow.com/questio ...

  5. linux7 上安装mongodb4.2.1操作步骤

    MongoDB是一个通用的.基于文档的分布式数据库,它是为现代应用程序开发人员和云时代而构建的.没有数据库能让你更有效率. 1.下载需要的软件包https://www.mongodb.com/down ...

  6. c# 虚属性

  7. Linux之mysql的安装与,主从设置

    一,MYSQL(mariadb) MariaDB数据库管理系统是MySQL的一个分支,主要由开源社区在维护,采用GPL授权许可. 开发这个分支的原因之一是:甲骨文公司收购了MySQL后,有将MySQL ...

  8. kubernetes集群初始化(二)

    参考地址:https://github.com/unixhot/salt-kubernetes 一.系统初始化 1.1.安装docker 所有节点都安装docker,设置docker国内yum源 [r ...

  9. linux网络编程之posix线程(一)

    今天继续学习posix IPC相关的东东,消息队列和共享内存已经学习过,接下来学习线程相关的知识,下面开始: [注意]:创建失败这时会返回错误码,而通常函数创建失败都会返回-1,然后错误码会保存在er ...

  10. Entity Framework的一个坑

    由于业务需要写了一个批量数据导入工具.中间踩了一个坑 问: 1. SaveChange 实体A 发生pk冲突,异常了.2.记录日志3.不让退出程序,继续处理下一个实体4.Add新的实体B5.再次调用S ...