Android 解决ListView的复用问题

ListView是大家在项目的开发过程中不可避免要使用到的,使用ListView的同时我们还要使用到适配器,如果ListView只有一两条数据的话我们可能不会考虑到用ListView的复用机制,因为你用不用对象的创建和空间的开辟都是那么多。这样的话ListView复用出现的问题也就不存在了。然而很多应用展示的条目并不是那一两条数据,而是很多会多余一屏的显示,不然也就不会有加载更多的出现了。如果我们不使用ListView的复用机制的话会造成资源空间的浪费。其实我们的ListView的复用问题是一直存在的,只不过是在有的场景显示的比较明显而已。如果你的条目上面有点击发生变化的情况下,比如说,你的item上面有点击显示隐藏效果、星星的滑动效果、CheckBox的选择效果的时候这些复用的问题就会展现出来。关于ListView是如何实现回收复用的先看一张图片
这里写图片描述
通过上面的图片也许大家就明白的差不多了,ListView会默认的创建可见条目的实例,可见的有几个条目就会创建几个Item实例,这种情况是在ListView在布局文件中设置的高是充满屏幕的,如果设置高是包裹内容的话,可能就会出现不一样的效果了。不信的话可以通过打印日志的看看public View getView(int position, View convertView, ViewGroup parent)这个方法被多调用了一次,这是为什么呢?自定义控件的时候说过一个onMeasure测量的方法。这是因为当我们固定listview的高度时(match_parent或直接固定高度),那么ListView很容易就能计算出容器内可以显示多少行。但如果我们使用了“wrap_content”,只有在屏幕内控件完全加载后才知道到底能显示多少行数据时,ListView自身便会做一些尝试性计算。在源码中可以发现一些叫做onMeasure的方法在里面我们会看到这段代码

 if (heightMode == MeasureSpec.AT_MOST) {
            // TODO: after first layout we should maybe start at the first visible position, not 0
            heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
        }

MeasureSpec.AT_MOST这个模式在自定义控件里面说过wrap_content其实就是这个模式,after first layout we should maybe start at the first visible position, not 0这句话的意思我的理解是我们可见的第一个布局的位置不是以0开始的。就是说我们看到的默认的布局位置为0的其实是已经执行过一次初始化后,可以理解为我们看到的是第一条其实是第二条。如果不相信的话你可以把以下代码复制粘贴到你的工程里面试试

public class MainActivity extends Activity {
    private Context mContext;
    private ListView lv_test;
    private List<String>mTestList;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initData();
    }

    private void initData() {
        mTestList = new ArrayList<>();
        mTestList.add("我是用来测试的我的我的索引位置为");
        mTestList.add("我是用来测试的我的我的索引位置为");
        mTestList.add("我是用来测试的我的我的索引位置为");
        MyApadater apadater = new MyApadater();
        lv_test.setAdapter(apadater);
        apadater.notifyDataSetChanged();
    }
    private void initView() {
        mContext = MainActivity.this;
        lv_test = (ListView) findViewById(R.id.lv_test);
    }

   public class MyApadater extends BaseAdapter{

       @Override
       public int getCount() {
           return mTestList.size();
       }

       @Override
       public Object getItem(int position) {
           return getItem(position);
       }

       @Override
       public long getItemId(int position) {
           return position;
       }

       @Override
       public View getView(int position, View convertView, ViewGroup parent) {
           ViewHolder holder;
           if (convertView == null) {
               holder = new ViewHolder();
               convertView = LayoutInflater.from(mContext).inflate(R.layout.item_test,null);
               holder.tv_test = (TextView) convertView.findViewById(R.id.tv_test);
               System.out.println("我被执行了-------->");
               convertView.setTag(holder);
           } else {
               holder = (ViewHolder) convertView.getTag();
           }
           holder.tv_test.setText(mTestList.get(position)+position);
           return convertView;
       }
       class ViewHolder{
           TextView tv_test;
       }
   }
}

activity_main布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.lyxrobert.listview.MainActivity">

    <ListView
        android:id="@+id/lv_test"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:scrollbars="none" >
    </ListView>
</LinearLayout>

item_test布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
<TextView
    android:id="@+id/tv_test"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

</LinearLayout>

打印的日志如下
这里写图片描述

扯远了!上面只是一个小插曲。下面我们来看一看复用出现问题的效果图
这里写图片描述
这种情况的产生就是复用的原因,比如说一屏显示9条数据,那么就是创建9个对象,当地10条数据出来的时候第一条已经不可见了,这时候第10条数据所用的空间是第一条数据的(就好比我们去餐厅吃饭的时候,店家会给我们发个购餐牌,上面标有号码。假如说第一个人的牌号为0,店家就10个牌,那么当第11个人过来的时候怎么办?这个时候可能拥有1号牌的人员已经在吃了,那么那个一号牌就会有店家给11号来用),我们在使用号牌的时候号牌肯定会慢慢的变脏,但是这个脏的程度不是很明显而已,于是大家也就没有注意到。比如说当第一人在排队打饭的时候不小心在号牌上面洒了一些东西,店家也没有注意,也是到第11人使用的时候这个号牌的脏度就明显出来了。到最后店家也不知道是谁弄脏号牌的,但是他只知道是第一人还是第十一人或是其他使用者。其实我们做的再item进行显示隐藏、星星的滑动就好比在号牌上面洒了一些东西一样。你对item做了什么样的操作下面复用的都会延续下去,如果号牌不清洗就会一直脏下去。
那么这个问题如何解决呢?这个问题也好解决。如果店家细心一点,在给第11人的时候发现号牌脏了,这时候店家就知道是第一个人弄脏的,然后店家就会进行清洗再给第11个人。那么我们的ListView的复用怎么解决这个问题呢?这个时候我们就可以给Item做一些检查操作了,我们可以根据ListView的position来标记对象。这样做复用的效果还是有的,但是需要开辟更多的空间容纳更多的对象。就是我们中学阶段在餐厅吃饭一样,自备餐具,自己的餐具可以自己重复的使用。具体的实现代码如下
HashMap

if (mHashMap.get(position) == null) {
            holder = new ViewHolder();
            convertView = inflater.inflate(R.layout.softmanager_localapp_item,
                    null);

            mHashMap.put(position, convertView);
            convertView.setTag(holder);
        } else {
            convertView = mHashMap.get(position);
            holder = (ViewHolder) convertView.getTag();
        }

mHashMap.get(position)和convertView 一样, 第一次进来的时候是没有数据的。这样的话就可以解决ListView复用出现以上的问题。
解决之后的效果图
这里写图片描述

点击下载源码

如有疑问欢迎留言

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值