zoukankan      html  css  js  c++  java
  • 正确处理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.


  • 相关阅读:
    POJ3977 Subset 折半枚举
    Ubuntu和Win7双系统,ubuntu被删,重新启动之后显示,no such partition
    hdu 4296 贪心
    Python标准库:内置函数tuple([iterable])
    【python自制】让大白成为你的个人助手!
    Flex设置LinkButton的背景色
    VB6基本数据库应用(五):数据的查找与筛选
    正态分布(normal distribution)与偏态分布(skewed distribution)
    windows 系统文件 —— 特殊文件及文件类型
    windows 系统文件 —— 特殊文件及文件类型
  • 原文地址:https://www.cnblogs.com/zsw-1993/p/4879116.html
Copyright © 2011-2022 走看看