• 你还在用notifyDataSetChanged?


      想到发这篇帖子是源于我的上一篇帖子#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. <font face="宋体">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. }
    133. </font>
    复制代码
    为了运行的效率,我在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. <font face="宋体"><?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>
    14. </font>
    复制代码
      然后是main_item.xml
      
    1. <font face="宋体"><?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></font>
    复制代码
       这两个布局文件很简单,大家一看就明白了,现在我们来测试一下,看看notifyDataSetChanged方法后,getView一共会执行几次?
    <ignore_js_op>
      从图中我们可以看见,尽管我们只是想更新8.mp3这个item的进度条,但是所有的进度条都被更新了,每次使用notifyDataSetChanged方法,对会调用8次getView方法。天哪。这个效率。。。
      这不是我们想要的,我们想要的是什么?我们想要的就是如果只需要更新8.mp3这个item,那么其他的item将保持不变,不需要更新他们。那么我们该怎么解决这个问题呢?
      其实这个问题的解决也不麻烦,所谓难者不会,会者不难啊。我们只需要修改adapter这个类,在其中添加一个方法即可。现在我们来看更新后的adapter类。
    1. <font face="宋体">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. }
    176. </font>
    复制代码
      在类中我们对handleMessage做了一点修改。
      <ignore_js_op>
      看到红色箭头的地方就是修改过的。然后多添加了一个updateView方法。这个方法需要传入你想更新的item下标。
      最核心的地方是这2句代码。
       <ignore_js_op>
      主要的意思就是根据你想要更新的item下标去得到这个item的View对象,这样你就可以为所欲为啦。哈哈哈哈哈。。。{:soso_e144:}
      咱们来看看运行效果。
      <ignore_js_op>
      这里我改为同时更新2个item,看看这样会不会出错,可以看到,运行的很正常。控制台的打印结果也只是更新了7-8.mp3这2个item。
      好了,文章到此已经结束了,肯定有人要骂我了,这么简单的东西,你啰嗦了半天。整的我都没怎么听懂。{:soso__2830444864204702144_2:}  不管怎样,都要感谢CCTV,MTV,还有KTV。当然还有我们安卓巴士提供的这个平台。希望安卓巴士越办越好
  • 相关阅读:
    2019-2020 20175207- 20175235 实验四 外设驱动程序设计
    2019-2020 20175207- 20175235 实验三 实时系统
    2019-2020 20175207- 20175235 实验二 固件程序设计
    2019-2020-1 20175207 20175235 实验一开发环境的熟悉
    2018-2019-2 20175235 实验五《网络编程与安全》实验报告
    2018-2019-2 20175235 实验四《Android开发基础》实验报告
    2018-2019-2 20175235 实验三《敏捷开发与XP实践》实验报告
    2018-2019-2 20175235 实验二《Java面向对象程序设计》实验报告
    第六周学习总结
    第五周学习总结
  • 原文地址:https://www.cnblogs.com/bdbw2012/p/4670255.html
Copyright © 2020-2023  润新知