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

来源:互联网 发布:软件行业新技术 编辑:程序博客网 时间:2024/06/07 00:00

在android开发中,listview是比较常用的一个组件,在listview的数据需要更新的时候,一般会用notifyDataSetChanged()这个函数,但是它会更新listview中所有可视范围内的item,这样对性能肯定会有影响。比较常见的情景是android应用商店中的下载列表,当我们下载一款游戏的时候,只需要更新这款游戏对应的进度就可以了。本文就来模拟android应用商店的游戏下载,实现对listview的局部刷新,只实现一个简单的demo,不去真的下载文件。
1. 首先来创建代表应用商店中的app文件的类:AppFile.java,包含了一些基本的属性,源码:

[java] view plaincopyprint?
  1. package com.alexzhou.downloadfile;  
  2.     
  3. /** 
  4.  * author:alexzhou  
  5.  * email :zhoujiangbohai@163.com  
  6.  * date :2013-1-27 
  7.  *  
  8.  * 游戏列表中的app文件 
  9.  **/  
  10.     
  11. public class AppFile {  
  12.     
  13.     public int id;  
  14.     public String name;  
  15.     // app的大小  
  16.     public int size;  
  17.     // 已下载大小  
  18.     public int downloadSize;  
  19.     // 下载状态:正常,正在下载,暂停,等待,已下载  
  20.     public int downloadState;  
  21. }  


2. 由于实际开发时,AppFile的属性比较多,这里创建一个辅助类:DownloadFile.java,代表下载中的文件,源码:

[java] view plaincopyprint?
  1. package com.alexzhou.downloadfile;  
  2.     
  3. /** 
  4.  * author:alexzhou  
  5.  * email :zhoujiangbohai@163.com  
  6.  * date :2013-1-27 
  7.  *  
  8.  * 下载的文件 
  9.  **/  
  10.     
  11. public class DownloadFile {  
  12.     
  13.     public int downloadID;  
  14.     public int downloadSize;  
  15.     public int totalSize;  
  16.     public int downloadState;  
  17. }  


3. 接下来需要一个下载管理类:DownloadManager.java,它管理所有下载任务。当同时下载很多任务的时候,界面会卡,所以指定只能同时下载3个任务,每个任务会启动一个线程,这里使用了ExecutorService线程池。当提交了超过三个下载任务时,只执行前3个任务,第四个任务会等到前面有一个下载完成后再下载,以此类推。这里还用到了android提供的一个工具类SparseArray,它是用来替代HashMap的,性能比HashMap要好。下面看源码:

