在android开发中,listview是比较常用的一个组件,在listview的数据需要更新的时候,一般会用notifyDataSetChanged()这个函数,但是它会更新listview中所有可视范围内的item,这样对性能肯定会有影响。比较常见的情景是android应用商店中的下载列表,当我们下载一款游戏的时候,只需要更新这款游戏对应的进度就可以了。本文就来模拟android应用商店的游戏下载,实现对listview的局部刷新,只实现一个简单的demo,不去真的下载文件。
1. 首先来创建代表应用商店中的app文件的类:AppFile.java,包含了一些基本的属性,源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
package
com.alexzhou.downloadfile;
/**
* author:alexzhou
* email :zhoujiangbohai@163.com
* date :2013-1-27
*
* 游戏列表中的app文件
**/
public
class
AppFile {
public
int
id;
public
String name;
// app的大小
public
int
size;
// 已下载大小
public
int
downloadSize;
// 下载状态:正常,正在下载,暂停,等待,已下载
public
int
downloadState;
}
|
2. 由于实际开发时,AppFile的属性比较多,这里创建一个辅助类:DownloadFile.java,代表下载中的文件,源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
package
com.alexzhou.downloadfile;
/**
* author:alexzhou
* email :zhoujiangbohai@163.com
* date :2013-1-27
*
* 下载的文件
**/
public
class
DownloadFile {
public
int
downloadID;
public
int
downloadSize;
public
int
totalSize;
public
int
downloadState;
}
|
3. 接下来需要一个下载管理类:DownloadManager.java,它管理所有下载任务。当同时下载很多任务的时候,界面会卡,所以指定只能同时下载3个任务,每个任务会启动一个线程,这里使用了ExecutorService线程池。当提交了超过三个下载任务时,只执行前3个任务,第四个任务会等到前面有一个下载完成后再下载,以此类推。这里还用到了android提供的一个工具类SparseArray,它是用来替代HashMap的,性能比HashMap要好。下面看源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
package
com.alexzhou.downloadfile;
import
java.util.ArrayList;
import
java.util.concurrent.ExecutorService;
import
java.util.concurrent.Executors;
import
android.os.Handler;
import
android.os.Message;
import
android.util.Log;
import
android.util.SparseArray;
/**
author:alexzhou
email :zhoujiangbohai@163.com
date :2013-1-27
下载管理
**/
public
class
DownloadManager {
// 下载状态:正常,暂停,下载中,已下载,排队中
public
static
final
int
DOWNLOAD_STATE_NORMAL =
0x00
;
public
static
final
int
DOWNLOAD_STATE_PAUSE =
0x01
;
public
static
final
int
DOWNLOAD_STATE_DOWNLOADING =
0x02
;
public
static
final
int
DOWNLOAD_STATE_FINISH =
0x03
;
public
static
final
int
DOWNLOAD_STATE_WAITING =
0x04
;
// SparseArray是android中替代Hashmap的类,可以提高效率
private
SparseArray<DownloadFile> downloadFiles =
new
SparseArray<DownloadFile>();
// 用来管理所有下载任务
private
ArrayList<DownloadTask> taskList =
new
ArrayList<DownloadTask>();
private
Handler mHandler;
private
final
static
Object syncObj =
new
Object();
private
static
DownloadManager instance;
private
ExecutorService executorService;
private
DownloadManager()
{
// 最多只能同时下载3个任务,其余的任务排队等待
executorService = Executors.newFixedThreadPool(
3
);
}
public
static
DownloadManager getInstance()
{
if
(
null
== instance)
{
synchronized
(syncObj) {
instance =
new
DownloadManager();
}
return
instance;
}
return
instance;
}
public
void
setHandler(Handler handler) {
this
.mHandler = handler;
}
// 开始下载,创建一个下载线程
public
void
startDownload(DownloadFile file) {
downloadFiles.put(file.downloadID, file);
DownloadTask task =
new
DownloadTask(file.downloadID);
taskList.add(task);
executorService.submit(task);
}
public
void
stopAllDownloadTask() {
while
(taskList.size() !=
0
)
{
DownloadTask task = taskList.remove(
0
);
// 可以在这里做其他的处理
task.stopTask();
}
// 会停止正在进行的任务和拒绝接受新的任务
executorService.shutdownNow();
}
// 下载任务
class
DownloadTask
implements
Runnable {
private
boolean
isWorking =
false
;
private
int
downloadId;
public
DownloadTask(
int
id)
{
this
.isWorking =
true
;
this
.downloadId = id;
}
public
void
stopTask()
{
this
.isWorking =
false
;
}
// 更新listview中对应的item
public
void
update(DownloadFile downloadFile)
{
Message msg = mHandler.obtainMessage();
if
(downloadFile.totalSize == downloadFile.downloadSize)
downloadFile.downloadState = DOWNLOAD_STATE_FINISH;
msg.obj = downloadFile;
msg.sendToTarget();
}
public
void
run() {
// 更新下载文件的状态
DownloadFile downloadFile = downloadFiles.get(downloadId);
downloadFile.downloadState = DOWNLOAD_STATE_DOWNLOADING;
while
(isWorking)
{
// 检测是否下载完成
if
(downloadFile.downloadState != DOWNLOAD_STATE_DOWNLOADING)
{
downloadFiles.remove(downloadFile.downloadID);
taskList.remove(
this
);
isWorking =
false
;
break
;
}
//Log.e("", "downloadSize="+downloadFile.downloadSize+"; size="+downloadFile.totalSize);
// 这里只是模拟了下载,每一秒更新一次item的下载状态
if
(downloadFile.downloadSize <= downloadFile.totalSize)
{
this
.update(downloadFile);
}
if
(downloadFile.downloadSize < downloadFile.totalSize)
{
try
{
Thread.sleep(
100
);
}
catch
(InterruptedException e) {
e.printStackTrace();
downloadFile.downloadState = DOWNLOAD_STATE_PAUSE;
this
.update(downloadFile);
downloadFiles.remove(downloadId);
isWorking =
false
;
break
;
}
++ downloadFile.downloadSize;
}
}
}
}
}
|
4. 接下来就需要实现listview的adapter了,这里比较重要的一个函数是updateView,这是实现listview局部刷新的关键,通过索引index得到listview中对应位置的子view,然后再更新该view的数据。源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
|
package
com.alexzhou.downloadfile;
import
android.content.Context;
import
android.graphics.drawable.Drawable;
import
android.os.Handler;
import
android.os.Message;
import
android.util.Log;
import
android.util.SparseArray;
import
android.view.LayoutInflater;
import
android.view.View;
import
android.view.View.OnClickListener;
import
android.view.ViewGroup;
import
android.widget.BaseAdapter;
import
android.widget.Button;
import
android.widget.ImageView;
import
android.widget.LinearLayout;
import
android.widget.ListView;
import
android.widget.TextView;
/**
author:alexzhou
email :zhoujiangbohai@163.com
date :2013-1-27
app列表的数据适配器
**/
public
class
AppListAdapter
extends
BaseAdapter {
private
SparseArray<AppFile> dataList =
null
;
private
LayoutInflater inflater =
null
;
private
Context mContext;
private
DownloadManager downloadManager;
private
ListView listView;
public
AppListAdapter(Context context, SparseArray<AppFile> dataList) {
this
.inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this
.dataList = dataList;
this
.mContext = context;
this
.downloadManager = DownloadManager.getInstance();
this
.downloadManager.setHandler(mHandler);
}
public
void
setListView(ListView view)
{
this
.listView = view;
}
@Override
public
int
getCount() {
return
dataList.size();
}
@Override
public
Object getItem(
int
position) {
return
dataList.get(position);
}
@Override
public
long
getItemId(
int
position) {
return
position;
}
// 改变下载按钮的样式
private
void
changeBtnStyle(Button btn,
boolean
enable)
{
if
(enable)
{
btn.setBackgroundResource(R.drawable.btn_download_norm);
}
else
{
btn.setBackgroundResource(R.drawable.btn_download_disable);
}
btn.setEnabled(enable);
}
@Override
public
View getView(
int
position, View convertView, ViewGroup parent) {
final
ViewHolder holder;
if
(
null
== convertView) {
holder =
new
ViewHolder();
convertView = inflater.inflate(R.layout.listitem_app,
null
);
holder.layout = (LinearLayout) convertView
.findViewById(R.id.gamelist_item_layout);
holder.icon = (ImageView) convertView
.findViewById(R.id.app_icon);
holder.name = (TextView) convertView
.findViewById(R.id.app_name);
holder.size = (TextView) convertView
.findViewById(R.id.app_size);
holder.btn = (Button) convertView
.findViewById(R.id.download_btn);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder) convertView.getTag();
}
// 这里position和app.id的值是相等的
final
AppFile app = dataList.get(position);
//Log.e("", "id="+app.id+", name="+app.name);
holder.name.setText(app.name);
holder.size.setText((app.downloadSize *
100
.0f / app.size) +
"%"
);
Drawable drawable = mContext.getResources().getDrawable(R.drawable.app_icon);
holder.icon.setImageDrawable(drawable);
switch
(app.downloadState)
{
case
DownloadManager.DOWNLOAD_STATE_NORMAL:
holder.btn.setText(
"下载"
);
this
.changeBtnStyle(holder.btn,
true
);
break
;
case
DownloadManager.DOWNLOAD_STATE_DOWNLOADING:
holder.btn.setText(
"下载中"
);
this
.changeBtnStyle(holder.btn,
false
);
break
;
case
DownloadManager.DOWNLOAD_STATE_FINISH:
holder.btn.setText(
"已下载"
);
this
.changeBtnStyle(holder.btn,
false
);
break
;
case
DownloadManager.DOWNLOAD_STATE_WAITING:
holder.btn.setText(
"排队中"
);
this
.changeBtnStyle(holder.btn,
false
);
break
;
}
holder.btn.setOnClickListener(
new
OnClickListener() {
@Override
public
void
onClick(View v) {
DownloadFile downloadFile =
new
DownloadFile();
downloadFile.downloadID = app.id;
downloadFile.downloadState = DownloadManager.DOWNLOAD_STATE_WAITING;
app.downloadState = DownloadManager.DOWNLOAD_STATE_WAITING;
downloadFile.downloadSize = app.downloadSize;
downloadFile.totalSize = app.size;
holder.btn.setText(
"排队中"
);
changeBtnStyle(holder.btn,
false
);
downloadManager.startDownload(downloadFile);
}
});
return
convertView;
}
static
class
ViewHolder {
LinearLayout layout;
ImageView icon;
TextView name;
TextView size;
Button btn;
}
private
Handler mHandler =
new
Handler() {
public
void
handleMessage(Message msg)
{
DownloadFile downloadFile = (DownloadFile)msg.obj;
AppFile appFile = dataList.get(downloadFile.downloadID);
appFile.downloadSize = downloadFile.downloadSize;
appFile.downloadState = downloadFile.downloadState;
// notifyDataSetChanged会执行getView函数,更新所有可视item的数据
//notifyDataSetChanged();
// 只更新指定item的数据,提高了性能
updateView(appFile.id);
}
};
// 更新指定item的数据
private
void
updateView(
int
index)
{
int
visiblePos = listView.getFirstVisiblePosition();
int
offset = index - visiblePos;
//Log.e("", "index="+index+"visiblePos="+visiblePos+"offset="+offset);
// 只有在可见区域才更新
if
(offset <
0
)
return
;
View view = listView.getChildAt(offset);
final
AppFile app = dataList.get(index);
ViewHolder holder = (ViewHolder)view.getTag();
//Log.e("", "id="+app.id+", name="+app.name);
holder.name.setText(app.name);
holder.size.setText((app.downloadSize *
100
.0f / app.size) +
"%"
);
Drawable drawable = mContext.getResources().getDrawable(R.drawable.app_icon);
holder.icon.setImageDrawable(drawable);
switch
(app.downloadState)
{
case
DownloadManager.DOWNLOAD_STATE_DOWNLOADING:
holder.btn.setText(
"下载中"
);
this
.changeBtnStyle(holder.btn,
false
);
break
;
case
DownloadManager.DOWNLOAD_STATE_FINISH:
holder.btn.setText(
"已下载"
);
this
.changeBtnStyle(holder.btn,
false
);
break
;
}
}
}
|
布局文件listitem_app.xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
LinearLayout
xmlns:android
=
"http://schemas.android.com/apk/res/android"
android:id
=
"@+id/gamelist_item_layout"
android:layout_width
=
"fill_parent"
android:layout_height
=
"wrap_content"
android:gravity
=
"center_vertical"
android:background
=
"@drawable/style_listitem_background"
android:paddingBottom
=
"5dp"
android:paddingTop
=
"5dp"
>
<
ImageView
android:id
=
"@+id/app_icon"
android:layout_width
=
"53dip"
android:layout_height
=
"53dip"
android:layout_marginLeft
=
"5dip"
android:adjustViewBounds
=
"false"
android:padding
=
"5dp"
/>
<
LinearLayout
android:layout_width
=
"match_parent"
android:layout_height
=
"60dp"
android:layout_marginLeft
=
"5dp"
android:layout_weight
=
"1"
android:gravity
=
"center_vertical"
android:orientation
=
"vertical"
>
<
TextView
android:id
=
"@+id/app_name"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:singleLine
=
"true"
android:text
=
""
android:textColor
=
"#000000"
android:textSize
=
"13sp"
/>
<
TextView
android:id
=
"@+id/app_size"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:textColor
=
"#000000"
android:textSize
=
"10sp"
/>
</
LinearLayout
>
<
Button
android:id
=
"@+id/download_btn"
android:layout_width
=
"55dip"
android:layout_height
=
"30dip"
android:layout_marginRight
=
"10dip"
android:background
=
"@drawable/style_btn_download"
android:focusable
=
"false"
android:text
=
"@string/download"
android:textColor
=
"#ffffffff"
android:textSize
=
"12sp"
/>
</
LinearLayout
>
|
listview中item样式文件style_listitem_background.xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
selector
xmlns:android
=
"http://schemas.android.com/apk/res/android"
>
<!-- 没有焦点时的背景颜色 -->
<
item
android:state_window_focused
=
"false"
>
<
shape
>
<
gradient
android:startColor
=
"#ffffff"
android:endColor
=
"#E3E3E3"
android:angle
=
"-90"
/>
</
shape
>
</
item
>
<!-- 非触摸模式下获得焦点并单击时的背景颜色 -->
<
item
android:state_focused
=
"true"
android:state_pressed
=
"true"
android:drawable
=
"@drawable/bg_listview_item_selected"
/>
<!--触摸模式下单击时的背景颜色 -->
<
item
android:state_focused
=
"false"
android:state_pressed
=
"true"
android:drawable
=
"@drawable/bg_listview_item_selected"
/>
<!--选中时的背景颜色 -->
<
item
android:state_selected
=
"true"
android:drawable
=
"@drawable/bg_listview_item_selected"
/>
<!--获得焦点时的背景 颜色-->
<
item
android:state_focused
=
"true"
android:drawable
=
"@drawable/bg_listview_item_selected"
/>
</
selector
>
|
item中的button样式文件style_btn_download.xml:
1
2
3
4
5
6
|
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
selector
xmlns:android
=
"http://schemas.android.com/apk/res/android"
>
<
item
android:state_pressed
=
"true"
android:drawable
=
"@drawable/btn_download_pressed"
/>
<
item
android:drawable
=
"@drawable/btn_download_norm"
/>
</
selector
>
|
字符文件strings.xml:
1
2
3
4
5
|
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
resources
>
<
string
name
=
"app_name"
>AndroidDownloadFile</
string
>
<
string
name
=
"download"
>下载</
string
>
</
resources
>
|
5. 最后创建MainActivity.java,源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
package
com.alexzhou.downloadfile;
import
android.app.Activity;
import
android.os.Bundle;
import
android.util.SparseArray;
import
android.widget.ListView;
public
class
MainActivity
extends
Activity
{
private
SparseArray<AppFile> appList =
new
SparseArray<AppFile>();
private
ListView listView;
@Override
public
void
onCreate(Bundle savedInstanceState)
{
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
initUI();
}
private
void
initData()
{
for
(
int
i =
0
; i<
20
; i++)
{
AppFile app =
new
AppFile();
app.name =
"快玩游戏--"
+ (i+
1
);
app.size =
100
;
app.id = i;
app.downloadState = DownloadManager.DOWNLOAD_STATE_NORMAL;
app.downloadSize =
0
;
appList.put(app.id, app);
}
}
private
void
initUI()
{
listView = (ListView)
this
.findViewById(R.id.listview);
AppListAdapter adapter =
new
AppListAdapter(
this
, appList);
adapter.setListView(listView);
listView.setAdapter(adapter);
}
@Override
protected
void
onDestroy() {
super
.onDestroy();
DownloadManager.getInstance().stopAllDownloadTask();
}
}
|
布局文件activity_main.xml:
1
2
3
4
5
6
7
8
9
10
11
12
|
<
LinearLayout
xmlns:android
=
"http://schemas.android.com/apk/res/android"
android:orientation
=
"vertical"
android:layout_width
=
"fill_parent"
android:layout_height
=
"fill_parent"
>
<
ListView
android:id
=
"@+id/listview"
android:layout_width
=
"fill_parent"
android:layout_height
=
"fill_parent"
android:fastScrollEnabled
=
"true"
/>
</
LinearLayout
>
|
到此为止,代码部分已经全部完成了,下面来看看最终效果图:
这里对比一下分别使用updateView和notifyDataSetChanged时,有什么不一样,看看打印日志:
(1)使用notifyDataSetChanged时,listview可视范围内的所有子项都更新了。
(2)使用updateView时,只更新了指定的子项。
实例源码地址:http://pan.baidu.com/share/link?shareid=229182&uk=167811495,