关于Adapter的notifyDataSetChanged()方法数据不更新问题解析
2017-04-06 10:52
531 查看
概述
做安卓开发的同学应该大多都经历过adapter中在调用了notifyDataSetChanged()方法之后数据不更新的问题,作为菜鸟的我也同样踩过坑,现在写这篇文章作为总结。正文
话不多说,上代码!首先是Activity的布局,两个按钮,代表两种加载数据的方式,然后一个ListView。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.example.indicatedemo.MainActivity"> <Button android:id="@+id/FirstWay" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="方法一" /> <Button android:id="@+id/SecondWay" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="方法二" /> <Button android:id="@+id/ThirdWay" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="方法三" /> <ListView android:id="@+id/listView" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
接下来是模拟数据实体类Person。
public class Person { private String name;// 姓名 private int age;// 年龄 private boolean isMan;// 是男生吗 public Person(String name, int age, boolean isMan) { this.name = name; this.age = age; this.isMan = isMan; } public boolean isMan() { return isMan; } public void setMan(boolean man) { isMan = man; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
然后一个再正常不过的Adapter。
public class MyAdapter extends BaseAdapter { private static final String TAG = "MyAdapter"; private List<Person> data; private LayoutInflater inflater; public MyAdapter(Context context) { inflater = LayoutInflater.from(context); } public void setData(List<Person> data) { this.data = data; } @Override public int getCount() { return data == null ? 0 : data.size(); } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return 0; } @Override public View getView(final int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if (null == convertView) { holder = new ViewHolder(); convertView = inflater.inflate(R.layout.item, null); holder.isMan = (TextView) convertView.findViewById(R.id.isMan); holder.name = (TextView) convertView.findViewById(R.id.name); holder.age = (TextView) convertView.findViewById(R.id.age); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.name.setText(data.get(position).getName() + ""); holder.age.setText(data.get(position).getAge() + ""); holder.isMan.setText(data.get(position).isMan() + ""); Log.i(TAG, data.get(position).getName() + "|" + data.get(position).getAge() + "|" + data.get(position).isMan()); return convertView; } private final class ViewHolder { private TextView isMan; private TextView name; private TextView age; } }
然后是ListView里面的子布局。
<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/name" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/age" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/isMan" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
值得一提的是,ListView中子布局最外层的宽度和高度是不受控制的,无论将这两个值设为多少都是无效的,原因是这样的:要设置一个View的大小,要求这个View必须存在于一个布局中,那么大家可能会问,我平时用的Activity中的布局可以怎么随意设置大小呢,因为在Activity加载的时候就已经设置好了一个FramLayout,我们的setContentView方法只是把一个布局加载到这个FramLayout中,有兴趣的同学可以去研究一下LayoutInflat加载布局的流程。
接下来是我们的Activity的代码
public class MainActivity extends AppCompatActivity implements View.OnClickListener { private Button firstWayBt; private Button secondWayBt; private Button thirdWayBt; private ListView listView; private MyAdapter adapter; private List<Person> data; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initUI(); bindData(); } private void initUI() { firstWayBt = (Button) findViewById(R.id.FirstWay); secondWayBt = (Button) findViewById(R.id.SecondWay); thirdWayBt = (Button) findViewById(R.id.ThirdWay); listView = (ListView) findViewById(R.id.listView); } private void bindData() { data = initData(); adapter = new MyAdapter(this); adapter.setData(data); listView.setAdapter(adapter); firstWayBt.setOnClickListener(this); secondWayBt.setOnClickListener(this); thirdWayBt.setOnClickListener(this); } private List<Person> initData() { List<Person> personList = new ArrayList<>(); Person p1 = new Person("1号", 10, true); Person p2 = new Person("2号", 20, true); Person p3 = new Person("3号", 30, true); Person p4 = new Person("4号", 40, true); personList.add(p1); personList.add(p2); personList.add(p3); personList.add(p4); return personList; } private List<Person> getNewData() { List<Person> personList = new ArrayList<>(); Person p1 = new Person("5号", 50, false); Person p2 = new Person("6号", 60, false); Person p3 = new Person("7号", 70, false); Person p4 = new Person("8号", 80, false); personList.add(p1); personList.add(p2); personList.add(p3); personList.add(p4); return personList; } @Override public void onClick(View v) { switch (v.getId()) { case R.id.FirstWay: adapter.setData(getNewData()); break; case R.id.SecondWay: data = getNewData(); break; case R.id.ThirdWay: adapter.setData(data = getNewData()); break; } adapter.notifyDataSetChanged(); } }
也是很简单的加载UI然后设置数据到ListView的流程。这里我写了三种更换数据的方式:
1. 第一种:FirstWay设置数据的方式就类似于,在网络请求拿到数据之后直接设置到Adapter中然后调用notifyDataSetChanged()方法。
2. 第二种:SecondWay设置数据的方法就是先给Adapter一个默认的数据data,然后网络请求拿到新的数据之后先赋值给原先的老数据data,再调用notifyDataSetChanged()方法刷新数据。
3. 第三种:ThirdWay设置数据的方法和第一种其实本质上是相同的,但是读起来逻辑是有点不同的,先给Adapter设置一个默认的data,然后通过网络请求获取到数据后替换到原来data的数据在设置给Adapter,在调用notifyDataSetChanged()方法。
默认打印的日志是这样的:
1号|10|true 2号|20|true 3号|30|true 4号|40|true
分别运行三种加载数据的方式,打印出来的日志是这样的:
第一种:
5号|50|false 6号|60|false 7号|70|false 8号|80|false
第二种:
1号|10|true 2号|20|true 3号|30|true 4号|40|true
第三种:
5号|50|false 6号|60|false 7号|70|false 8号|80|false
从日志可以看出来第二种方法的数据是没有更新的,既然数据没更新当然就不会刷新啦。所以使用Adapter设置数据时要注意数据源data的使用,防止出现使用notifyDataSetChange()方法数据不更新的错误。
然后我们再来看一下另一种情况!!!
让我们修改一下Activity的布局文件,很简单,去掉第三个按钮
<Button android:id="@+id/FirstWay" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="方法一" /> <Button android:id="@+id/SecondWay" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="方法二" /> <ListView android:id="@+id/listView" android:layout_width="wrap_content" android:layout_height="wrap_content" />
然后为我们的Person类添加一个print()方法用来打印数据,这里直接用toString()方法,因为。。。直接用toString()方法的话会影响后面代码打印内存地址。。。
public String print() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", isMan=" + isMan + '}'; }
修改一下Activity中的代码,如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener { private Button firstWayBt; private Button secondWayBt; private ListView listView; private MyAdapter adapter; private List<Person> data; private static final String TAG = "MyMainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initUI(); bindData(); } private void initUI() { firstWayBt = (Button) findViewById(R.id.FirstWay); secondWayBt = (Button) findViewById(R.id.SecondWay); listView = (ListView) findViewById(R.id.listView); } private void bindData() { data = initData(); Log.i(TAG, "第一次内存地址:" + data); adapter = new MyAdapter(this); adapter.setData(data); listView.setAdapter(adapter); firstWayBt.setOnClickListener(this); secondWayBt.setOnClickListener(this); } private List<Person> initData() { List<Person> personList = new ArrayList<>(); Person p1 = new Person("1号", 10, true); Person p2 = new Person("2号", 20, true); Person p3 = new Person("3号", 30, true); Person p4 = new Person("4号", 40, true); personList.add(p1); personList.add(p2); personList.add(p3); personList.add(p4); return personList; } private void getNewData1() { List<Person> personList = new ArrayList<>(); Person p1 = new Person("100号", 100, false); Person p2 = new Person("100号", 100, false); Person p3 = new Person("100号", 100, false); Person p4 = new Person("100号", 100, false); personList.add(p1); personList.add(p2); personList.add(p3); personList.add(p4); data = personList; } private void getNewData2() { for (Person person : data) { person.setName("100号"); person.setAge(100); person.setMan(false); } } @Override public void onClick(View v) { switch (v.getId()) { case R.id.FirstWay: getNewData1(); break; case R.id.SecondWay: getNewData2(); break; } printAddress(); adapter.notifyDataSetChanged(); } private void printAddress() { Log.i(TAG, "更新后的data内存地址:" + data); for (Person person : data) { Log.i(TAG, person.print()); } } }
修改了加载数据的方法,为了方便大家理解,在几个关键的地方输出一下日志。
private void bindData() { data = initData(); Log.i(TAG, "第一次内存地址:" + data); // 。。。省略代码 }
在第一次加载数据的地方打印内存地址。
private void getNewData1() { List<Person> personList = new ArrayList<>(); Person p1 = new Person("100号", 100, false); Person p2 = new Person("100号", 100, false); Person p3 = new Person("100号", 100, false); Person p4 = new Person("100号", 100, false); personList.add(p1); personList.add(p2); personList.add(p3); personList.add(p4); data = personList; }
然后第一种加载数据的方式,注意这里是new了一个新的ArrayList然后赋值给data,相当于data换了一个对象。
private void getNewData2() { for (Person person : data) { person.setName("100号"); person.setAge(100); person.setMan(false); } }
第二种加载数据的方式,这里并没有像第一种方式那样new一个新的ArrayList然后赋值,而是直接修改原data中的数据。
@Override public void onClick(View v) { switch (v.getId()) { case R.id.FirstWay: getNewData1(); break; case R.id.SecondWay: getNewData2(); break; } printAddress(); adapter.notifyDataSetChanged(); } private void printAddress() { Log.i(TAG, "更新后的data内存地址:" + data); for (Person person : data) { Log.i(TAG, person.print()); } }
在点击按钮后增加了一个data内存地址和数据的方法。
我们首次加载Activity输出日志如下:
第一次内存地址:
[com.example.indicatedemo.Person@1d07ca95, com.example.indicatedemo.Person@48d7aa, com.example.indicatedemo.Person@835cf9b, com.example.indicatedemo.Person@110ccf38]
点击方法一按钮之后输出日志如下:
更新后的data内存地址:
[com.example.indicatedemo.Person@1b3ceeac, com.example.indicatedemo.Person@2eb23775, com.example.indicatedemo.Person@845730a, com.example.indicatedemo.Person@325b377b Person{name='100号', age=100, isMan=false} Person{name='100号', age=100, isMan=false} Person{name='100号', age=100, isMan=false} Person{name='100号', age=100, isMan=false}]
可以很明显发现:data的内存地址已经更改,已经不是原来的对象了,虽然看上去数据是变成了加载后的数据,但是已经不是原来的data了,然后可以看到界面并没有更新。
然后我们再试一下第二种方式(注意要重新进入软件才能保证地址是对的),输出日志如下:
第一次内存地址:
[com.example.indicatedemo.Person@1d07ca95, com.example.indicatedemo.Person@48d7aa, com.example.indicatedemo.Person@835cf9b, com.example.indicatedemo.Person@110ccf38]
更新后的data内存地址:
[com.example.indicatedemo.Person@1d07ca95, com.example.indicatedemo.Person@48d7aa, com.example.indicatedemo.Person@835cf9b, com.example.indicatedemo.Person@110ccf38] Person{name='100号', age=100, isMan=false} Person{name='100号', age=100, isMan=false} Person{name='100号', age=100, isMan=false} Person{name='100号', age=100, isMan=false}
可以看到内存地址是相同的,说明还是同一个对象,所以!界面改变了!
总结
首次进入activity,我们adapter的数据源data指向一个内存地址,可以理解为adapte中的数据直接指向的是这个内存地址,然后通过getNewData1()方法加载数据,此时的data已经不是原来的data了,而是另外一个新的对象,指向了一个新的内存地址!然而adapter还是指向原来的内存地址,所以,用notifyDataSetChanged()方法刷新的是原来内存地址中的数据,发现压根就没变啊,所以视图根本就不会更新。然而我们通过getNewData2()方法,只是修改了数据,并不是新的对象,所有在用notifyDataSetChanged()方法刷新数据视图当然会改变啦!听上去很复杂的样子,其实理解一下就是个内存指向的问题,自己特此记录一下
相关文章推荐
- 关于adapter的数据更新问题
- 关于listitem点击事件根据position取得数据库数据Onresume()更新Adapter后取得数据错误的问题
- 关于BaseAdapter的notifyDataSetChanged()方法无法更新list数据的研究
- 关于使用dataAdapter.acceptChanges( )方法更新dataSet和数据库的问题
- 关于web页面缓存问题解决方法,如图片缓存,异步提交数据页面不更新
- ThinkPHP 关于用create方法实现数据更新的问题
- 关于CListCtrl控件更新Item的闪烁问题和一次插入大容量数据的显示问题解决办法
- 解决列表框更新数据的时候的闪烁问题(VC防止窗口及其控件(如CListCtrl)闪烁的简单方法(一组有用的宏) )
- 关于管道的大量数据传输问题解决方法 ( vc )
- 关于Silverlight对匿名类型数据绑定的问题及其解决方法
- 关于EF4.1更新数据后的显示问题-----PagedList
- 关于extjs中的tabpanel的刷新等若干问题,解决tabpanel内页面刷新,更新数据等问题。
- 关于JQuery的clone方法无法拷贝data缓存数据的问题
- linq to sql统一更新方法,直接返回更新的对象(解决更新后再刷新数据错误显示问题)
- 关于EF4.1更新数据后的显示问题-----PagedList
- 关于大批量数据上传和更新的方法
- 关于asp.net网站发布后,使用登录控件和注册控件时出现“数据库只读,无法进行数据更新”的解决方法
- 关于Silverlight对匿名类型数据绑定的问题及其解决方法
- 关于2147217913 从 char 数据类型到 datetime 数据类型的转换导致 datetime 值越界 的问题解决方法
- 关于ADO直连数据库报无法更新错误的解决方法