我在Adapter中使用holder时踩到的坑
说到这个坑,一切都源于万恶的用户需求,下面且听我娓娓道来。
最开始我们界面大概是这个样子滴~(以下图乃本人简画所得,真实设计比这个好很多滴)
要完成这个界面是不是so easy! 一个ListView,set一个Adapter,Adapter里对应绑定一个layout,完成啦~然后我就可以高高兴兴领盒饭啦~
BUT!
用户说,我们要求复制text的文字,我们要求在text旁边加一个按钮!
SO…我们的界面变成下面这个样子
哎呀,这也简单嘛,无非就是在layout里面多加个button嘛对不对~所以我就愉快的在layout里加上了这个胖胖的button,然后如往常一样,给button绑定一个onClickListener处理复制事件,收工!
然后我信心满满的点了一下Text 1旁边的copy按钮,随便打开一个可以粘贴的地方,粘贴!然后神奇的事情发生了…
妈蛋粘到这里的不是Text 1而是Text 7 !!!!然而我并不相信!于是我又复制了Text 2,粘贴,我擦为什么是Text 8?!我又继续回去实验,复制Text 4的内容,粘贴,怎么又是Text 7 ?!千千万万只草泥马在我内心翱翔啊~~~~我开始怀疑是什么地方出了问题,于是我回归了代码。
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
this.position = position;
if (view == null) {
view = LayoutInflater.from(getBaseContext()).inflate(R.layout.item_layout, parent, false);
holder = new Holder();
holder.t1 = (TextView) view.findViewById(R.id.t1);
holder.t2 = (TextView) view.findViewById(R.id.t2);
holder.t3 = (TextView) view.findViewById(R.id.t3);
holder.btn1 = (Button) view.findViewById(R.id.btn1);
holder.btn2 = (Button) view.findViewById(R.id.btn2);
holder.btn3 = (Button) view.findViewById(R.id.btn3);
view.setTag(holder);
} else {
holder = (Holder) view.getTag();
}
holder.btn1.setOnClickListener(this);
holder.btn2.setOnClickListener(this);
holder.btn3.setOnClickListener(this);
holder.t1.setText(copies.get(position).str1);
holder.t2.setText(copies.get(position).str2);
holder.t3.setText(copies.get(position).str3);
return view;
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn1:
copy(copies.get(position).str1);
break;
case R.id.btn2:
copy(copies.get(position).str2);
break;
case R.id.btn3:
copy(copies.get(position).str3);
break;
}
}
看起来似乎没啥问题对不对… 等等,是不是复用视图搞出的鬼?于是上网查了相关资料,发现了原来问题果然出在这里:
ListView只会缓存第一屏里面的List Item的视图布局,之后滚动ListView后面的Item的布局就都是用前面所缓存的视图布局(也就是convertView不为null)。
当超出第一屏的item被滑出来则会重新更新视图,所以此时的position会被赋为未滑动前被隐藏的item的position,进而取出缓存的视图进行更新。
因此只要保证position正确我们就成功了~
所以我总结了三种方法可以进行修改:
- 方法1 用setTag方法记录position (别废话上代码(╰_╯)#)
@Override
public View getView(int position, View convertView, ViewGroup parent) {
...
holder.btn1.setTag(position);
holder.btn2.setTag(position);
holder.btn3.setTag(position);
return view;
}
@Override
public void onClick(View v) {
//这里要强转为Integer哦
int position = (Integer) v.getTag();
switch (v.getId()) {
case R.id.btn1:
copy(copies.get(position).str1);
break;
case R.id.btn2:
copy(copies.get(position).str2);
break;
case R.id.btn3:
copy(copies.get(position).str3);
break;
}
}
好,下面介绍第二种方法,这个方法简单粗暴~
- 方法2 用setTag方法将对应的String设置进去
@Override
public View getView(int position, View convertView, ViewGroup parent) {
...
holder.btn1.setTag(copies.get(position).str1);
holder.btn2.setTag(copies.get(position).str2);
holder.btn3.setTag(copies.get(position).str3);
return view;
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn1:
case R.id.btn2:
case R.id.btn3:
copy((String) v.getTag());
break;
}
}
完成啦~是不是炒鸡简单~
- 方法3 略微麻烦
下面是最后一种方法啦~当当当,那就是自定义一个类实现OnClickListener接口,重写onClick方法,并且类里增加一个setPosition方法巴拉巴拉~具体看代码
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
this.position = position;
OnCopyClickListener onCopyClickListener;
if (view == null) {
...
onCopyClickListener = new OnCopyClickListener();
holder.btn1.setOnClickListener(onCopyClickListener);
view.setTag(holder.btn1.getId(), onCopyClickListener);
...
} else {
holder = (Holder) view.getTag();
onCopyClickListener = (OnCopyClickListener) view.getTag(holder.btn1.getId());
}
...
onCopyClickListener.setPosition(position);
return view;
}
class OnCopyClickListener implements View.OnClickListener {
int position;
public void setPosition(int position) {
this.position = position;
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn1:
copy(copies.get(position).str1);
break;
...
}
}
}
这是网友给出的方法,略复杂,但当时楼主居然用这么复杂的方法实现了,也是脑子瓦塔啦,大家也就看看就可以啦~~
细心的朋友会说了:你这不是都用的setTag实现的吗!有什么区别啊!你在逗我们啊!
哈哈哈确实,虽然楼主提供了三种方法,其实核心实现都是靠setTag方法,但是setTag的确是复用机制的好伴侣啊哈哈哈~~~
好啦,本篇博客就到这里啦~ 概括来说这篇主要是讲楼主在项目中踩到的holder的坑,为避免大家再次踩坑所以写了这篇文章,不足之处还请大家多多指教啦~~~