RecyclerView Part 1:為ListView專家寫的基礎

idaretobe發表於2015-01-13

是時候講清楚了:我瞭解RecylerView有些晚。太遲了。

這隻能怪我自己。你可以從文件中得知RecylerView應該會取代ListView,並且在工具包裡沒有幾個檢視比ListView更重要。所以,RecyclerView十分重要。

但是它們還是會有很多不同,對嗎?嗯。所以我一直推遲對它的講解,直到幾周之前我為Hack Night準備一個講座的時候。我最終做了大量的研究,我承認這些研究很有樂趣。

最終的結果是RecyclerView非常酷,值的去替換listView。它把以前非常棘手的東西變的非常非常簡單。最重要的是,它可以讓一個item輕鬆的從一個可行的列表中動畫流入和流出,而不像之前那樣慢。

通過指出所有的這些要點,我決定把ListView從我們的 Android programming guide中刪除,並用RecyclerView代替。我發現使用RecyclerView會使大多數的工作變的非常容易,並且最終的程式碼非常簡潔。

除了一點:選擇模式(choice modes)。setChoiceMode()被去掉了,並且讓一個RecyclerView轉換多項選擇,會導致了一些有趣的問題。在以後的幾篇部落格中我將會按照步驟來跟你分享我的解決方案。在這裡我先從RecyclerView的基本用法開始。

RecyclerView做的更少
如果你打算替換ListView,那麼現在讓我們討論下RecyclerView中的一些重大變化。
第1步,當然這是非常重要的,你需要在build.gradle裡寫入下面這一行:
compile ‘com.android.support:recyclerview-v7:+’

第二步:有一個很好的的去掉setChoiceMode(int)的原因。RecyclerView能做的比ListView更多,但是比起ListView它有更少的責任。創造性的,RecyclerView並不需要處理:
1.在螢幕上item的位置
2.動畫檢視
3.除了滾動可以捕獲任何觸控事件

但是,所有的這些都被放到了ListView中,而RecyclerView是通過使用合作者類去(collaborator class)做這些工作的。

前兩個,RecyclerView通過使用LayoutManager和ItemAnimator來實現。RecyclerView擁有一個預設的ItemAdapter,所以你不必要擔心這些。你要做的是給item一個LayoutManager。這是我們的CriminalIntent應用中list fragment的OnCreateView():

01 @Override
02  public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)
03  
04 {
05  View v = inflater.inflate(R.layout.fragment_recyclerview, parent, false);
06  
07 mRecyclerView = (RecyclerView) v.findViewById(R.id.recycler_view);
08  mRecyclerView.setLayoutManager(newLinearLayoutManager(getActivity()));
09  mCrimes = CrimeLab.get(getActivity()).getCrimes();
10  mRecyclerView.setAdapter(new CrimeAdapter());
11  
12 return v;
13  }

LinearLayoutManager在RecyclerView support library中。它會讓RecyclerView中的item像ListView中顯示的那樣。還有其它的佈局管理者,比如網格,或者交錯網格。至於CrimeAdapter,我將立即討論它。

當在ListView中點選一個item時,你可能會這樣做:

01 public View onCreateView(LayoutInflater inflater, ViewGroup parent,Bundle savedInstanceState)
02  
03 {
04  ...
05  
06 mListView.setOnItemClickListener(this);
07  
08 ...
09  }
10  
11 public void onItemClick(AdapterView<?> parent, View view,int position,
12  long id) {
13  // handle item click
14  }

通過監聽事件你可以處理adapter返回的單個item的點選事件。這並不常見,也不推薦使用。雖然ListView給了你一些不錯的功能,諸如,允許在點選處理之上建立可選擇的item的ListView.setChoiceMode(int)。(setChoiceMode(int)可以讓你選擇單獨的item或者是多個item)
然而,RecyclerView並不需要處理這些,所以它不需要模式選擇。你不能使用OnItemClickListener來捕獲item被點選。RecyclerView提供了一個OnItemTouchListener,但這不同於OnItemClickListener:這個事件並不告訴你哪個item被觸發了。你需要通過MotionEvent去尋找哪個item被觸發,但大多數情況這並不需要。
ViewHolder

另一個大的不同是view holder變的更重要。如果你從我們這裡學習怎樣使用ListView,你可以不知道什麼是view holder,因為我們並沒有教那樣做。View holder是附加在ListView中每一行的物件。一般的view holder會如下圖所展示的這樣:

01 private static class ViewHolder {
02  private CheckBox mSolvedCheckBox;
03  }
04  
05 private class CrimeAdapter extends ArrayAdapter<Crime> {
06  public CrimeAdapter(ArrayList<Crime> crimes) {
07  super(getActivity(), 0, crimes);
08  }
09  
10 @Override
11  public View getView(int position, View convertView, ViewGroup parent) {
12  ViewHolder holder;
13  if (null == convertView) {
14  convertView = LayoutInflater.from(getActivity())
15  .inflate(R.layout.list_item_crime, parent, false);
16  holder = new ViewHolder();
17  holder.mSolvedCheckBox = (CheckBox) convertView
18  .findViewById(R.id.solvedCheckBox);
19  
20 convertView.setTag(holder);
21  }
22  
23 ViewHolder holder = (ViewHolder) convertView.getTag();
24  Crime crime = getItem(postion);
25  holder.mSolvedCheckBox.setChecked(crime.isSolved());
26  
27 return convertView;
28  }
29  }

