Android-----notifyDataSetChanged


 

你還在用notifyDataSetChanged? 你已經out了

分類: android開發   1401人閱讀   評論(0)   收藏   舉報
帖子中講述的項目使用了listView這個控件,而且自定義了adapter。在更新item的進度條時發現每次使用notifyDataSetChanged(),都會去調用自定義adapter中的getView方法。這時問題就出現了,用notifyDataSetChanged方法去更新listView中的item,是更新需要更新的Item呢?還是更新所有的item呢?如果是更新所有的item那么效率不就會很低嗎?有什么辦法可以解決這個問題呢?
  懷着心中的疑惑,我開始了這次的實驗。。。
  我的想法很簡單現實模擬遠程下載文件,創建一個Activity做主界面,主界面采用listView。然后自定義一個adapter實現BaseAdapter,再創建一個線程類,線程類當中采用循環的方式不斷的往adapter發送消息.然后使用notifyDataSetChanged方法更新界面,在調用getView方法時在控制台輸出語句,這樣我就可以知道notifyDatatSetChanged方法執行時是更新一個item還是更新所有的item了。
  有了思路就好辦了,我們先建立一個類,叫FileState。
  1. package edu.notify.viking.entity;
  2. public class FileState 
  3. {
  4. String fileName;//文件名字
  5. int completeSize;//完成的長度
  6. boolean state;//文件狀態,true為已經完成,false為未完成
  7. public String getFileName() {
  8. return fileName;
  9. }
  10. public void setFileName(String fileName) {
  11. this.fileName = fileName;
  12. }
  13. public int getCompleteSize() {
  14. return completeSize;
  15. }
  16. public void setCompleteSize(int completeSize) {
  17. this.completeSize = completeSize;
  18. }
  19. public boolean isState() {
  20. return state;
  21. }
  22. public void setState(boolean state) {
  23. this.state = state;
  24. }
  25. }
復制代碼
這個類中有3個屬性,分別是文件名字,文件已經下載的長度,還有文件當前的狀態。然后就是get與set方法。這個類的作用我想大家應該一眼就明白了,沒錯,既然使用了listView,我在主界面就想着要定義一個List,這個list當中的內容當然就是我們FileState啦。
  接着我們去實現主界面。創建主界面MainActivity
  1. package edu.notify.viking.activity;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import edu.notify.viking.adapter.MyAdapter;
  5. import edu.notify.viking.entity.FileState;
  6. import android.app.Activity;
  7. import android.os.Bundle;
  8. import android.widget.ListView;
  9. public class MainActivity extends Activity 
  10. {
  11. private List<FileState> list=new ArrayList<FileState>();
  12. private ListView listView;
  13. /** Called when the activity is first created. */
  14. @Override
  15. public void onCreate(Bundle savedInstanceState)
  16. {
  17. super.onCreate(savedInstanceState);
  18. setContentView(R.layout.main);
  19. initFileState();//先對FileState進行初始化
  20. initUI();//對界面進行初始化
  21. }
  22. /**
  23. * 把數據放進list中,因為是測試所以我手工添加數據
  24. * **/
  25. private void initFileState()
  26. {
  27. //給FileState賦值
  28. for(int i =1;i<8;i++)
  29. {
  30. FileState fileState=new FileState();
  31. fileState.setFileName(i+".mp3");//名字
  32. fileState.setCompleteSize(100);//初始化下載程度
  33. fileState.setState(true);
  34. list.add(fileState);
  35. }
  36. FileState f=new FileState();
  37. f.setFileName("8.mp3");
  38. f.setCompleteSize(0);
  39. f.setState(false);
  40. list.add(f);
  41. }
  42. private void initUI()
  43. {
  44. listView = (ListView)this.findViewById(R.id.listview);
  45. MyAdapter adapter = new MyAdapter(list,this);
  46. listView.setAdapter(adapter);
  47. adapter.setListView(listView);
  48. }
  49. }