[java] view plaincopyprint?
  1. package com.alexzhou.downloadfile;  
  2.     
  3. import java.util.ArrayList;  
  4. import java.util.concurrent.ExecutorService;  
  5. import java.util.concurrent.Executors;  
  6.     
  7. import android.os.Handler;  
  8. import android.os.Message;  
  9. import android.util.Log;  
  10. import android.util.SparseArray;  
  11.     
  12. /** 
  13. author:alexzhou  
  14. email :zhoujiangbohai@163.com 
  15. date  :2013-1-27 
  16.    
  17. 下载管理 
  18.  **/  
  19.     
  20. public class DownloadManager {  
  21.     
  22.     // 下载状态:正常,暂停,下载中,已下载,排队中  
  23.     public static final int DOWNLOAD_STATE_NORMAL = 0x00;  
  24.     public static final int DOWNLOAD_STATE_PAUSE = 0x01;  
  25.     public static final int DOWNLOAD_STATE_DOWNLOADING = 0x02;  
  26.     public static final int DOWNLOAD_STATE_FINISH = 0x03;  
  27.     public static final int DOWNLOAD_STATE_WAITING = 0x04;  
  28.     
  29.     // SparseArray是android中替代Hashmap的类,可以提高效率  
  30.     private SparseArray<DownloadFile> downloadFiles = new SparseArray<DownloadFile>();  
  31.     // 用来管理所有下载任务  
  32.     private ArrayList<DownloadTask> taskList = new ArrayList<DownloadTask>();  
  33.     private Handler mHandler;  
  34.     private final static Object syncObj = new Object();  
  35.     private static DownloadManager instance;  
  36.     private ExecutorService executorService;  
  37.     
  38.     private DownloadManager()  
  39.     {  
  40.         // 最多只能同时下载3个任务,其余的任务排队等待  
  41.         executorService = Executors.newFixedThreadPool(3);  
  42.     }  
  43.     
  44.     public static DownloadManager getInstance()  
  45.     {  
  46.         if(null == instance)  
  47.         {  
  48.             synchronized(syncObj) {  
  49.                 instance = new DownloadManager();  
  50.             }  
  51.             return instance;  
  52.         }  
  53.         return instance;  
  54.     }  
  55.     
  56.     public void setHandler(Handler handler) {  
  57.         this.mHandler =  handler;  
  58.     }  
  59.     
  60.     // 开始下载,创建一个下载线程  
  61.     public void startDownload(DownloadFile file) {  
  62.         downloadFiles.put(file.downloadID, file);  
  63.         DownloadTask task = new DownloadTask(file.downloadID);  
  64.         taskList.add(task);  
  65.         executorService.submit(task);  
  66.     }  
  67.     
  68.     public void stopAllDownloadTask() {  
  69.         while(taskList.size() != 0)  
  70.         {  
  71.             DownloadTask task = taskList.remove(0);  
  72.             // 可以在这里做其他的处理  
  73.             task.stopTask();  
  74.         }  
  75.         // 会停止正在进行的任务和拒绝接受新的任务  
  76.         executorService.shutdownNow();  
  77.     
  78.     }  
  79.     
  80.     // 下载任务  
  81.     class DownloadTask implements Runnable {  
  82.     
  83.         private boolean isWorking = false;  
  84.         private int downloadId;  
  85.     
  86.         public DownloadTask(int id)  
  87.         {  
  88.             this.isWorking = true;  
  89.             this.downloadId = id;  
  90.         }  
  91.     
  92.         public void stopTask()  
  93.         {  
  94.             this.isWorking = false;  
  95.         }  
  96.     
  97.         // 更新listview中对应的item  
  98.         public void update(DownloadFile downloadFile)  
  99.         {  
  100.             Message msg = mHandler.obtainMessage();  
  101.             if(downloadFile.totalSize == downloadFile.downloadSize)  
  102.                 downloadFile.downloadState = DOWNLOAD_STATE_FINISH;  
  103.             msg.obj = downloadFile;  
  104.             msg.sendToTarget();  
  105.     
  106.         }  
  107.     
  108.         public void run() {  
  109.             // 更新下载文件的状态  
  110.             DownloadFile downloadFile = downloadFiles.get(downloadId);  
  111.             downloadFile.downloadState = DOWNLOAD_STATE_DOWNLOADING;  
  112.             while(isWorking)  
  113.             {  
  114.                 // 检测是否下载完成  
  115.                 if(downloadFile.downloadState != DOWNLOAD_STATE_DOWNLOADING)  
  116.                 {  
  117.                     downloadFiles.remove(downloadFile.downloadID);  
  118.                     taskList.remove(this);  
  119.                     isWorking = false;  
  120.                     break;  
  121.                 }  
  122.                 //Log.e("", "downloadSize="+downloadFile.downloadSize+"; size="+downloadFile.totalSize);  
  123.                 // 这里只是模拟了下载,每一秒更新一次item的下载状态  
  124.                 if(downloadFile.downloadSize <= downloadFile.totalSize)  
  125.                 {  
  126.                     this.update(downloadFile);  
  127.                 }  
  128.     
  129.                 if(downloadFile.downloadSize < downloadFile.totalSize)  
  130.                 {  
  131.                     try {  
  132.                         Thread.sleep(100);  
  133.                     } catch (InterruptedException e) {  
  134.                         e.printStackTrace();  
  135.                         downloadFile.downloadState = DOWNLOAD_STATE_PAUSE;  
  136.                         this.update(downloadFile);  
  137.                         downloadFiles.remove(downloadId);  
  138.                         isWorking = false;  
  139.                         break;  
  140.                     }  
  141.     
  142.                     ++ downloadFile.downloadSize;  
  143.                 }  
  144.             }  
  145.     
  146.         }  
  147.     }  
  148.     
  149. }  


