(原创)你还在用notifyDataSetChanged? [复制链接]

2012-8-22 17:02
X-viking 阅读:27697 评论:16 赞:25
 想到发这篇帖子是源于我的上一篇帖子#Testin杯#多线程断点续传后台下载 。
帖子中讲述的项目使用了listView这个控件,而且自定义了adapter。在更新item的进度条时发现每次使用notifyDataSetChanged(),都会去调用自定义adapter中的getView方法。这时问题就出现了,用notifyDataSetChanged方法去更新listView中的item,是更新需要更新的Item呢?还是更新所有的item呢?如果是更新所有的item那么效率不就会很低吗?有什么办法可以解决这个问题呢?
  怀着心中的疑惑,我开始了这次的实验。。。
  我的想法很简单现实模拟远程下载文件,创建一个Activity做主界面,主界面采用listView。然后自定义一个adapter实现BaseAdapter,再创建一个线程类,线程类当中采用循环的方式不断的往adapter发送消息.然后使用notifyDataSetChanged方法更新界面,在调用getView方法时在控制台输出语句,这样我就可以知道notifyDatatSetChanged方法执行时是更新一个item还是更新所有的item了。
  有了思路就好办了,我们先建立一个类,叫FileState。
  1. package edu.notify.viking.entity;

  2. public class FileState 
  3. {
  4. String fileName;//文件名字
  5. int completeSize;//完成的长度
  6. boolean state;//文件状态,true为已经完成,false为未完成
  7. public String getFileName() {
  8. return fileName;
  9. }
  10. public void setFileName(String fileName) {
  11. this.fileName = fileName;
  12. }
  13. public int getCompleteSize() {
  14. return completeSize;
  15. }
  16. public void setCompleteSize(int completeSize) {
  17. this.completeSize = completeSize;
  18. }
  19. public boolean isState() {
  20. return state;
  21. }
  22. public void setState(boolean state) {
  23. this.state = state;
  24. }

  25. }
复制代码
这个类中有3个属性,分别是文件名字,文件已经下载的长度,还有文件当前的状态。然后就是get与set方法。这个类的作用我想大家应该一眼就明白了,没错,既然使用了listView,我在主界面就想着要定义一个List,这个list当中的内容当然就是我们FileState啦。
  接着我们去实现主界面。创建主界面MainActivity
  1. package edu.notify.viking.activity;

  2. import java.util.ArrayList;
  3. import java.util.List;

  4. import edu.notify.viking.adapter.MyAdapter;
  5. import edu.notify.viking.entity.FileState;

  6. import android.app.Activity;
  7. import android.os.Bundle;
  8. import android.widget.ListView;

  9. public class MainActivity extends Activity 
  10. {
  11. private List<FileState> list=new ArrayList<FileState>();
  12. private ListView listView;
  13. /** Called when the activity is first created. */
  14. @Override
  15. public void onCreate(Bundle savedInstanceState)
  16. {
  17. super.onCreate(savedInstanceState);
  18. setContentView(R.layout.main);
  19. initFileState();//先对FileState进行初始化
  20. initUI();//对界面进行初始化
  21. }

  22. /**
  23. * 把数据放进list中,因为是测试所以我手工添加数据
  24. * **/
  25. private void initFileState()
  26. {
  27. //给FileState赋值
  28. for(int i =1;i<8;i++)
  29. {
  30. FileState fileState=new FileState();
  31. fileState.setFileName(i+".mp3");//名字
  32. fileState.setCompleteSize(100);//初始化下载程度
  33. fileState.setState(true);
  34. list.add(fileState);
  35. }
  36. FileState f=new FileState();
  37. f.setFileName("8.mp3");
  38. f.setCompleteSize(0);
  39. f.setState(false);
  40. list.add(f);
  41. }

  42. private void initUI()
  43. {
  44. listView = (ListView)this.findViewById(R.id.listview);
  45. MyAdapter adapter = new MyAdapter(list,this);
  46. listView.setAdapter(adapter);
  47. adapter.setListView(listView);
  48. }
  49. }