復制代碼
因為是模擬,所以主界面很簡單,只有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. <font face="宋體">package edu.notify.viking.adapter;
  2. import java.util.List;
  3. import edu.notify.viking.activity.R;
  4. import edu.notify.viking.down.Downloader;
  5. import edu.notify.viking.entity.FileState;
  6. import android.content.Context;
  7. import android.os.Handler;
  8. import android.os.Message;
  9. import android.view.LayoutInflater;
  10. import android.view.View;
  11. import android.view.ViewGroup;
  12. import android.widget.BaseAdapter;
  13. import android.widget.ImageView;
  14. import android.widget.ListView;
  15. import android.widget.ProgressBar;
  16. import android.widget.TextView;
  17. public class MyAdapter extends BaseAdapter 
  18. {
  19. private List<FileState> list;
  20. private Context context;
  21. private LayoutInflater inflater=null;
  22. private ListView listView;
  23. private Handler mHandler = new Handler()
  24. {
  25. @Override
  26. public void handleMessage(Message msg) 
  27. {
  28. if(msg.what==1)
  29. {
  30. String name=(String)msg.obj;
  31. int length=msg.arg1;
  32. for(int i=0;i<list.size();i++)
  33. {
  34. FileState fileState=list.get(i);
  35. if(fileState.getFileName().equals(name))
  36. {
  37. fileState.setCompleteSize(length);
  38. list.set(i, fileState);
  39. break;
  40. }
  41. }
  42. notifyDataSetChanged();
  43. }
  44. }
  45. };
  46. public MyAdapter(List<FileState> list,Context context)
  47. {
  48. inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  49. this.list=list;
  50. }
  51. class ViewHolder
  52. {
  53. public TextView fileName;//文件名稱
  54. public ProgressBar progressBar;//進度條
  55. public TextView percent;//百分比
  56. public ImageView down;//下載
  57. }
  58. public int getCount() 
  59. {
  60. // TODO Auto-generated method stub
  61. return list.size();
  62. }
  63. public Object getItem(int position) 
  64. {
  65. // TODO Auto-generated method stub
  66. return list.get(position);
  67. }
  68. public long getItemId(int position) 
  69. {
  70. // TODO Auto-generated method stub
  71. return position;
  72. }
  73. public View getView(int position, View convertView, ViewGroup parent) 
  74. {
  75. ViewHolder holder;
  76. if(convertView==null)
  77. {
  78. convertView=inflater.inflate(R.layout.main_item, null);
  79. holder=new ViewHolder();
  80. holder.fileName=(TextView)convertView.findViewById(R.id.fileName);
  81. holder.progressBar=(ProgressBar)convertView.findViewById(R.id.down_progressBar);
  82. holder.percent = (TextView) convertView.findViewById(R.id.percent_text);
  83. holder.down = (ImageView) convertView.findViewById(R.id.down_view);
  84. convertView.setTag(holder);
  85. }
  86. else
  87. {
  88. holder=(ViewHolder)convertView.getTag();
  89. }
  90. FileState fileState=list.get(position);
  91. final String name = fileState.getFileName();
  92. System.out.println(name+"---run getView");
  93. //如果文件狀態為已經下載
  94. if(fileState.isState()==true)
  95. {
  96. holder.fileName.setText(fileState.getFileName());
  97. //下載完成的文件,進度條被隱藏
  98. holder.progressBar.setVisibility(ProgressBar.INVISIBLE);
  99. //設置為已下載
  100. holder.percent.setText("已下載");
  101. //下載完成的文件,下載按鈕被隱藏,防止重復下載
  102. holder.down.setVisibility(ImageView.INVISIBLE);
  103. }
  104. else
  105. {
  106. holder.fileName.setText(fileState.getFileName());
  107. holder.progressBar.setVisibility(ProgressBar.VISIBLE);
  108. holder.progressBar.setProgress(fileState.getCompleteSize());
  109. holder.percent.setText(fileState.getCompleteSize()+"%");
  110. holder.down.setOnClickListener(new View.OnClickListener()
  111. {
  112. public void onClick(View v) 
  113. {
  114. Downloader down= new Downloader(name,mHandler);
  115. down.download();
  116. }
  117. });
  118. if(fileState.getCompleteSize()==100)
  119. {
  120. holder.progressBar.setVisibility(ProgressBar.INVISIBLE);
  121. holder.percent.setText("已下載");
  122. holder.down.setVisibility(ProgressBar.INVISIBLE);
  123. fileState.setState(true);
  124. list.set(position, fileState);
  125. }
  126. }
  127. return convertView;
  128. }
  129. public void setListView(ListView listView) {
  130. this.listView = listView;
  131. }
  132. }
  133. </font>
復制代碼
為了運行的效率,我在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. import android.os.Handler;
  4. import android.os.Message;
  5. public class Downloader 
  6. {
  7. private String fileName;
  8. private Handler mHandler;
  9. public Downloader(String fileName, Handler handler)
  10. {
  11. super();
  12. this.fileName = fileName;
  13. mHandler = handler;
  14. }
  15. public void download()
  16. {
  17. new MyThread().start();
  18. }
  19. class MyThread extends Thread
  20. {
  21. @Override
  22. public void run()
  23. {
  24. for(int i=0;i<=100;i++)
  25. {
  26. System.out.println("i:"+i);
  27. try {
  28. this.currentThread().sleep(1000);
  29. } catch (InterruptedException e) {
  30. // TODO Auto-generated catch block
  31. e.printStackTrace();
  32. }
  33. Message msg=Message.obtain();
  34. msg.what=1;
  35. msg.obj=fileName;
  36. msg.arg1=i;
  37. mHandler.sendMessage(msg);
  38. }
  39. }
  40. }
  41. }
