想到发这篇帖子是源于我的上一篇帖子#Testin杯#多线程断点续传后台下载 。
帖子中讲述的项目使用了listView这个控件,而且自定义了adapter。在更新item的进度条时发现每次使用notifyDataSetChanged(),都会去调用自定义adapter中的getView方法。这时问题就出现了,用notifyDataSetChanged方法去更新listView中的item,是更新需要更新的Item呢?还是更新所有的item呢?如果是更新所有的item那么效率不就会很低吗?有什么办法可以解决这个问题呢?
怀着心中的疑惑,我开始了这次的实验。。。
我的想法很简单现实模拟远程下载文件,创建一个Activity做主界面,主界面采用listView。然后自定义一个adapter实现BaseAdapter,再创建一个线程类,线程类当中采用循环的方式不断的往adapter发送消息.然后使用notifyDataSetChanged方法更新界面,在调用getView方法时在控制台输出语句,这样我就可以知道notifyDatatSetChanged方法执行时是更新一个item还是更新所有的item了。
有了思路就好办了,我们先建立一个类,叫FileState。
package edu.notify.viking.entity;
public class FileState
{
String fileName;//文件名字
int completeSize;//完成的长度
boolean state;//文件状态,true为已经完成,false为未完成
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public int getCompleteSize() {
return completeSize;
}
public void setCompleteSize(int completeSize) {
this.completeSize = completeSize;
}
public boolean isState() {
return state;
}
public void setState(boolean state) {
this.state = state;
}
}
这个类中有3个属性,分别是文件名字,文件已经下载的长度,还有文件当前的状态。然后就是get与set方法。这个类的作用我想大家应该一眼就明白了,没错,既然使用了listView,我在主界面就想着要定义一个List,这个list当中的内容当然就是我们FileState啦。
接着我们去实现主界面。创建主界面MainActivity
package edu.notify.viking.activity; import java.util.ArrayList; import java.util.List; import edu.notify.viking.adapter.MyAdapter; import edu.notify.viking.entity.FileState; import android.app.Activity; import android.os.Bundle; import android.widget.ListView; public class MainActivity extends Activity { private List<FileState> list=new ArrayList<FileState>(); private ListView listView; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); initFileState();//先对FileState进行初始化 initUI();//对界面进行初始化 } /** * 把数据放进list中,因为是测试所以我手工添加数据 * **/ private void initFileState() { //给FileState赋值 for(int i =1;i<8;i++) { FileState fileState=new FileState(); fileState.setFileName(i+".mp3");//名字 fileState.setCompleteSize(100);//初始化下载程度 fileState.setState(true); list.add(fileState); } FileState f=new FileState(); f.setFileName("8.mp3"); f.setCompleteSize(0); f.setState(false); list.add(f); } private void initUI() { listView = (ListView)this.findViewById(R.id.listview); MyAdapter adapter = new MyAdapter(list,this); listView.setAdapter(adapter); adapter.setListView(listView); } }
因为是模拟,所以主界面很简单,只有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 3 import java.util.List; 4 5 import edu.notify.viking.activity.R; 6 import edu.notify.viking.down.Downloader; 7 import edu.notify.viking.entity.FileState; 8 9 import android.content.Context; 10 import android.os.Handler; 11 import android.os.Message; 12 import android.view.LayoutInflater; 13 import android.view.View; 14 import android.view.ViewGroup; 15 import android.widget.BaseAdapter; 16 import android.widget.ImageView; 17 import android.widget.ListView; 18 import android.widget.ProgressBar; 19 import android.widget.TextView; 20 21 public class MyAdapter extends BaseAdapter 22 { 23 private List<FileState> list; 24 private Context context; 25 private LayoutInflater inflater=null; 26 private ListView listView; 27 private Handler mHandler = new Handler() 28 { 29 30 @Override 31 public void handleMessage(Message msg) 32 { 33 if(msg.what==1) 34 { 35 String name=(String)msg.obj; 36 int length=msg.arg1; 37 for(int i=0;i<list.size();i++) 38 { 39 FileState fileState=list.get(i); 40 if(fileState.getFileName().equals(name)) 41 { 42 fileState.setCompleteSize(length); 43 list.set(i, fileState); 44 45 break; 46 } 47 } 48 notifyDataSetChanged(); 49 } 50 } 51 52 }; 53 public MyAdapter(List<FileState> list,Context context) 54 { 55 inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 56 this.list=list; 57 } 58 59 60 class ViewHolder 61 { 62 public TextView fileName;//文件名称 63 public ProgressBar progressBar;//进度条 64 public TextView percent;//百分比 65 public ImageView down;//下载 66 } 67 68 69 70 public int getCount() 71 { 72 // TODO Auto-generated method stub 73 return list.size(); 74 } 75 76 public Object getItem(int position) 77 { 78 // TODO Auto-generated method stub 79 return list.get(position); 80 } 81 82 public long getItemId(int position) 83 { 84 // TODO Auto-generated method stub 85 return position; 86 } 87 88 public View getView(int position, View convertView, ViewGroup parent) 89 { 90 ViewHolder holder; 91 if(convertView==null) 92 { 93 convertView=inflater.inflate(R.layout.main_item, null); 94 holder=new ViewHolder(); 95 holder.fileName=(TextView)convertView.findViewById(R.id.fileName); 96 holder.progressBar=(ProgressBar)convertView.findViewById(R.id.down_progressBar); 97 holder.percent = (TextView) convertView.findViewById(R.id.percent_text); 98 holder.down = (ImageView) convertView.findViewById(R.id.down_view); 99 convertView.setTag(holder); 100 } 101 else 102 { 103 holder=(ViewHolder)convertView.getTag(); 104 } 105 FileState fileState=list.get(position); 106 final String name = fileState.getFileName(); 107 System.out.println(name+"---run getView"); 108 //如果文件状态为已经下载 109 if(fileState.isState()==true) 110 { 111 holder.fileName.setText(fileState.getFileName()); 112 //下载完成的文件,进度条被隐藏 113 holder.progressBar.setVisibility(ProgressBar.INVISIBLE); 114 //设置为已下载 115 holder.percent.setText("已下载"); 116 //下载完成的文件,下载按钮被隐藏,防止重复下载 117 holder.down.setVisibility(ImageView.INVISIBLE); 118 } 119 else 120 { 121 holder.fileName.setText(fileState.getFileName()); 122 holder.progressBar.setVisibility(ProgressBar.VISIBLE); 123 holder.progressBar.setProgress(fileState.getCompleteSize()); 124 holder.percent.setText(fileState.getCompleteSize()+"%"); 125 holder.down.setOnClickListener(new View.OnClickListener() 126 { 127 128 public void onClick(View v) 129 { 130 Downloader down= new Downloader(name,mHandler); 131 down.download(); 132 } 133 134 }); 135 if(fileState.getCompleteSize()==100) 136 { 137 holder.progressBar.setVisibility(ProgressBar.INVISIBLE); 138 holder.percent.setText("已下载"); 139 holder.down.setVisibility(ProgressBar.INVISIBLE); 140 fileState.setState(true); 141 list.set(position, fileState); 142 } 143 144 } 145 return convertView; 146 } 147 148 public void setListView(ListView listView) { 149 this.listView = listView; 150 } 151 152 }
为了运行的效率,我在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 4 import android.os.Handler; 5 import android.os.Message; 6 7 8 public class Downloader 9 { 10 private String fileName; 11 private Handler mHandler; 12 public Downloader(String fileName, Handler handler) 13 { 14 super(); 15 this.fileName = fileName; 16 mHandler = handler; 17 } 18 19 public void download() 20 { 21 new MyThread().start(); 22 } 23 24 class MyThread extends Thread 25 { 26 27 @Override 28 public void run() 29 { 30 for(int i=0;i<=100;i++) 31 { 32 System.out.println("i:"+i); 33 try { 34 this.currentThread().sleep(1000); 35 } catch (InterruptedException e) { 36 // TODO Auto-generated catch block 37 e.printStackTrace(); 38 } 39 Message msg=Message.obtain(); 40 msg.what=1; 41 msg.obj=fileName; 42 msg.arg1=i; 43 mHandler.sendMessage(msg); 44 } 45 } 46 47 } 48 }
这个类用于开启一个单独的线程来模拟下载的环节。其中的内容很简单,无非是接收从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一共会执行几次?
<IGNORE_JS_OP style="WORD-WRAP: break-word">
从图中我们可以看见,尽管我们只是想更新8.mp3这个item的进度条,但是所有的进度条都被更新了,每次使用notifyDataSetChanged方法,对会调用8次getView方法。天哪。这个效率。。。
这不是我们想要的,我们想要的是什么?我们想要的就是如果只需要更新8.mp3这个item,那么其他的item将保持不变,不需要更新他们。那么我们该怎么解决这个问题呢?
其实这个问题的解决也不麻烦,所谓难者不会,会者不难啊。我们只需要修改adapter这个类,在其中添加一个方法即可。现在我们来看更新后的adapter类。
1 package edu.notify.viking.adapter; 2 3 import java.util.List; 4 5 import edu.notify.viking.activity.R; 6 import edu.notify.viking.down.Downloader; 7 import edu.notify.viking.entity.FileState; 8 9 import android.content.Context; 10 import android.os.Handler; 11 import android.os.Message; 12 import android.view.LayoutInflater; 13 import android.view.View; 14 import android.view.ViewGroup; 15 import android.widget.BaseAdapter; 16 import android.widget.ImageView; 17 import android.widget.ListView; 18 import android.widget.ProgressBar; 19 import android.widget.TextView; 20 21 public class MyAdapter extends BaseAdapter 22 { 23 private List<FileState> list; 24 private Context context; 25 private LayoutInflater inflater=null; 26 private ListView listView; 27 private Handler mHandler = new Handler() 28 { 29 30 @Override 31 public void handleMessage(Message msg) 32 { 33 if(msg.what==1) 34 { 35 String name=(String)msg.obj; 36 int length=msg.arg1; 37 for(int i=0;i<list.size();i++) 38 { 39 FileState fileState=list.get(i); 40 if(fileState.getFileName().equals(name)) 41 { 42 fileState.setCompleteSize(length); 43 list.set(i, fileState); 44 updateView(i);//用我们自己写的方法 45 break; 46 } 47 } 48 //notifyDataSetChanged();不用了 49 } 50 } 51 52 }; 53 public MyAdapter(List<FileState> list,Context context) 54 { 55 inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 56 this.list=list; 57 } 58 59 60 class ViewHolder 61 { 62 public TextView fileName;//文件名称 63 public ProgressBar progressBar;//进度条 64 public TextView percent;//百分比 65 public ImageView down;//下载 66 } 67 68 /** 69 * 用于更新我们想要更新的item 70 * @param itemIndex 想更新item的下标 71 * **/ 72 private void updateView(int itemIndex) 73 { 74 //得到第1个可显示控件的位置,记住是第1个可显示控件噢。而不是第1个控件 75 int visiblePosition = listView.getFirstVisiblePosition(); 76 //得到你需要更新item的View 77 View view = listView.getChildAt(itemIndex - visiblePosition); 78 FileState fileState=list.get(itemIndex); 79 final String name=fileState.getFileName(); 80 System.out.println(name+"---run updateView"); 81 if(fileState.isState()==false) 82 { 83 ViewHolder holderOne=new ViewHolder(); 84 //start:初始化 85 holderOne.fileName=(TextView)view.findViewById(R.id.fileName); 86 holderOne.progressBar=(ProgressBar)view.findViewById(R.id.down_progressBar); 87 holderOne.percent = (TextView) view.findViewById(R.id.percent_text); 88 holderOne.down = (ImageView) view.findViewById(R.id.down_view); 89 //end 90 holderOne.fileName.setText(fileState.getFileName()); 91 holderOne.progressBar.setVisibility(ProgressBar.VISIBLE); 92 holderOne.progressBar.setProgress(fileState.getCompleteSize()); 93 holderOne.percent.setText(fileState.getCompleteSize()+"%"); 94 holderOne.down.setOnClickListener(new View.OnClickListener() 95 { 96 97 public void onClick(View v) 98 { 99 Downloader down= new Downloader(name,mHandler); 100 down.download(); 101 } 102 103 }); 104 if(fileState.getCompleteSize()==100) 105 { 106 holderOne.progressBar.setVisibility(ProgressBar.INVISIBLE); 107 holderOne.percent.setText("已下载"); 108 holderOne.down.setVisibility(ProgressBar.INVISIBLE); 109 fileState.setState(true); 110 list.set(itemIndex, fileState); 111 } 112 } 113 } 114 115 public int getCount() 116 { 117 // TODO Auto-generated method stub 118 return list.size(); 119 } 120 121 public Object getItem(int position) 122 { 123 // TODO Auto-generated method stub 124 return list.get(position); 125 } 126 127 public long getItemId(int position) 128 { 129 // TODO Auto-generated method stub 130 return position; 131 } 132 133 public View getView(int position, View convertView, ViewGroup parent) 134 { 135 ViewHolder holder; 136 if(convertView==null) 137 { 138 convertView=inflater.inflate(R.layout.main_item, null); 139 holder=new ViewHolder(); 140 holder.fileName=(TextView)convertView.findViewById(R.id.fileName); 141 holder.progressBar=(ProgressBar)convertView.findViewById(R.id.down_progressBar); 142 holder.percent = (TextView) convertView.findViewById(R.id.percent_text); 143 holder.down = (ImageView) convertView.findViewById(R.id.down_view); 144 convertView.setTag(holder); 145 } 146 else 147 { 148 holder=(ViewHolder)convertView.getTag(); 149 } 150 FileState fileState=list.get(position); 151 final String name = fileState.getFileName(); 152 System.out.println(name+"---run getView"); 153 //如果文件状态为已经下载 154 if(fileState.isState()==true) 155 { 156 holder.fileName.setText(fileState.getFileName()); 157 //下载完成的文件,进度条被隐藏 158 holder.progressBar.setVisibility(ProgressBar.INVISIBLE); 159 //设置为已下载 160 holder.percent.setText("已下载"); 161 //下载完成的文件,下载按钮被隐藏,防止重复下载 162 holder.down.setVisibility(ImageView.INVISIBLE); 163 } 164 else 165 { 166 holder.fileName.setText(fileState.getFileName()); 167 holder.progressBar.setVisibility(ProgressBar.VISIBLE); 168 holder.progressBar.setProgress(fileState.getCompleteSize()); 169 holder.percent.setText(fileState.getCompleteSize()+"%"); 170 holder.down.setOnClickListener(new View.OnClickListener() 171 { 172 173 public void onClick(View v) 174 { 175 Downloader down= new Downloader(name,mHandler); 176 down.download(); 177 } 178 179 }); 180 if(fileState.getCompleteSize()==100) 181 { 182 holder.progressBar.setVisibility(ProgressBar.INVISIBLE); 183 holder.percent.setText("已下载"); 184 holder.down.setVisibility(ProgressBar.INVISIBLE); 185 fileState.setState(true); 186 list.set(position, fileState); 187 } 188 189 } 190 return convertView; 191 } 192 193 public void setListView(ListView listView) { 194 this.listView = listView; 195 } 196 197 }
在类中我们对handleMessage做了一点修改。
<IGNORE_JS_OP style="WORD-WRAP: break-word">
看到红色箭头的地方就是修改过的。然后多添加了一个updateView方法。这个方法需要传入你想更新的item下标。
最核心的地方是这2句代码。
<IGNORE_JS_OP style="WORD-WRAP: break-word">
主要的意思就是根据你想要更新的item下标去得到这个item的View对象,这样你就可以为所欲为啦。哈哈哈哈哈。。。
咱们来看看运行效果。
<IGNORE_JS_OP style="WORD-WRAP: break-word">
这里我改为同时更新2个item,看看这样会不会出错,可以看到,运行的很正常。控制台的打印结果也只是更新了7-8.mp3这2个item。
源码下载地址:http://www.apkbus.com/android-60714-1-1.html
好了,文章到此已经结束了,肯定有人要骂我了,这么简单的东西,你啰嗦了半天。整的我都没怎么听懂。 不管怎样,都要感谢CCTV,MTV,还有KTV。