你还在用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。

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">1.jpg 
  从图中我们可以看见,尽管我们只是想更新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">3.jpg 
  看到红色箭头的地方就是修改过的。然后多添加了一个updateView方法。这个方法需要传入你想更新的item下标。
  最核心的地方是这2句代码。
   <IGNORE_JS_OP style="WORD-WRAP: break-word">4.jpg 
  主要的意思就是根据你想要更新的item下标去得到这个item的View对象,这样你就可以为所欲为啦。哈哈哈哈哈。。。
  咱们来看看运行效果。
  <IGNORE_JS_OP style="WORD-WRAP: break-word">5.jpg 
  这里我改为同时更新2个item,看看这样会不会出错,可以看到,运行的很正常。控制台的打印结果也只是更新了7-8.mp3这2个item。

源码下载地址:http://www.apkbus.com/android-60714-1-1.html


  好了,文章到此已经结束了,肯定有人要骂我了,这么简单的东西,你啰嗦了半天。整的我都没怎么听懂。  不管怎样,都要感谢CCTV,MTV,还有KTV。

 

 

 

 

 

 

 

转载于:https://www.cnblogs.com/zzy0127/archive/2012/09/05/2672062.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值