Android问题集(2):notifyDataSetChanged()方法无用

问题描述

话说,我接了坑同事的代码,据说这是同事接的前同事的代码…..不管怎么说,遇到了一个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);

之所以写出这种写法,是之前的同事很多都用这种写法,自己虽然不习惯这种写法,但还是写出来,供大家参考一下。

总结

简单一句话,这个问题不是移动端问题(我实在受不了红色的那个啥,所以用移动端代替),是个对象问题…

版权声明:本文为u013673799原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u013673799/article/details/71105215