4. 接下来就需要实现listview的adapter了,这里比较重要的一个函数是updateView,这是实现listview局部刷新的关键,通过索引index得到listview中对应位置的子view,然后再更新该view的数据。源码:

[java] view plaincopyprint?
  1. package com.alexzhou.downloadfile;  
  2.     
  3. import android.content.Context;  
  4. import android.graphics.drawable.Drawable;  
  5. import android.os.Handler;  
  6. import android.os.Message;  
  7. import android.util.Log;  
  8. import android.util.SparseArray;  
  9. import android.view.LayoutInflater;  
  10. import android.view.View;  
  11. import android.view.View.OnClickListener;  
  12. import android.view.ViewGroup;  
  13. import android.widget.BaseAdapter;  
  14. import android.widget.Button;  
  15. import android.widget.ImageView;  
  16. import android.widget.LinearLayout;  
  17. import android.widget.ListView;  
  18. import android.widget.TextView;  
  19.     
  20. /** 
  21. author:alexzhou  
  22. email :zhoujiangbohai@163.com 
  23. date  :2013-1-27 
  24.    
  25. app列表的数据适配器 
  26.  **/  
  27.     
  28. public class AppListAdapter extends BaseAdapter {  
  29.     
  30.     private SparseArray<AppFile> dataList = null;  
  31.     private LayoutInflater inflater = null;  
  32.     private Context mContext;  
  33.     private DownloadManager downloadManager;  
  34.     private ListView listView;  
  35.     
  36.     public AppListAdapter(Context context, SparseArray<AppFile> dataList) {  
  37.         this.inflater = (LayoutInflater) context  
  38.                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
  39.         this.dataList = dataList;  
  40.         this.mContext = context;  
  41.         this.downloadManager = DownloadManager.getInstance();  
  42.         this.downloadManager.setHandler(mHandler);  
  43.     }  
  44.     
  45.     public void setListView(ListView view)  
  46.     {  
  47.         this.listView = view;  
  48.     }  
  49.     
  50.     @Override  
  51.     public int getCount() {  
  52.         return dataList.size();  
  53.     }  
  54.     
  55.     @Override  
  56.     public Object getItem(int position) {  
  57.         return dataList.get(position);  
  58.     }  
  59.     
  60.     @Override  
  61.     public long getItemId(int position) {  
  62.         return position;  
  63.     }  
  64.     
  65.     // 改变下载按钮的样式  
  66.     private void changeBtnStyle(Button btn, boolean enable)  
  67.     {  
  68.         if(enable)  
  69.         {  
  70.             btn.setBackgroundResource(R.drawable.btn_download_norm);  
  71.         }  
  72.         else  
  73.         {  
  74.             btn.setBackgroundResource(R.drawable.btn_download_disable);  
  75.         }  
  76.         btn.setEnabled(enable);  
  77.     }  
  78.     
  79.     @Override  
  80.     public View getView(int position, View convertView, ViewGroup parent) {  
  81.     
  82.         final ViewHolder holder;  
  83.         if (null == convertView) {  
  84.             holder = new ViewHolder();  
  85.             convertView = inflater.inflate(R.layout.listitem_app, null);  
  86.             holder.layout = (LinearLayout) convertView  
  87.                     .findViewById(R.id.gamelist_item_layout);  
  88.             holder.icon = (ImageView) convertView  
  89.                     .findViewById(R.id.app_icon);  
  90.             holder.name = (TextView) convertView  
  91.                     .findViewById(R.id.app_name);  
  92.             holder.size = (TextView) convertView  
  93.                     .findViewById(R.id.app_size);  
  94.             holder.btn = (Button) convertView  
  95.                     .findViewById(R.id.download_btn);  
  96.             convertView.setTag(holder);  
  97.         } else {  
  98.             holder = (ViewHolder) convertView.getTag();  
  99.         }  
  100.     
  101.         // 这里position和app.id的值是相等的  
  102.         final AppFile app = dataList.get(position);  
  103.         //Log.e("", "id="+app.id+", name="+app.name);  
  104.     
  105.         holder.name.setText(app.name);  
  106.         holder.size.setText((app.downloadSize * 100.0f / app.size) + "%");  
  107.     
  108.         Drawable drawable = mContext.getResources().getDrawable(R.drawable.app_icon);  
  109.         holder.icon.setImageDrawable(drawable);  
  110.     
  111.         switch(app.downloadState)  
  112.         {  
  113.         case DownloadManager.DOWNLOAD_STATE_NORMAL:  
  114.             holder.btn.setText("下载");  
  115.             this.changeBtnStyle(holder.btn, true);  
  116.             break;  
  117.         case DownloadManager.DOWNLOAD_STATE_DOWNLOADING:  
  118.             holder.btn.setText("下载中");  
  119.             this.changeBtnStyle(holder.btn, false);  
  120.             break;  
  121.         case DownloadManager.DOWNLOAD_STATE_FINISH:  
  122.             holder.btn.setText("已下载");  
  123.             this.changeBtnStyle(holder.btn, false);  
  124.             break;  
  125.         case DownloadManager.DOWNLOAD_STATE_WAITING:  
  126.             holder.btn.setText("排队中");  
  127.             this.changeBtnStyle(holder.btn, false);  
  128.             break;  
  129.         }  
  130.         holder.btn.setOnClickListener(new OnClickListener() {  
  131.             @Override  
  132.             public void onClick(View v) {  
  133.                 DownloadFile downloadFile = new DownloadFile();  
  134.                 downloadFile.downloadID = app.id;  
  135.                 downloadFile.downloadState = DownloadManager.DOWNLOAD_STATE_WAITING;  
  136.                 app.downloadState = DownloadManager.DOWNLOAD_STATE_WAITING;  
  137.                 downloadFile.downloadSize = app.downloadSize;  
  138.                 downloadFile.totalSize = app.size;  
  139.                 holder.btn.setText("排队中");  
  140.                 changeBtnStyle(holder.btn, false);  
  141.                 downloadManager.startDownload(downloadFile);  
  142.             }  
  143.         });  
  144.         return convertView;  
  145.     }  
  146.     
  147.     static class ViewHolder {  
  148.         LinearLayout layout;  
  149.         ImageView icon;  
  150.         TextView name;  
  151.         TextView size;  
  152.         Button btn;  
  153.     }  
  154.     
  155.     private Handler mHandler = new Handler() {  
  156.     
  157.         public void handleMessage(Message msg)  
  158.         {  
  159.             DownloadFile downloadFile = (DownloadFile)msg.obj;  
  160.             AppFile appFile = dataList.get(downloadFile.downloadID);  
  161.             appFile.downloadSize = downloadFile.downloadSize;  
  162.             appFile.downloadState = downloadFile.downloadState;  
  163.     
  164.             // notifyDataSetChanged会执行getView函数,更新所有可视item的数据  
  165.             //notifyDataSetChanged();  
  166.             // 只更新指定item的数据,提高了性能  
  167.             updateView(appFile.id);  
  168.         }  
  169.     };  
  170.     
  171.     // 更新指定item的数据  
  172.     private void updateView(int index)  
  173.     {  
  174.         int visiblePos = listView.getFirstVisiblePosition();  
  175.         int offset = index - visiblePos;  
  176.         //Log.e("", "index="+index+"visiblePos="+visiblePos+"offset="+offset);  
  177.         // 只有在可见区域才更新  
  178.         if(offset < 0return;  
  179.     
  180.         View view = listView.getChildAt(offset);  
  181.         final AppFile app = dataList.get(index);  
  182.         ViewHolder holder = (ViewHolder)view.getTag();  
  183.         //Log.e("", "id="+app.id+", name="+app.name);  
  184.     
  185.         holder.name.setText(app.name);  
  186.         holder.size.setText((app.downloadSize * 100.0f / app.size) + "%");  
  187.         Drawable drawable = mContext.getResources().getDrawable(R.drawable.app_icon);  
  188.         holder.icon.setImageDrawable(drawable);  
  189.     
  190.         switch(app.downloadState)  
  191.         {  
  192.         case DownloadManager.DOWNLOAD_STATE_DOWNLOADING:  
  193.             holder.btn.setText("下载中");  
  194.             this.changeBtnStyle(holder.btn, false);  
  195.             break;  
  196.         case DownloadManager.DOWNLOAD_STATE_FINISH:  
  197.             holder.btn.setText("已下载");  
  198.             this.changeBtnStyle(holder.btn, false);  
  199.             break;  
  200.         }  
  201.     
  202.     }  
  203. }  


布局文件listitem_app.xml:

[html] view plaincopyprint?
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:id="@+id/gamelist_item_layout"  
  4.     android:layout_width="fill_parent"  
  5.     android:layout_height="wrap_content"  
  6.     android:gravity="center_vertical"  
  7.     android:background="@drawable/style_listitem_background"  
  8.     android:paddingBottom="5dp"  
  9.     android:paddingTop="5dp" >  
  10.     
  11.     <ImageView  
  12.         android:id="@+id/app_icon"  
  13.         android:layout_width="53dip"  
  14.         android:layout_height="53dip"  
  15.         android:layout_marginLeft="5dip"  
  16.         android:adjustViewBounds="false"  
  17.         android:padding="5dp" />  
  18.     
  19.     <LinearLayout  
  20.         android:layout_width="match_parent"  
  21.         android:layout_height="60dp"  
  22.         android:layout_marginLeft="5dp"  
  23.         android:layout_weight="1"  
  24.         android:gravity="center_vertical"  
  25.         android:orientation="vertical" >  
  26.     
  27.         <TextView  
  28.             android:id="@+id/app_name"  
  29.             android:layout_width="wrap_content"  
  30.             android:layout_height="wrap_content"  
  31.             android:singleLine="true"  
  32.             android:text=""  
  33.             android:textColor="#000000"  
  34.             android:textSize="13sp" />  
  35.     
  36.         <TextView  
  37.             android:id="@+id/app_size"  
  38.             android:layout_width="wrap_content"  
  39.             android:layout_height="wrap_content"  
  40.             android:textColor="#000000"  
  41.             android:textSize="10sp" />  
  42.     
  43.     </LinearLayout>  
  44.     
  45.     <Button  
  46.         android:id="@+id/download_btn"  
  47.         android:layout_width="55dip"  
  48.         android:layout_height="30dip"  
  49.         android:layout_marginRight="10dip"  
  50.         android:background="@drawable/style_btn_download"  
  51.         android:focusable="false"  
  52.         android:text="@string/download"  
  53.         android:textColor="#ffffffff"  
  54.         android:textSize="12sp" />  
  55.     
  56. </LinearLayout>  


listview中item样式文件style_listitem_background.xml:

[html] view plaincopyprint?
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <selector xmlns:android="http://schemas.android.com/apk/res/android">  
  3.    <!-- 没有焦点时的背景颜色 -->  
  4.   <item android:state_window_focused="false"  >  
  5.       <shape>    
  6.         <gradient    
  7.             android:startColor="#ffffff"    
  8.             android:endColor="#E3E3E3"    
  9.             android:angle="-90" />    
  10.       </shape>   
  11.   </item>  
  12.     
  13.    <!-- 非触摸模式下获得焦点并单击时的背景颜色 -->       
  14.   <item android:state_focused="true" android:state_pressed="true"   
  15.         android:drawable="@drawable/bg_listview_item_selected" />  
  16.    <!--触摸模式下单击时的背景颜色  -->   
  17.   <item android:state_focused="false" android:state_pressed="true"   
  18.         android:drawable="@drawable/bg_listview_item_selected" />   
  19.    <!--选中时的背景颜色  -->  
  20.   <item android:state_selected="true"  android:drawable="@drawable/bg_listview_item_selected" />   
  21.   <!--获得焦点时的背景  颜色-->  
  22.   <item android:state_focused="true" android:drawable="@drawable/bg_listview_item_selected" />   
  23. </selector>  


item中的button样式文件style_btn_download.xml:

[html] view plaincopyprint?
  1. ?xml version="1.0" encoding="utf-8"?>  
  2. <selector xmlns:android="http://schemas.android.com/apk/res/android">  
  3.     <item android:state_pressed="true"  
  4.         android:drawable="@drawable/btn_download_pressed" />  
  5.     <item android:drawable="@drawable/btn_download_norm" />  
  6. </selector>  


字符文件strings.xml:

[html] view plaincopyprint?
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.     <string name="app_name">AndroidDownloadFile</string>  
  4.     <string name="download">下载</string>  
  5. </resources>  


5. 最后创建MainActivity.java,源码:

[java] view plaincopyprint?
  1. package com.alexzhou.downloadfile;  
  2.     
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.util.SparseArray;  
  6. import android.widget.ListView;  
  7.     
  8. public class MainActivity extends Activity   
  9. {  
  10.     private SparseArray<AppFile> appList = new SparseArray<AppFile>();  
  11.     
  12.     private ListView listView;  
  13.     
  14.     @Override  
  15.     public void onCreate(Bundle savedInstanceState)  
  16.     {  
  17.         super.onCreate(savedInstanceState);  
  18.         setContentView(R.layout.activity_main);  
  19.         initData();  
  20.         initUI();  
  21.     }  
  22.     
  23.     private void initData()  
  24.     {  
  25.         for(int i =0; i<20; i++)  
  26.         {  
  27.             AppFile app = new AppFile();  
  28.             app.name = "快玩游戏--" + (i+1);  
  29.             app.size = 100;  
  30.             app.id = i;  
  31.             app.downloadState = DownloadManager.DOWNLOAD_STATE_NORMAL;  
  32.             app.downloadSize = 0;  
  33.             appList.put(app.id, app);  
  34.         }  
  35.     }  
  36.     
  37.     private void initUI()  
  38.     {  
  39.         listView = (ListView)this.findViewById(R.id.listview);  
  40.         AppListAdapter adapter = new AppListAdapter(this, appList);  
  41.         adapter.setListView(listView);  
  42.         listView.setAdapter(adapter);  
  43.     }  
  44.     
  45.     @Override  
  46.     protected void onDestroy() {  
  47.         super.onDestroy();  
  48.         DownloadManager.getInstance().stopAllDownloadTask();  
  49.     }  
  50.     
  51. }  


布局文件activity_main.xml:

[html] view plaincopyprint?
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2. android:orientation="vertical"  
  3. android:layout_width="fill_parent"  
  4. android:layout_height="fill_parent"  
  5. >  
  6. <ListView  
  7. android:id="@+id/listview"  
  8. android:layout_width="fill_parent"  
  9. android:layout_height="fill_parent"  
  10. android:fastScrollEnabled="true"  
  11. />  
  12. </LinearLayout>  


到此为止,代码部分已经全部完成了,下面来看看最终效果图:

这里对比一下分别使用updateView和notifyDataSetChanged时,有什么不一样,看看打印日志:
(1)使用notifyDataSetChanged时,listview可视范围内的所有子项都更新了。

(2)使用updateView时,只更新了指定的子项。

实例源码地址:http://pan.baidu.com/share/link?shareid=229182&uk=167811495,

0 0