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复用出现以上的问题。
解决之后的效果图
点击下载源码
如有疑问欢迎留言