當view獲得檢視時,你也需要建立一個ViewHolder物件,並且添入相應的資料。然後通過setTag()把ViewHolder關聯到view上。
你每建立一個view,相應的要建立一個ViewHolder。之前使用ViewHolder是為了實現滾動效能更好:它通過呼叫findViewById()儲存了solvedCheckBox所以在每次尋找元件時更方便。如果你有其它的操作需要呼叫元件,你同樣會選擇使用ViewHolder,而不是使用getView()。
下面是我們為RecyclerView寫的包含ViewHolder的程式碼:

01 private class CrimeHolder extends ViewHolder {
02  private final CheckBox mSolvedCheckBox;
03  private Crime mCrime;
04  
05 public CrimeHolder(View itemView) {
06  super(itemView);
07  
08 mSolvedCheckBox = (CheckBox) itemView
09  .findViewById(R.id.crime_list_item_solvedCheckBox);
10  }
11  
12 public void bindCrime(Crime crime) {
13  mCrime = crime;
14  mSolvedCheckBox.setChecked(crime.isSolved());
15  }
16  }
17  
18 private class CrimeAdapter 
19  extends RecyclerView.Adapter<CrimeHolder> {
20  @Override
21  public CrimeHolder onCreateViewHolder(ViewGroup parent, intpos) {
22  View view = LayoutInflater.from(parent.getContext())
23  .inflate(R.layout.list_item_crime, parent, false);
24  return new CrimeHolder(view);
25  }
26  
27 @Override
28  public void onBindViewHolder(CrimeHolder holder, int pos) {
29  Crime crime = mCrimes.get(pos);
30  holder.bindCrime(crime);
31  }
32  
33 @Override
34  public int getItemCount() {
35  return mCrimes.size();
36  }
37  }

注意這裡沒有ArrayAdapter,所以你需要為Adapter關聯一個List。幸運的是這並不難。

就像ListView Adapter中的那樣,你需要要建立兩個類:一個Adapter和一個ViewHolder。在RecyclerView的adapter中,更像是系統的一塊基石。你的RecyclerView.Adapter去創造和繫結ViewHolder,而不是之前的View。

同樣你建立的ViewHolder更加健壯。RecyclerView.ViewHolder子類擁有一大堆RecyclerView使用的方法。ViewHolder知道剛剛繫結的是哪個位置和item的id。

這樣ViewHolder就被建立了。以前控制整個item檢視是ListView的工作,ViewHolder只是控制其中的一小部分。現在,ViewHolder在ViewHolder.itemView中控制所有檢視,並在建構函式中給itemView賦值。
自從ViewHolder有了這個新的責任,它同樣可以被賦予更多其它的責任。所以在新的實現中我們加入了一個新的方法——bindCrime(),以前我們常常在getView()裡做很多的工作。ViewHolder同樣成為捕獲特定item點選事件的場所。

01 private class CrimeHolder extends ViewHolder 
02  implements View.OnClickListener {
03  ...
04  
05 public CrimeHolder(View itemView) {
06  super(itemView);
07  
08 itemView.setOnClickListener(this);
09  
10 ...
11  }
12  
13 @Override
14  public void onClick(View v) {
15  if (mCrime != null) {
16  Intent i = CrimeActivity.getIntent(v.getContext(), mCrime);
17  startActivity(i);
18  }
19  }
20  }

在ListView中關於怎樣控制點選事件有一些地方比較模糊:是每個單獨的視力控制這些事件,還是ListView通過OnItemClickListener控制?在RecyclerView中ViewHolder是確定做為行級(row-level)控制物件來處理這些細節。
我們之前看到LayoutManager處理檢視位置,ItemAnimator處理動畫。ViewHolder是最後的部分:它的職責是處理髮生在RecyclerView中特定item的事件。

選擇模式及下一個是什麼
那麼選擇模式(choice mode)怎麼辦?怎麼在RecyclerView中使用它們?我們在這裡會很自然的在ViewHolder中展示出來,因為它處理item的點選事件。
在這裡我有一些壞訊息:ViewHolder並沒有提供任何工具來實現選擇。去實現單個或多個選擇,我將在下一篇部落格中講遇到的兩個問題:

1.跟蹤選擇。我們需要一些程式碼來獲取哪個item被選擇了,或者選擇有沒有被啟用。
2.展示哪些item被選擇了。ListView通過呼叫每個item檢視中的setActivated()實現。你可以在Lollipop中實現,但是在實現過程中幾個陷阱。

轉載自併發程式設計網 – ifeve.com本文連結地址: RecyclerView Part 1:為ListView專家寫的基礎

相關文章