分享

android listview局部刷新和模拟应用下载 | Alex Zhou的程序世界

 飞鹰飞龙飞天 2014-05-08

在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./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./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./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./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

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多