android listview局部刷新和模拟应用下载

转自:http://codingnow.cn/android/1059.html 


在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,


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值