443
汽車大全
RecyclerView Part 1:為ListView專家寫的基礎
是時候講清楚了:我了解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中實現,但是在實現過程中幾個陷阱。
最後更新:2017-05-23 10:32:01