復制代碼
這個類用於開啟一個單獨的線程來模擬下載的環節。其中的內容很簡單,無非是接收從adapter中傳遞過來的數據,當用戶點擊下載圖標時,執行download方法。在線程類的run方法中,我們做了一個想0-100的循環,當然為了讓大家看到進度條的更新,我們讓線程每次休眠1秒鍾。然后用adapter對象中傳遞過來的handler對象發送message。這時候我們的adapter類中的handleMessage方法就可以收到消息,並進行處理,最后調用notifyDataSetChanged方法了。好的,我們把xml文件也先給大家。
  先是main.xml
  
  1. <font face="宋體"><?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>
  14. </font>
復制代碼
  然后是main_item.xml
  
  1. <font face="宋體"><?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></font>
復制代碼
   這兩個布局文件很簡單,大家一看就明白了,現在我們來測試一下,看看notifyDataSetChanged方法后,getView一共會執行幾次?
1.jpg 
2012-7-25 20:28 上傳
下載附件 (362.59 KB)
 

  從圖中我們可以看見,盡管我們只是想更新8.mp3這個item的進度條,但是所有的進度條都被更新了,每次使用notifyDataSetChanged方法,對會調用8次getView方法。天哪。這個效率。。。
  這不是我們想要的,我們想要的是什么?我們想要的就是如果只需要更新8.mp3這個item,那么其他的item將保持不變,不需要更新他們。那么我們該怎么解決這個問題呢?
  其實這個問題的解決也不麻煩,所謂難者不會,會者不難啊。我們只需要修改adapter這個類,在其中添加一個方法即可。現在我們來看更新后的adapter類。
  1. <font face="宋體">package edu.notify.viking.adapter;
  2. import java.util.List;
  3. import edu.notify.viking.activity.R;
  4. import edu.notify.viking.down.Downloader;
  5. import edu.notify.viking.entity.FileState;
  6. import android.content.Context;
  7. import android.os.Handler;
  8. import android.os.Message;
  9. import android.view.LayoutInflater;
  10. import android.view.View;
  11. import android.view.ViewGroup;
  12. import android.widget.BaseAdapter;
  13. import android.widget.ImageView;
  14. import android.widget.ListView;
  15. import android.widget.ProgressBar;
  16. import android.widget.TextView;
  17. public class MyAdapter extends BaseAdapter 
  18. {
  19. private List<FileState> list;
  20. private Context context;
  21. private LayoutInflater inflater=null;
  22. private ListView listView;
  23. private Handler mHandler = new Handler()
  24. {
  25. @Override
  26. public void handleMessage(Message msg) 
  27. {
  28. if(msg.what==1)
  29. {
  30. String name=(String)msg.obj;
  31. int length=msg.arg1;
  32. for(int i=0;i<list.size();i++)
  33. {
  34. FileState fileState=list.get(i);
  35. if(fileState.getFileName().equals(name))
  36. {
  37. fileState.setCompleteSize(length);
  38. list.set(i, fileState);
  39. updateView(i);//用我們自己寫的方法 
  40. break;
  41. }
  42. }
  43. //notifyDataSetChanged();不用了
  44. }
  45. }
  46. };
  47. public MyAdapter(List<FileState> list,Context context)
  48. {
  49. inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  50. this.list=list;
  51. }
  52. class ViewHolder
  53. {
  54. public TextView fileName;//文件名稱
  55. public ProgressBar progressBar;//進度條
  56. public TextView percent;//百分比
  57. public ImageView down;//下載
  58. }
  59. /**
  60. * 用於更新我們想要更新的item
  61. * @param itemIndex 想更新item的下標
  62. * **/
  63. private void updateView(int itemIndex)
  64. { 
  65. //得到第1個可顯示控件的位置,記住是第1個可顯示控件噢。而不是第1個控件
  66. int visiblePosition = listView.getFirstVisiblePosition(); 
  67. //得到你需要更新item的View
  68. View view = listView.getChildAt(itemIndex - visiblePosition);
  69. FileState fileState=list.get(itemIndex);
  70. final String name=fileState.getFileName();
  71. System.out.println(name+"---run updateView");
  72. if(fileState.isState()==false)
  73. {
  74. ViewHolder holderOne=new ViewHolder();
  75. //start:初始化
  76. holderOne.fileName=(TextView)view.findViewById(R.id.fileName);
  77. holderOne.progressBar=(ProgressBar)view.findViewById(R.id.down_progressBar);
  78. holderOne.percent = (TextView) view.findViewById(R.id.percent_text);
  79. holderOne.down = (ImageView) view.findViewById(R.id.down_view);
  80. //end
  81. holderOne.fileName.setText(fileState.getFileName());
  82. holderOne.progressBar.setVisibility(ProgressBar.VISIBLE);
  83. holderOne.progressBar.setProgress(fileState.getCompleteSize());
  84. holderOne.percent.setText(fileState.getCompleteSize()+"%");
  85. holderOne.down.setOnClickListener(new View.OnClickListener()
  86. {
  87. public void onClick(View v) 
  88. {
  89. Downloader down= new Downloader(name,mHandler);
  90. down.download();
  91. }
  92. });
  93. if(fileState.getCompleteSize()==100)
  94. {
  95. holderOne.progressBar.setVisibility(ProgressBar.INVISIBLE);
  96. holderOne.percent.setText("已下載");
  97. holderOne.down.setVisibility(ProgressBar.INVISIBLE);
  98. fileState.setState(true);
  99. list.set(itemIndex, fileState);
  100. }
  101. }
  102. } 
  103. public int getCount() 
  104. {
  105. // TODO Auto-generated method stub
  106. return list.size();
  107. }
  108. public Object getItem(int position) 
  109. {
  110. // TODO Auto-generated method stub
  111. return list.get(position);
  112. }
  113. public long getItemId(int position) 
  114. {
  115. // TODO Auto-generated method stub
  116. return position;
  117. }
  118. public View getView(int position, View convertView, ViewGroup parent) 
  119. {
  120. ViewHolder holder;
  121. if(convertView==null)
  122. {
  123. convertView=inflater.inflate(R.layout.main_item, null);
  124. holder=new ViewHolder();
  125. holder.fileName=(TextView)convertView.findViewById(R.id.fileName);
  126. holder.progressBar=(ProgressBar)convertView.findViewById(R.id.down_progressBar);
  127. holder.percent = (TextView) convertView.findViewById(R.id.percent_text);
  128. holder.down = (ImageView) convertView.findViewById(R.id.down_view);
  129. convertView.setTag(holder);
  130. }
  131. else
  132. {
  133. holder=(ViewHolder)convertView.getTag();
  134. }
  135. FileState fileState=list.get(position);
  136. final String name = fileState.getFileName();
  137. System.out.println(name+"---run getView");
  138. //如果文件狀態為已經下載
  139. if(fileState.isState()==true)
  140. {
  141. holder.fileName.setText(fileState.getFileName());
  142. //下載完成的文件,進度條被隱藏
  143. holder.progressBar.setVisibility(ProgressBar.INVISIBLE);
  144. //設置為已下載
  145. holder.percent.setText("已下載");
  146. //下載完成的文件,下載按鈕被隱藏,防止重復下載
  147. holder.down.setVisibility(ImageView.INVISIBLE);
  148. }
  149. else
  150. {
  151. holder.fileName.setText(fileState.getFileName());
  152. holder.progressBar.setVisibility(ProgressBar.VISIBLE);
  153. holder.progressBar.setProgress(fileState.getCompleteSize());
  154. holder.percent.setText(fileState.getCompleteSize()+"%");
  155. holder.down.setOnClickListener(new View.OnClickListener()
  156. {
  157. public void onClick(View v) 
  158. {
  159. Downloader down= new Downloader(name,mHandler);
  160. down.download();
  161. }
  162. });
  163. if(fileState.getCompleteSize()==100)
  164. {
  165. holder.progressBar.setVisibility(ProgressBar.INVISIBLE);
  166. holder.percent.setText("已下載");
  167. holder.down.setVisibility(ProgressBar.INVISIBLE);
  168. fileState.setState(true);
  169. list.set(position, fileState);
  170. }
  171. }
  172. return convertView;
  173. }
  174. public void setListView(ListView listView) {
  175. this.listView = listView;
  176. }
  177. }
  178. </font>
復制代碼
  在類中我們對handleMessage做了一點修改。
   3.jpg 
2012-7-25 21:22 上傳
下載附件 (85.08 KB)
 

  看到紅色箭頭的地方就是修改過的。然后多添加了一個updateView方法。這個方法需要傳入你想更新的item下標。
  最核心的地方是這2句代碼。
    4.jpg 
2012-7-25 20:56 上傳
下載附件 (21.68 KB)
 

  主要的意思就是根據你想要更新的item下標去得到這個item的View對象,這樣你就可以為所欲為啦。哈哈哈哈哈。。。
  咱們來看看運行效果。
   5.jpg 
2012-7-25 21:08 上傳
下載附件 (280.63 KB)
 

  這里我改為同時更新2個item,看看這樣會不會出錯,可以看到,運行的很正常。控制台的打印結果也只是更新了7-8.mp3這2個item。
  好了,文章到此已經結束了,肯定有人要罵我了,這么簡單的東西,你啰嗦了半天。整的我都沒怎么聽懂。  不管怎樣,都要感謝CCTV,MTV,還有KTV。當然還有我們安卓巴士提供的這個平台。希望安卓巴士越辦越好。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM