=======================ListView原理==============================
Android 的 ListView 的原理打個簡單的比喻就是:
演員演小品(假設演員都長一樣,每個角色任何演員都可以演)
小品劇不需要為每個角色都招募一個演員。ListView 也沒必要為每一個 Item 創建 View 對象。
小品劇的演員在一個角色表演完成后,會在后台換下一個角色的服裝,等待需要表演的時候再出場。
ListView 會讓未顯示的 View 填充數據后緩存在后台,等待滑動時再將它顯示出來。
小品演員換個服裝就成了另一個角色,所以不能以角色來判斷是哪個演員。
ListView 中的 Item 的樣式是隨填充的數據動態變化的,所以不能以某個樣式作為Item的標識。
如果你是導演,你要警察這個角色在白領抬手時雙手舉起,你會怎們做?如果你找上次演過警察的那個演員,告訴他你在白領抬手時將雙手舉起。
那么有三個結果:一、他仍然演警察、他出色的完成了表演。二、他演廚師,結果白領抬手時廚師舉起了手。三、他沒上台,結果警察沒舉手。
ListView 中如果你根據某個 Item 的狀態來獲取它的 View 對象,通過線程改變它的狀態,就會發生這三種情況。
那么要如何做呢?當然是告訴所有演員,誰扮演警察誰就在白領抬手時雙手舉起。那表演時如何判斷誰演的是警察呢?警察帽子就是標志,誰戴着誰是警察。
ListView 中你要獲取所有的 View 對象的集合,並為每一個 View 設置標識,傳遞需要更新狀態的視圖標識。更新前,在集合中找到標識匹配的 View 對象,讓它做出相應的更新操作。
=====================ListView 中更新 ProgressBar=========================
知道這個 ListView 這個特性之后,就可以動手開始寫了:
布局文件

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:id="@+id/list_view" android:layout_width="match_parent" android:layout_height="match_parent"> </ListView> </RelativeLayout>

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="50dp" android:orientation="horizontal"> <ProgressBar android:id="@+id/progress_bar" style="@style/Widget.AppCompat.ProgressBar.Horizontal" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:max="100" /> <Button android:id="@+id/btn" android:layout_width="wrap_content" android:layout_height="match_parent" android:text="download" /> </LinearLayout>
Java文件

public class MainActivity extends AppCompatActivity { private ListView listView; //列表控件 private List<MyObject> data; //數據源(模擬) private MyAdapter adapter; //自定義適配器 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); /* 初始化控件 */ listView = (ListView) findViewById(R.id.list_view); /* 初始化數據 */ data = new ArrayList<>(); for (int i = 0; i < 30; i++) { //組裝數據 MyObject myObject = new MyObject(); myObject.text = "按鈕" + i; myObject.progress = -1; //添加到數據源 data.add(myObject); } /* 填充適配器 */ adapter = new MyAdapter(this, data); listView.setAdapter(adapter); } /** * 實體對象,用於保存數據 */ class MyObject { Integer progress; //下載進度 String text; //按鈕文字 } }

public class MyAdapter extends BaseAdapter { private Context context; //上下文對象用於視圖填充 private List<MainActivity.MyObject> data; //需要適配的數據源 private List<View> viewList; //View對象集合 public MyAdapter(Context context, List<MainActivity.MyObject> data) { this.viewList = new ArrayList<>(); this.context = context; this.data = data; } @Override public int getCount() { return data.size(); } @Override public Object getItem(int position) { return data.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(final int position, View convertView, ViewGroup parent) { final ViewHolder viewHolder; /* 初始化控件 */ if (convertView == null) { convertView = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false); viewHolder = new ViewHolder(); viewHolder.progressBar = (ProgressBar) convertView.findViewById(R.id.progress_bar); viewHolder.button = (Button) convertView.findViewById(R.id.btn); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } /* 添加控件樣式 */ final MainActivity.MyObject myObject = data.get(position); viewHolder.button.setText(myObject.text); viewHolder.progressBar.setProgress(myObject.progress); /* 設置按鈕點擊事件 */ viewHolder.button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myObject.progress == -1) { myObject.progress = 0; //如果未開始下載,啟動異步下載任務 MyAsyncTask asyncTask = new MyAsyncTask(viewList, position); //添加THREAD_POOL_EXECUTOR可啟動多個異步任務 asyncTask.executeOnExecutor(MyAsyncTask.THREAD_POOL_EXECUTOR, myObject); } } }); /* 標識View對象 */ //將list_view的ID作為Tag的Key值 convertView.setTag(R.id.list_view, position);//此處將位置信息作為標識傳遞 viewList.add(convertView); return convertView; } /** * 用於緩存控件ID */ class ViewHolder { ProgressBar progressBar; Button button; } }

public class MyAsyncTask extends AsyncTask<MainActivity.MyObject, Integer, Void> { private MainActivity.MyObject myObject; //單個數據,用於完成后的處理 private List<View> viewList; //視圖對象集合,用於設置樣式 private Integer viewId; //視圖標識,用於匹配視圖對象 public MyAsyncTask(List<View> viewList, Integer viewId) { this.viewList = viewList; this.viewId = viewId; } @Override protected Void doInBackground(MainActivity.MyObject... params) { myObject = params[0]; /* 模擬下載任務 */ for (int i = 0; i < 100; i++) { //發布進度 publishProgress(i); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } return null; } @Override protected void onProgressUpdate(Integer... values) { View view = null; /* 匹配視圖對象 */ for (int i = 0; i < viewList.size(); i++) { if (viewList.get(i).getTag(R.id.list_view) == viewId) { //檢查所有視圖ID,如果ID匹配則取出該對象 view = viewList.get(i); break; } } if (view != null) { //將視圖對象中緩存的ViewHolder對象取出,並使用該對象對控件進行更新 MyAdapter.ViewHolder viewHolder = (MyAdapter.ViewHolder) view.getTag(); viewHolder.progressBar.setProgress(values[0]); } myObject.progress = values[0]; } @Override protected void onPostExecute(Void aVoid) { //更新數據源信息 myObject.progress = 100; } }
實現效果
這里主要強調一下如何為View設置標識,以及從View集合中匹配標識:
首先是為View設置標識:
@Override public View getView(final int position, View convertView, ViewGroup parent) { final ViewHolder viewHolder; /* 初始化控件 */ if (convertView == null) { convertView = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false); viewHolder = new ViewHolder(); viewHolder.progressBar = (ProgressBar) convertView.findViewById(R.id.progress_bar); viewHolder.button = (Button) convertView.findViewById(R.id.btn); convertView.setTag(viewHolder);//記錄ViewHolder對象,緩存控件實例 } else { viewHolder = (ViewHolder) convertView.getTag(); } /* 添加控件樣式 */ //略去…… /* 設置按鈕點擊事件 */ //略去…… /* 標識View對象 */ convertView.setTag(R.id.list_view, position); //此處將位置信息作為標識傳遞 viewList.add(convertView); //將每個View添加到視圖集合中 /** * View.setTag(int Key,Object object)中的Key值必須唯一 * 傳入任何常量都是無效的,必須傳入R.id中生成的值 * * 標識並非用於識別View對象,而是識別View的狀態 * 就像警帽並非用於識別演員,而是識別演員當前扮演的角色 * * View集合就像演員名單一樣重要,如果沒有它表演無從開展 * * notifyDataSetChanged()雖然能更新列表,但是它是更新所有控件數據 * 相比於選擇某個控件進行更新,這種方法性能開銷大,體驗差 */ return convertView; }
然后是在更新時匹配標識:
@Override protected void onProgressUpdate(Integer... values) { View view = null; /* 匹配視圖對象 */ for (int i = 0; i < viewList.size(); i++) { //上場名單清點 if (viewList.get(i).getTag(R.id.list_view) == viewId) { //服裝確認匹配 //檢查所有視圖ID,如果ID匹配則取出該對象 view = viewList.get(i); break; } } if (view != null) { //上場進行表演 //將視圖對象中緩存的ViewHolder對象取出,並使用該對象對控件進行更新 MyAdapter.ViewHolder viewHolder = (MyAdapter.ViewHolder) view.getTag(); viewHolder.progressBar.setProgress(values[0]); } /** * 在更新時ViewList的重要性就體現出來了 * 遍歷整個ViewList直到找到標識相同的視圖 * * 因為每次填充View時,View都會添加一個標識,而標識記錄了當前的位置 * 所以標識代表某個視圖在特定的位置,如果標識固定那么位置也就固定了 * * 就像演員每次表演前,雖然角色誰都可以演,但是只要服裝確定 * 那么只有穿着這服裝且在上場名單內的演員才可以進行表演 * */ }
其實更新 ListView 中的某個控件的狀態真是是很麻煩的事,因為適配器會為視圖填充新的數據,這就要求使用對象記錄狀態,比如在實體對象中添加完成與否的判斷,還有完成進度的記錄,並且在更新視圖中也同步更新這些數據。