复制代码
因为是模拟,所以主界面很简单,只有2个属性,一个List<FileState>,这里面的内容用来显示到listview上,还有一个就是咱们的ListView啦。咱们先对FileState进行初始化,否则就没内容显示。我使用了一个循环,把文件的名字取为1.mp3---7.mp3,这7个文件的状态都是true,也就是已经下载完成。然后单独的初始化了8.mp3这个文件,这个文件完成度为0,状态也是false,我这么做的目的相信聪明的你,已经明白了。按照正常的逻辑,我们去更新界面,这些已经下载完成的文件,我们就没有必要再让他们去更新,只用更新正在下载中的文件就可以了。但是使用notifyDatatSetChanged方法真的能满足我们的需求吗?
  接着我创建了自定义的adapter,并将他与listView绑定在一起。然后将listView传进了adapter中。
  那我们来看看adapter中是如何实现的吧。新建一个adapter,取名叫MyAdatper继承BaseAdapter.
  
  1. package edu.notify.viking.adapter;

  2. import java.util.List;

  3. import edu.notify.viking.activity.R;
  4. import edu.notify.viking.down.Downloader;
  5. import edu.notify.viking.entity.FileState;

  6. import android.content.Context;
  7. import android.os.Handler;
  8. import android.os.Message;
  9. import android.view.LayoutInflater;
  10. import android.view.View;
  11. import android.view.ViewGroup;
  12. import android.widget.BaseAdapter;
  13. import android.widget.ImageView;
  14. import android.widget.ListView;
  15. import android.widget.ProgressBar;
  16. import android.widget.TextView;

  17. public class MyAdapter extends BaseAdapter 
  18. {
  19. private List<FileState> list;
  20. private Context context;
  21. private LayoutInflater inflater=null;
  22. private ListView listView;
  23. private Handler mHandler = new Handler()
  24. {

  25. @Override
  26. public void handleMessage(Message msg) 
  27. {
  28. if(msg.what==1)
  29. {
  30. String name=(String)msg.obj;
  31. int length=msg.arg1;
  32. for(int i=0;i<list.size();i++)
  33. {
  34. FileState fileState=list.get(i);
  35. if(fileState.getFileName().equals(name))
  36. {
  37. fileState.setCompleteSize(length);
  38. list.set(i, fileState);

  39. break;
  40. }
  41. }
  42. notifyDataSetChanged();
  43. }
  44. }

  45. };
  46. public MyAdapter(List<FileState> list,Context context)
  47. {
  48. inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  49. this.list=list;
  50. }


  51. class ViewHolder
  52. {
  53. public TextView fileName;//文件名称
  54. public ProgressBar progressBar;//进度条
  55. public TextView percent;//百分比
  56. public ImageView down;//下载
  57. }



  58. public int getCount() 
  59. {
  60. // TODO Auto-generated method stub
  61. return list.size();
  62. }

  63. public Object getItem(int position) 
  64. {
  65. // TODO Auto-generated method stub
  66. return list.get(position);
  67. }

  68. public long getItemId(int position) 
  69. {
  70. // TODO Auto-generated method stub
  71. return position;
  72. }

  73. public View getView(int position, View convertView, ViewGroup parent) 
  74. {
  75. ViewHolder holder;
  76. if(convertView==null)
  77. {
  78. convertView=inflater.inflate(R.layout.main_item, null);
  79. holder=new ViewHolder();
  80. holder.fileName=(TextView)convertView.findViewById(R.id.fileName);
  81. holder.progressBar=(ProgressBar)convertView.findViewById(R.id.down_progressBar);
  82. holder.percent = (TextView) convertView.findViewById(R.id.percent_text);
  83. holder.down = (ImageView) convertView.findViewById(R.id.down_view);
  84. convertView.setTag(holder);
  85. }
  86. else
  87. {
  88. holder=(ViewHolder)convertView.getTag();
  89. }
  90. FileState fileState=list.get(position);
  91. final String name = fileState.getFileName();
  92. System.out.println(name+"---run getView");
  93. //如果文件状态为已经下载
  94. if(fileState.isState()==true)
  95. {
  96. holder.fileName.setText(fileState.getFileName());
  97. //下载完成的文件,进度条被隐藏
  98. holder.progressBar.setVisibility(ProgressBar.INVISIBLE);
  99. //设置为已下载
  100. holder.percent.setText("已下载");
  101. //下载完成的文件,下载按钮被隐藏,防止重复下载
  102. holder.down.setVisibility(ImageView.INVISIBLE);
  103. }
  104. else
  105. {
  106. holder.fileName.setText(fileState.getFileName());
  107. holder.progressBar.setVisibility(ProgressBar.VISIBLE);
  108. holder.progressBar.setProgress(fileState.getCompleteSize());
  109. holder.percent.setText(fileState.getCompleteSize()+"%");
  110. holder.down.setOnClickListener(new View.OnClickListener()
  111. {

  112. public void onClick(View v) 
  113. {
  114. Downloader down= new Downloader(name,mHandler);
  115. down.download();
  116. }

  117. });
  118. if(fileState.getCompleteSize()==100)
  119. {
  120. holder.progressBar.setVisibility(ProgressBar.INVISIBLE);
  121. holder.percent.setText("已下载");
  122. holder.down.setVisibility(ProgressBar.INVISIBLE);
  123. fileState.setState(true);
  124. list.set(position, fileState);
  125. }

  126. }
  127. return convertView;
  128. }

  129. public void setListView(ListView listView) {
  130. this.listView = listView;
  131. }

  132. }
复制代码
为了运行的效率,我在adapter中定义了一个内部类,ViewHolder,其中的属性都是我们要绘制出来的控件。主要的绘制工作在于getView这个方法,在getView中我们对变量初始化以后,就将list当中对应的FileState拿了出来,根据文件的状态进行了分别的处理,下载完成的怎么怎么显示。。。下载为完成的怎么怎么显示。。。这些都不难,很容易就看明白了。在这里面我们实现了下载按钮这个点击事件,这个事件中的代码也很简单,就是创建一个Downloader对象,然后调用其中的download方法进行下载。在这个类中我们看到,其中创建了一个Handler对象,并在里面实现它的消息处理方法handleMessage。当我们点击下载图标时会把这个Handler对象传进Downloader这个类中。当文件开始下载时,下载完成的数据长度将会传到handlerMessage这个方法中被处理,我们在这个方法中对FileState与list做了更新,然后调用了notifyDatatSetChanged方法.
  接下来我们来看最后一个类。创建Downloader类。
  1. package edu.notify.viking.down;
  2. import java.util.Map;

  3. import android.os.Handler;
  4. import android.os.Message;


  5. public class Downloader 
  6. {
  7. private String fileName;
  8. private Handler mHandler;
  9. public Downloader(String fileName, Handler handler)
  10. {
  11. super();
  12. this.fileName = fileName;
  13. mHandler = handler;
  14. }

  15. public void download()
  16. {
  17. new MyThread().start();
  18. }

  19. class MyThread extends Thread
  20. {

  21. @Override
  22. public void run()
  23. {
  24. for(int i=0;i<=100;i++)
  25. {
  26. System.out.println("i:"+i);
  27. try {
  28. this.currentThread().sleep(1000);
  29. } catch (InterruptedException e) {
  30. // TODO Auto-generated catch block
  31. e.printStackTrace();
  32. }
  33. Message msg=Message.obtain();
  34. msg.what=1;
  35. msg.obj=fileName;
  36. msg.arg1=i;
  37. mHandler.sendMessage(msg);
  38. }
  39. }

  40. }
  41. }
复制代码
这个类用于开启一个单独的线程来模拟下载的环节。其中的内容很简单,无非是接收从adapter中传递过来的数据,当用户点击下载图标时,执行download方法。在线程类的run方法中,我们做了一个想0-100的循环,当然为了让大家看到进度条的更新,我们让线程每次休眠1秒钟。然后用adapter对象中传递过来的handler对象发送message。这时候我们的adapter类中的handleMessage方法就可以收到消息,并进行处理,最后调用notifyDataSetChanged方法了。好的,我们把xml文件也先给大家。
  先是main.xml
  
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent"
  6. >
  7. <ListView
  8. android:id="@+id/listview"
  9. android:layout_width="fill_parent"
  10. android:layout_height="fill_parent"
  11. android:fastScrollEnabled="true"
  12. />
  13. </LinearLayout>
复制代码
  然后是main_item.xml
  
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="horizontal"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent"
  6. >
  7. <TextView
  8. android:id="@+id/fileName"
  9. android:layout_width="wrap_content"
  10. android:layout_height="wrap_content"
  11. android:layout_weight="1"
  12. />
  13. <ProgressBar
  14. android:id="@+id/down_progressBar"
  15. android:layout_width="wrap_content"
  16. android:layout_height="wrap_content"
  17. android:layout_weight="1"
  18. style="@android:style/Widget.ProgressBar.Horizontal"
  19. />
  20. <TextView
  21. android:id="@+id/percent_text"
  22. android:layout_width="wrap_content"
  23. android:layout_height="wrap_content"
  24. />
  25. <ImageView 
  26. android:id="@+id/down_view"
  27. android:layout_width="wrap_content"
  28. android:layout_height="wrap_content"
  29. android:src="@drawable/down"
  30. />
  31. </LinearLayout>
复制代码
   这两个布局文件很简单,大家一看就明白了,现在我们来测试一下,看看notifyDataSetChanged方法后,getView一共会执行几次?
1.jpg 
  从图中我们可以看见,尽管我们只是想更新8.mp3这个item的进度条,但是所有的进度条都被更新了,每次使用notifyDataSetChanged方法,对会调用8次getView方法。天哪。这个效率。。。
  这不是我们想要的,我们想要的是什么?我们想要的就是如果只需要更新8.mp3这个item,那么其他的item将保持不变,不需要更新他们。那么我们该怎么解决这个问题呢?
  其实这个问题的解决也不麻烦,所谓难者不会,会者不难啊。我们只需要修改adapter这个类,在其中添加一个方法即可。现在我们来看更新后的adapter类。
  1. package edu.notify.viking.adapter;

  2. import java.util.List;

  3. import edu.notify.viking.activity.R;
  4. import edu.notify.viking.down.Downloader;
  5. import edu.notify.viking.entity.FileState;

  6. import android.content.Context;
  7. import android.os.Handler;
  8. import android.os.Message;
  9. import android.view.LayoutInflater;
  10. import android.view.View;
  11. import android.view.ViewGroup;
  12. import android.widget.BaseAdapter;
  13. import android.widget.ImageView;
  14. import android.widget.ListView;
  15. import android.widget.ProgressBar;
  16. import android.widget.TextView;

  17. public class MyAdapter extends BaseAdapter 
  18. {
  19. private List<FileState> list;
  20. private Context context;
  21. private LayoutInflater inflater=null;
  22. private ListView listView;
  23. private Handler mHandler = new Handler()
  24. {

  25. @Override
  26. public void handleMessage(Message msg) 
  27. {
  28. if(msg.what==1)
  29. {
  30. String name=(String)msg.obj;
  31. int length=msg.arg1;
  32. for(int i=0;i<list.size();i++)
  33. {
  34. FileState fileState=list.get(i);
  35. if(fileState.getFileName().equals(name))
  36. {
  37. fileState.setCompleteSize(length);
  38. list.set(i, fileState);
  39. updateView(i);//用我们自己写的方法 
  40. break;
  41. }
  42. }
  43. //notifyDataSetChanged();不用了
  44. }
  45. }

  46. };
  47. public MyAdapter(List<FileState> list,Context context)
  48. {
  49. inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  50. this.list=list;
  51. }


  52. class ViewHolder
  53. {
  54. public TextView fileName;//文件名称
  55. public ProgressBar progressBar;//进度条
  56. public TextView percent;//百分比
  57. public ImageView down;//下载
  58. }

  59. /**
  60. * 用于更新我们想要更新的item
  61. * @param itemIndex 想更新item的下标
  62. * **/
  63. private void updateView(int itemIndex)

  64. //得到第1个可显示控件的位置,记住是第1个可显示控件噢。而不是第1个控件
  65. int visiblePosition = listView.getFirstVisiblePosition(); 
  66. //得到你需要更新item的View
  67. View view = listView.getChildAt(itemIndex - visiblePosition);
  68. FileState fileState=list.get(itemIndex);
  69. final String name=fileState.getFileName();
  70. System.out.println(name+"---run updateView");
  71. if(fileState.isState()==false)
  72. {
  73. ViewHolder holderOne=new ViewHolder();
  74. //start:初始化
  75. holderOne.fileName=(TextView)view.findViewById(R.id.fileName);
  76. holderOne.progressBar=(ProgressBar)view.findViewById(R.id.down_progressBar);
  77. holderOne.percent = (TextView) view.findViewById(R.id.percent_text);
  78. holderOne.down = (ImageView) view.findViewById(R.id.down_view);
  79. //end
  80. holderOne.fileName.setText(fileState.getFileName());
  81. holderOne.progressBar.setVisibility(ProgressBar.VISIBLE);
  82. holderOne.progressBar.setProgress(fileState.getCompleteSize());
  83. holderOne.percent.setText(fileState.getCompleteSize()+"%");
  84. holderOne.down.setOnClickListener(new View.OnClickListener()
  85. {

  86. public void onClick(View v) 
  87. {
  88. Downloader down= new Downloader(name,mHandler);
  89. down.download();
  90. }

  91. });
  92. if(fileState.getCompleteSize()==100)
  93. {
  94. holderOne.progressBar.setVisibility(ProgressBar.INVISIBLE);
  95. holderOne.percent.setText("已下载");
  96. holderOne.down.setVisibility(ProgressBar.INVISIBLE);
  97. fileState.setState(true);
  98. list.set(itemIndex, fileState);
  99. }
  100. }


  101. public int getCount() 
  102. {
  103. // TODO Auto-generated method stub
  104. return list.size();
  105. }

  106. public Object getItem(int position) 
  107. {
  108. // TODO Auto-generated method stub
  109. return list.get(position);
  110. }

  111. public long getItemId(int position) 
  112. {
  113. // TODO Auto-generated method stub
  114. return position;
  115. }

  116. public View getView(int position, View convertView, ViewGroup parent) 
  117. {
  118. ViewHolder holder;
  119. if(convertView==null)
  120. {
  121. convertView=inflater.inflate(R.layout.main_item, null);
  122. holder=new ViewHolder();
  123. holder.fileName=(TextView)convertView.findViewById(R.id.fileName);
  124. holder.progressBar=(ProgressBar)convertView.findViewById(R.id.down_progressBar);
  125. holder.percent = (TextView) convertView.findViewById(R.id.percent_text);
  126. holder.down = (ImageView) convertView.findViewById(R.id.down_view);
  127. convertView.setTag(holder);
  128. }
  129. else
  130. {
  131. holder=(ViewHolder)convertView.getTag();
  132. }
  133. FileState fileState=list.get(position);
  134. final String name = fileState.getFileName();
  135. System.out.println(name+"---run getView");
  136. //如果文件状态为已经下载
  137. if(fileState.isState()==true)
  138. {
  139. holder.fileName.setText(fileState.getFileName());
  140. //下载完成的文件,进度条被隐藏
  141. holder.progressBar.setVisibility(ProgressBar.INVISIBLE);
  142. //设置为已下载
  143. holder.percent.setText("已下载");
  144. //下载完成的文件,下载按钮被隐藏,防止重复下载
  145. holder.down.setVisibility(ImageView.INVISIBLE);
  146. }
  147. else
  148. {
  149. holder.fileName.setText(fileState.getFileName());
  150. holder.progressBar.setVisibility(ProgressBar.VISIBLE);
  151. holder.progressBar.setProgress(fileState.getCompleteSize());
  152. holder.percent.setText(fileState.getCompleteSize()+"%");
  153. holder.down.setOnClickListener(new View.OnClickListener()
  154. {

  155. public void onClick(View v) 
  156. {
  157. Downloader down= new Downloader(name,mHandler);
  158. down.download();
  159. }

  160. });
  161. if(fileState.getCompleteSize()==100)
  162. {
  163. holder.progressBar.setVisibility(ProgressBar.INVISIBLE);
  164. holder.percent.setText("已下载");
  165. holder.down.setVisibility(ProgressBar.INVISIBLE);
  166. fileState.setState(true);
  167. list.set(position, fileState);
  168. }

  169. }
  170. return convertView;
  171. }

  172. public void setListView(ListView listView) {
  173. this.listView = listView;
  174. }

  175. }
复制代码
  在类中我们对handleMessage做了一点修改。
  3.jpg 
  看到红色箭头的地方就是修改过的。然后多添加了一个updateView方法。这个方法需要传入你想更新的item下标。
  最核心的地方是这2句代码。
   4.jpg 
  主要的意思就是根据你想要更新的item下标去得到这个item的View对象,这样你就可以为所欲为啦。哈哈哈哈哈。。。
  咱们来看看运行效果。
  5.jpg 
  这里我改为同时更新2个item,看看这样会不会出错,可以看到,运行的很正常。控制台的打印结果也只是更新了7-8.mp3这2个item。
  好了,文章到此已经结束了,肯定有人要骂我了,这么简单的东西,你啰嗦了半天。整的我都没怎么听懂。  不管怎样,都要感谢CCTV,MTV,还有KTV。当然还有我们安卓巴士提供的这个平台。希望安卓巴士越办越好。

我来说两句
您需要登录后才可以评论 登录 | 立即注册
facelist
所有评论(16)
geofferysun 2012-12-25 12:00
学习了,这才是学习精神
回复
一直狠安静 2013-10-16 16:38
这样是可以实现局部更新指定item 不过当数据条目超过了屏幕之后  listview 上下滚动那么之前更新的状态就不能保存了  怎样保存已经更新的视图的状态呢
回复
youshenyang 2015-5-18 22:46
楼主给力哦亲,优化得可以哦!
回复
youshenyang 2015-5-18 22:48
楼主给力哦亲,优化得可以哦!值得学习~!
回复
1165287425 2015-6-26 17:40
给楼主点个赞。
回复
此乃天命 2015-7-2 10:01
一直狠安静: 这样是可以实现局部更新指定item 不过当数据条目超过了屏幕之后  listview 上下滚动那么之前更新的状态就不能保存了  怎样保存已经更新的视图的状态呢 ...
所有的状态都是要保存到adapter中的map(entity)中,,根据map来刷新listview的额
回复
此乃天命 2015-7-2 10:01
一直狠安静: 这样是可以实现局部更新指定item 不过当数据条目超过了屏幕之后  listview 上下滚动那么之前更新的状态就不能保存了  怎样保存已经更新的视图的状态呢 ...
所有的状态都是要保存到adapter中的map(entity)中,,根据map来刷新listview的额
回复
acorn 2015-8-24 14:01
我来总结下..
1.notifyDataSetChanged()方法并不是只刷新数据有变动的item,而是很没效率的刷新所有显示出来的view.
2.所以直接获取想要刷新的那个view,然后随你怎么玩...
用这个方法:
View updateView=listview.getchildAt(updateItemIndex-listview.getFirstVisiblePosition());
回复
15093389880 2015-9-24 10:30
给楼主点个赞。
回复
Beiing 2015-10-13 18:08
哪里有源码下载
回复
aw456 2015-11-10 14:35
给力
回复
qhd双鱼座 2015-12-3 10:39
没看懂,拿下来好好研究一下
回复
taotao222222 2015-12-9 11:39
给大家推建一个微信公众号“开心段子手” 关注后  输入“电影”两个字,即可在线看最新电影!不骗!
回复
回忆碎片 2016-3-17 19:32
这就像微信里面点赞之后,更新一个条目,而不是全部条目,前天面试就遇到这个问题,要是早看到就好了。
回复
578911542 2016-3-18 17:46
支持
回复
mu5532123 2016-8-6 23:49
支持
回复
领先的中文移动开发者社区
18620764416
7*24全天服务
意见反馈:1294855032@qq.com

扫一扫关注我们

Powered by Discuz! X3.2© 2001-2019 Comsenz Inc.( 粤ICP备15117877号 )