在说 setTag和getTag之前,我们先说下 Viewholder,它不是Android开发固定的API,而是谷歌Demo中推荐的设计方法。Viewholder对象它一般包括listview子项里所有的组件,convertView是空的,在Viewholder里存储对列表子项每个组件的id应用,通过setTag方法,把这个带有view引用的对象附加在View上,如此,当listView更新的时候,就不用再次去重复寻找引用,并且强制转换等工作,findViewById(R.id.img);通过getTag直接从view携带的Viewholder中取出每个组件的引用。
那么 setTag和 getTag的作用就很明显了,tag是标签的意思,但是在这里它不单单是作为view的标签,从本质上说,它是附加在view上面的任意数据,setTag(Object obj) 形参是obj,任意对象。而getTag则更好理解了,既然setTag把数据附加上去,自然得有方法把它取下来再次使用。
请看代码——
用setTag和 getTag ,ViewHolder的代码
ViewHolder holder; if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item_icon_text,
null);
holder = new ViewHolder();
holder.icon1 = (ImageView) convertView.findViewById(R.id.icon1);
holder.text1 = (TextView) convertView.findViewById(R.id.text1);
holder.icon2 = (ImageView) convertView.findViewById(R.id.icon2);
holder.text2 = (TextView) convertView.findViewById(R.id.text2);
convertView.setTag(holder);
}
else{
holder = (ViewHolder)convertView.getTag();
}
holder.icon1.setImageResource(R.drawable.icon);
holder.text1.setText(mData[position]);
holder.icon2 .setImageResource(R.drawable.icon);
holder.text2.setText(mData[position]);
}
static class ViewHolder {
TextView text1;
ImageView icon1;
TextView text2;
ImageView icon2;
}
没用setTag,getTag 和 ViewHolder的时候
if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item_icon_text,
null);
}
((ImageView) convertView.findViewById(R.id.icon1)).setImageResource(R.drawable.icon);
((TextView) convertView.findViewById(R.id.text1)).setText(mData[position]);
((ImageView) convertView.findViewById(R.id.icon2)).setImageResource(R.drawable.icon);
((TextView) convertView.findViewById(R.id.text2)).setText(mData[position]);
}
当Android第一次加载第一页listView的时候,convertView是空的,因此通过
mInflater.inflate(R.layout.list_item_icon_text,
null);
这行代码实例化了一个 view给 convertView,然后加载数据,每一个组件(这里的是ImageView和TextView)都需要通过 findViewById这个方法去寻找视图控件。加载第一页的时候做一次这样的工作尚可忍受,然而,当加载第二页的时候, findViewById这个方法依然被调用,依次以后每次加载都如此。须知道,从xml中去寻找控件引用并加载时一件比较耗内存的事情,你每次都做这件事情,后果更甚。
因此,我定义了一个静态的 Viewholder类,放在静态存储区,并把组件引用通过setTag方法附加在View上面,当加载第二页的时候,你就不用再次去findViewById了,直接用getTag方法来取出数据引用即可。
这种方法叫做视图缓存。这也是2009年谷歌IO大会给出的优化建议。
如果你对这种方法存疑,有网友已经就这两种做法,还有另外一种,分别进行了数据测试,实测证明了,这个视图缓存方法的确是达到了优化的效果的。详情看此文章。
视图缓存的缺点——
应该注意到,这是一种以空间换时间的做法,因为增加了一个静态类,来达到减少 findViewById的次数。此外,setTag这样附加数据到view中会让view增加额外的负担,这样就造成了设置tag的view和不设置tag的view大小是不一样,只不过,我们一直都习惯了别人告诉我们这么做,没去比较而已。
网友农民伯伯通过反编译新浪微博而得到另外一种做法,就是用自定义的类(继承自RelativeLayout或其他容器),然后在这个里面直接把子元素findViewById后放到成员变量再暴露出来。
做法代码如下—— 出自农民伯伯博客
public View getView(int position, View convertView, ViewGroup parent) {
// 开始计时
long startTime = System.nanoTime();
TestItemLayout item;
if (convertView == null) {
item = new TestItemLayout(BaseAdapterActivity.this);
} else
item = (TestItemLayout) convertView;
item.icon1.setImageResource(R.drawable.icon);
item.text1.setText(mData[position]);
item.icon2.setImageResource(R.drawable.icon);
item.text2.setText(mData[position]);
// 停止计时
long endTime = System.nanoTime();
// 计算耗时
long val = (endTime - startTime) / 1000L;
Log.e("Test", "Position:" + position + ":" + val);
if (count < 100) {
if (val < 2000L) {
sum += val;
count++;
}
} else
mTV.setText(String.valueOf(sum / 100L) + ":" + nullcount);// 显示统计结果
return item;
}
TestItemLayout
public class TestItemLayout extends LinearLayout {
public TextView text1;
public ImageView icon1;
public TextView text2;
public ImageView icon2;
public TestItemLayout(Context context) {
super(context);
((LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(
R.layout.list_item_icon_text, this);
icon1 = (ImageView) findViewById(R.id.icon1);
text1 = (TextView) findViewById(R.id.text1);
icon2 = (ImageView) findViewById(R.id.icon2);
text2 = (TextView) findViewById(R.id.text2);
}
}
个人理解:这种做法是不是用了反射的思想呢?当加载第二页的时候, item = (TestItemLayout) convertView; 把convertView强制类型转换成 自定义的布局容器,其中就能把每个组件的引用给转换出来? 求解释。
农民伯伯最后的验证结果是跟 使用视图缓存效果差不多,但是空间上优化了,并且由于没有使用Tag,view也不会变小了。