问题描述
话说,我接了坑同事的代码,据说这是同事接的前同事的代码…..不管怎么说,遇到了一个bug,很容易解决,但原理一直搞不明白,于是网络搜索,没搜到结果,但发现了一个类似却又不同的问题,写代码重现了之后,准备记录与此。
在使用ListView时,不可避免地要为它设置adapter,并且要为adapter设置数据,那么就很容易出现该问题,先写个错误代码,大家high一下。
问题代码
布局不贴了,下面会贴一张代码的运行图,一看便知。先来看看activity的onCreate方法,如下
ListView mListView;
ArrayAdapter<String> adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_adapter_bug);
findViewById(R.id.tv_type_1).setOnClickListener(this);
findViewById(R.id.tv_type_2).setOnClickListener(this);
findViewById(R.id.tv_type_refresh).setOnClickListener(this);
mListView = (ListView) findViewById(R.id.lv_adapter_bug);
getData(1);
adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, mDatas);
mListView.setAdapter(adapter);
}
很简单,有没有,很单纯地为ListView设置了一个adapter,其中涉及到了一个方法,getData(),如下
List<String> mDatas = new ArrayList<>();
private void getData(int type) {
mDatas = new ArrayList<>();
for (int i = 0; i < 10; i++) {
if (type == 1) {
mDatas.add("--------aaaaaaa" + i);
} else {
mDatas.add("--------bbbbbbb" + i);
}
}
}
还是很简单,对不,我们再来看点击事件
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.tv_type_1:
getData(1);
break;
case R.id.tv_type_2:
getData(2);
break;
case R.id.tv_type_refresh:
adapter.notifyDataSetChanged();
break;
}
}
就是这个样子,貌似没啥问题,是不,好吧,我们来看下运行效果,
我去,你到底干了啥,为啥没啥变化,你操作了吗?脑袋中有没有一系列问号,来来来,我们看个正确的运行结果。
嗯,貌似看出点不对了,那么,为什么我调用了notifyDataSetChanged()方法,却没有刷新数据呢?
别着急,听我慢慢道来。
问题解析
假设,程序的onCreate()方法已经运行过,之后,我们按步骤来分析
第一步:点击type=2的按钮
这个方法做了啥,就调用了getData(2)方法,我们来看看getData()方法的关键代码,也就是第一句,
List<String> mDatas = new ArrayList<>();
private void getData(int type) {
mDatas = new ArrayList<>();
...
}
我们重新创建了一个对象,将它指向名为mDatas的引用,然后就是为集合添加元素。
第一步完成。第二步走起!
第二步:点击中间的按钮,刷新,代码很简单就是一句
adapter.notifyDataSetChanged();
结束了,没毛病啊,我点击刷新了,你为毛不给我刷啊?!!
年轻人,别冲动,电脑是不会骗人的,我们来分析一下adapter的构造方法,在onCreate方法中
adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, mDatas);
最后一个参数,将mDatas传进去了,之后ListView的源码会调用adapter的方法,获取数据,设置数据,都是mDatas这个对象。
mDatas这个对象!!!
看到没,问题就在这里,调用的是名为mDatas的对象。当我们点击type=2的按钮时,重新创建了一个对象,指向了mDatas,也就是说,现在另一个对象叫mDatas了,而传入了adapter中的那个集合对象,现在没有名字了,但是,它还在内存中,并没有消失,仅仅是没有名字罢了,匿名啊,兄弟!
所以,任凭你怎么调用adapter的notifyDataSetChanged()方法也没什么用。
问题解决
原因搞清楚了,解决就很简单了。而且方式有很多,比如我们改下getData()方法
List<String> mDatas = new ArrayList<>();
private void getData(int type) {
// mDatas = new ArrayList<>();
mDatas.clear();
for (int i = 0; i < 10; i++) {
if (type == 1) {
mDatas.add("--------aaaaaaa" + i);
} else {
mDatas.add("--------bbbbbbb" + i);
}
}
}
不再重建mDatas,每次清空集合即可。
下面是另一种,在点击事件中
adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, mDatas);
mListView.setAdapter(adapter);
// adapter.notifyDataSetChanged();
还有第三种方法,不过在这个demo中无法使用,比如你使用的是BaseAdapter的子类,那么就可以添加一个方法,setData(),具体如下
class Adapter extends BaseAdapter{
Context context;
List<String> list;
public Adapter(Context context,List<String> list){
this.context = context;
this.list = list;
}
public void setData(List<String> list){
this.list = list;
notifyDataSetChanged();
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = LayoutInflater.from(context).inflate(android.R.layout.simple_list_item_1, null, false);
TextView textView = (TextView) view.findViewById(android.R.id.text1);
String text = list.get(position);
textView.setText(text);
return view;
}
}
在点击事件中,调用方法如下
adapter.setData(mDatas);
之所以写出这种写法,是之前的同事很多都用这种写法,自己虽然不习惯这种写法,但还是写出来,供大家参考一下。
总结
简单一句话,这个问题不是移动端问题(我实在受不了红色的那个啥,所以用移动端代替),是个对象问题…