1. 程式人生 > >RecyclerView Part 1:為ListView專家寫的基礎

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

原文連結    作者:Bill Phillips    譯者:趙峰

是時候講清楚了:我瞭解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():

@Override
 public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)

{
 View v = inflater.inflate(R.layout.fragment_recyclerview, parent, false);

mRecyclerView = (RecyclerView) v.findViewById(R.id.recycler_view);
 mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
 mCrimes = CrimeLab.get(getActivity()).getCrimes();
 mRecyclerView.setAdapter(new CrimeAdapter());

return v;
 }

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

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

public View onCreateView(LayoutInflater inflater, ViewGroup parent,Bundle savedInstanceState)

{
 ...

mListView.setOnItemClickListener(this);

...
 }

public void onItemClick(AdapterView<?> parent, View view, int position,
 long id) {
 // handle item click
 }

通過監聽事件你可以處理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會如下圖所展示的這樣:

private static class ViewHolder {
 private CheckBox mSolvedCheckBox;
 }

private class CrimeAdapter extends ArrayAdapter<Crime> {
 public CrimeAdapter(ArrayList<Crime> crimes) {
 super(getActivity(), 0, crimes);
 }

@Override
 public View getView(int position, View convertView, ViewGroup parent) {
 ViewHolder holder;
 if (null == convertView) {
 convertView = LayoutInflater.from(getActivity())
 .inflate(R.layout.list_item_crime, parent, false);
 holder = new ViewHolder();
 holder.mSolvedCheckBox = (CheckBox) convertView
 .findViewById(R.id.solvedCheckBox);

convertView.setTag(holder);
 }

ViewHolder holder = (ViewHolder) convertView.getTag();
 Crime crime = getItem(postion);
 holder.mSolvedCheckBox.setChecked(crime.isSolved());

return convertView;
 }
 }

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

private class CrimeHolder extends ViewHolder {
 private final CheckBox mSolvedCheckBox;
 private Crime mCrime;

public CrimeHolder(View itemView) {
 super(itemView);

mSolvedCheckBox = (CheckBox) itemView
 .findViewById(R.id.crime_list_item_solvedCheckBox);
 }

public void bindCrime(Crime crime) {
 mCrime = crime;
 mSolvedCheckBox.setChecked(crime.isSolved());
 }
 }

private class CrimeAdapter 
 extends RecyclerView.Adapter<CrimeHolder> {
 @Override
 public CrimeHolder onCreateViewHolder(ViewGroup parent, int pos) {
 View view = LayoutInflater.from(parent.getContext())
 .inflate(R.layout.list_item_crime, parent, false);
 return new CrimeHolder(view);
 }

@Override
 public void onBindViewHolder(CrimeHolder holder, int pos) {
 Crime crime = mCrimes.get(pos);
 holder.bindCrime(crime);
 }

@Override
 public int getItemCount() {
 return mCrimes.size();
 }
 }

注意這裡沒有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點選事件的場所。

private class CrimeHolder extends ViewHolder 
 implements View.OnClickListener {
 ...

public CrimeHolder(View itemView) {
 super(itemView);

itemView.setOnClickListener(this);

...
 }

@Override
 public void onClick(View v) {
 if (mCrime != null) {
 Intent i = CrimeActivity.getIntent(v.getContext(), mCrime);
 startActivity(i);
 }
 }
 }

在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中實現,但是在實現過程中幾個陷阱。