使用 RecyclerView

使用 RecyclerView

概述


RecyclerView 是一個 ViewGroup,它用於渲染任何基於適配器的 View。它被官方定義爲 ListViewGridView 的取代者,是在 Support V7 包中引入的。使用該組件的一個理由是:它有一個更易於擴展的框架,尤爲是它提供了橫向和縱向兩個方向滾動的能力。當數據集合根據用戶的操做或網絡狀態的變化而變化時,你很須要這個控件。java

要使用 RecyclerView,須要如下的幾個元素:android

  • RecyclerView.Adapter :用於處理數據集合,並把數據綁定到視圖上git

  • LayoutManager :用於定位列表項github

  • ItemAnimator :用於讓常見操做(例如添加或移除列表項)變得活潑web

enter description here

工做流

此處,當 RecyclerView 添加或移除列表項時,它還提供了動畫支持,動畫操做在當前的實現下,是很是困難的事情。而且,ViewHolderRecyclerView 中被深度集成,再也不只是一個推薦方式。網絡

與 ListView 的對比


由於如下幾個理由,RecyclerView 與它的前輩 ListView 是不相同的:app

  • 適配器中須要 ViewHolderListView 中,要提高性能,你能夠不實現 ViewHolder,能夠嘗試其它的選擇;可是在 RecyclerView 的適配器中,ViewHolder 是必需要使用的。框架

  • 自定義列表項佈局ListView 只能把列表項以線性垂直的方式進行安排,而且不能自定義;RecyclerViewRecyclerView.LayoutManager 類,可讓任何列表項在水平方向排列,或是以交錯的網格模式排列。less

  • 簡單的列表項動畫 :關於添加或移除列表項的操做,ListView 並無添加任何的規定;對於 RecyclerView 來講,它有一個 RecyclerView.ItemAnimator 類,能夠用來處理列表項的動畫。ide

  • 手動的數據源 :對於不一樣類型的數據源來講,ListView 有着不一樣的適配器與之對應,例如 ArrayAdapterCursorAdapter。與此相反,RecyclerAdapter 須要開發者本身實現提供給適配器的數據。

  • 手動的列表項裝飾ListViewandroid:divider 屬性,用於設置列表項之間的分隔。與此相反,要給 RecyclerView 設置分隔線的裝飾,須要手動使用 RecyclerView.ItemDecoration 對象。

  • 手動監測點擊事件ListView 爲列表上的每一個列表項的點擊事件都使用 AdapterView.OnItemClickListener 接口進行了綁定。與之不一樣的是,RecyclerView 只提供了 RecyclerView.OnItemTouchListener 接口,它能夠管理單個的 touch 事件,而再也不內嵌點擊事件的處理。

RecyclerView 的組件

LayoutManager


LayoutManager 用於在 RecyclerView 中管理列表項的位置,對於再也不對用戶可見的視圖來講,它還能決定何時重用這些視圖。

RecyclerView 提供瞭如下幾種內嵌的佈局管理器:

  • LinearLayoutManager :在水平或垂直的滾動列表上顯示列表項

  • GridLayoutManager :在網格中顯示列表項

  • StaggeredGridLayoutManager :在交錯的網格中顯示列表項

繼承 RecyclerView.LayoutManager 就能夠建立自定義的佈局管理器。

RecyclerView.Adapter


RecyclerView 包含一種新的適配器,它與你以前使用過的適配器很相似,只是包含了一些特殊之處,例如必須的 ViewHolder 等。要使用這些適配器,須要重寫兩個方法:1. 用於渲染視圖和 ViewHolder 的方法;2. 用於把數據綁定到視圖的方法。每當須要建立一個新的視圖時,都會調用第一個方法,再也不須要檢測視圖是否被回收。

ItemAnimator


RecyclerView.ItemAnimator 將會使要通知適配器的 ViewGroup 的改變(例如添加/刪除/選擇列表項)動起來。DefaultItemAnimator 能夠用於基本的默認動畫,而且表現不俗。

使用 RecyclerView


使用 RecyclerView 須要遵循下面的關鍵步驟:

  1. gradle 構建文件中添加 RecyclerView 的支持庫

  2. 定義做爲數據源使用的 model

  3. Activity 中添加 RecyclerView

  4. 建立用於展示列表項的自定義行佈局文件

  5. 把數據源綁定到適配器中,用於填充 RecyclerView

安裝


app/build.gradle 文件中申明:

  1. dependencies { 
  2. ... 
  3. compile 'com.android.support:support-v4:24.2.1' 
  4. compile 'com.android.support:recyclerview-v7:24.2.1' 

定義 Model


每一個 RecyclerView 都有一個數據源支持。在本示例中,將定義一個 Contact 類,用於定義 RecyclerView 顯示的數據模型:

  1. public class Contact
  2. private String mName; 
  3. private boolean mOnline; 
  4.  
  5. public Contact(String name, boolean online)
  6. mName = name; 
  7. mOnline = online; 

  8.  
  9. public String getName()
  10. return mName; 

  11.  
  12. public boolean isOnline()
  13. return mOnline; 

  14.  
  15. private static int lastContactId = 0
  16.  
  17. public static ArrayList<Contact> createContactsList(int numContacts)
  18. ArrayList<Contact> contacts = new ArrayList<Contact>(); 
  19.  
  20. for (int i = 1; i <= numContacts; i++) { 
  21. contacts.add(new Contact("Person " + ++lastContactId, i <= numContacts / 2)); 

  22.  
  23. return contacts; 


在佈局文件中建立 RecycleView


res/layout/activity_user.xml 文件中,添加 RecyclerView

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  2. android:layout_width="match_parent" 
  3. android:layout_height="match_parent" > 
  4.  
  5. <android.support.v7.widget.RecyclerView 
  6. android:id="@+id/rvContacts" 
  7. android:layout_width="match_parent" 
  8. android:layout_height="match_parent" /> 
  9.  
  10. </RelativeLayout> 

建立自定義的行佈局


在建立適配器以前,先定義列表用於每行顯示數據的佈局。當前的列表項是一個水平的線性佈局,佈局中有一個文本框、一個按鈕:

enter description here

行佈局

行佈局文件保存在 res/layout/item_contact.xml 中,列表中每一行都會用到它。

注意:LinearLayout 的 layout_height 參數的值應該設置爲 wrap_content,這是由於早於 23.2.1 版本的 RecyclerView 將會忽略佈局參數。

  1. <?xml version="1.0" encoding="utf-8"?> 
  2. <LinearLayout 
  3. xmlns:android="http://schemas.android.com/apk/res/android" 
  4. android:orientation="horizontal" 
  5. android:layout_width="match_parent" 
  6. android:layout_height="wrap_content" 
  7. android:paddingTop="10dp" 
  8. android:paddingBottom="10dp" 
  9. > 
  10.  
  11. <TextView 
  12. android:id="@+id/contact_name" 
  13. android:layout_width="0dp" 
  14. android:layout_height="wrap_content" 
  15. android:layout_weight="1" 
  16. /> 
  17.  
  18. <Button 
  19. android:id="@+id/message_button" 
  20. android:layout_width="wrap_content" 
  21. android:layout_height="wrap_content" 
  22. android:paddingLeft="16dp" 
  23. android:paddingRight="16dp" 
  24. android:textSize="10sp" 
  25. /> 
  26. </LinearLayout> 

建立 RecyclerView.Adapter


接着,建立一個適配器,用於把數據填充到 RecyclerView 中。適配器的角色是:用於把某個位置上的對象轉換爲列表項,並顯示。

固然,RecyclerView 的適配器須要 ViewHolder 對象,用於描述和訪問每一個列表項中的全部視圖。在 ContactsAdapter.java 類中,先建立一個基本的、沒有實際內容的適配器:

  1. // Create the basic adapter extending from RecyclerView.Adapter 
  2. // Note that we specify the custom ViewHolder which gives us access to our views 
  3. public class ContactsAdapter extends  
  4. RecyclerView.Adapter<ContactsAdapter.ViewHolder>
  5.  
  6. // Provide a direct reference to each of the views within a data item 
  7. // Used to cache the views within the item layout for fast access 
  8. public static class ViewHolder extends RecyclerView.ViewHolder
  9. // Your holder should contain a member variable 
  10. // for any view that will be set as you render a row 
  11. public TextView nameTextView; 
  12. public Button messageButton; 
  13.  
  14. // We also create a constructor that accepts the entire item row 
  15. // and does the view lookups to find each subview 
  16. public ViewHolder(View itemView)
  17. // Stores the itemView in a public final member variable that can be used 
  18. // to access the context from any ViewHolder instance. 
  19. super(itemView); 
  20.  
  21. nameTextView = (TextView) itemView.findViewById(R.id.contact_name); 
  22. messageButton = (Button) itemView.findViewById(R.id.message_button); 



適配器和 ViewHolder 已經定義好了,接下來該填充它們了。首先,定義一個成員變量,用於存儲聯繫人數據,它在構造方法中被賦值:

  1. public class ContactsAdapter extends  
  2. RecyclerView.Adapter<ContactsAdapter.ViewHolder>
  3.  
  4. // ... view holder defined above... 
  5.  
  6. // Store a member variable for the contacts 
  7. private List<Contact> mContacts; 
  8. // Store the context for easy access 
  9. private Context mContext; 
  10.  
  11. // Pass in the contact array into the constructor 
  12. public ContactsAdapter(Context context, List<Contact> contacts)
  13. mContacts = contacts; 
  14. mContext = context; 

  15.  
  16. // Easy access to the context object in the recyclerview 
  17. private Context getContext()
  18. return mContext; 


每一個適配器都有三個主要的方法:1. onCreateViewHolder :實例化列表項佈局、建立 Holder;2. onBindViewHolder:基於數據設置視圖屬性;3. getItemCount:檢測列表項的數量。爲了完成適配器,須要實現它們:

  1. public class ContactsAdapter extends  
  2. RecyclerView.Adapter<ContactsAdapter.ViewHolder>
  3.  
  4. // ... constructor and member variables 
  5.  
  6. // Usually involves inflating a layout from XML and returning the holder 
  7. @Override 
  8. public ContactsAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
  9. Context context = parent.getContext(); 
  10. LayoutInflater inflater = LayoutInflater.from(context); 
  11.  
  12. // Inflate the custom layout 
  13. View contactView = inflater.inflate(R.layout.item_contact, parent, false); 
  14.  
  15. // Return a new holder instance 
  16. ViewHolder viewHolder = new ViewHolder(contactView); 
  17. return viewHolder; 

  18.  
  19. // Involves populating data into the item through holder 
  20. @Override 
  21. public void onBindViewHolder(ContactsAdapter.ViewHolder viewHolder, int position)
  22. // Get the data model based on position 
  23. Contact contact = mContacts.get(position); 
  24.  
  25. // Set item views based on your views and data model 
  26. TextView textView = viewHolder.nameTextView; 
  27. textView.setText(contact.getName()); 
  28. Button button = viewHolder.messageButton; 
  29. button.setText("Message"); 

  30.  
  31. // Returns the total count of items in the list 
  32. @Override 
  33. public int getItemCount()
  34. return mContacts.size(); 


把適配器綁定到 RecyclerView


Activity 中,給 RecyclerView 填充用於測試的用戶數據:

  1. public class UserListActivity extends AppCompatActivity
  2.  
  3. ArrayList<Contact> contacts; 
  4.  
  5. @Override 
  6. protected void onCreate(Bundle savedInstanceState)
  7. // ... 
  8. // Lookup the recyclerview in activity layout 
  9. RecyclerView rvContacts = (RecyclerView) findViewById(R.id.rvContacts); 
  10.  
  11. // Initialize contacts 
  12. contacts = Contact.createContactsList(20); 
  13. // Create adapter passing in the sample user data 
  14. ContactsAdapter adapter = new ContactsAdapter(this, contacts); 
  15. // Attach the adapter to the recyclerview to populate items 
  16. rvContacts.setAdapter(adapter); 
  17. // Set layout manager to position the items 
  18. rvContacts.setLayoutManager(new LinearLayoutManager(this)); 
  19. // That's all! 


編譯運行應用,你將看到相似於下圖的結果。若是建立了足夠多的元素,列表就能夠滾動了,你將會發現它的滾動遠比 ListView 流暢的多。

enter description here

最終結果

通知適配器


ListView 不一樣的是,經過 RecyclerView 的適配器,沒有辦法直接添加或移除列表項。你須要作的是:直接操做數據源,而後通知適配器數據源發生了變化。同時,每當添加或移除元素時,老是應該對已經存在的列表進行操做。例如,下面這句的改變,並不會影響適配器,這是由於適配器保存了舊數據的引用,可是生成的新數據的引用發生了變化。

  1. // do not reinitialize an existing reference used by an adapter 
  2. contacts = Contact.createContactsList(5); 

相反,你須要在已經存在的引用上直接進行操做:

  1. // add to the existing list 
  2. contacts.addAll(Contact.createContactsList(5)); 

若是要給適配器通知不一樣類型的改變,可使用下面的這些方法:

方法 描述
notifyItemChanged(int pos) 某個位置上元素改變的通知
notifyItemInserted(int pos) 某個位置上插入元素的通知
notifyItemRemoved(int pos) 某個位置上元素移除的通知
notifyDataSetChanged() 數據集改變的通知

ActivityFragment 中,應該這樣使用:

  1. // Add a new contact 
  2. contacts.add(0, new Contact("Barney", true)); 
  3. // Notify the adapter that an item was inserted at position 0 
  4. adapter.notifyItemInserted(0); 

每當咱們想給 RecyclerView 添加或移除數據時,咱們須要顯式地通知適配器正在發生的事件是什麼。與 ListView 的適配器不一樣,RecyclerView 的適配器不該該依賴於 notifyDataSetChanged(),應該使用粒度更細的動做。

若是你打算更新一個已經存在的數據列表,請在改變以前,務必要獲取當前元素的數量。例如,應該調用適配器的 getItemCount() 方法來記錄改變前的索引:

  1. // record this value before making any changes to the existing list 
  2. int curSize = adapter.getItemCount(); 
  3.  
  4. // replace this line with wherever you get new records 
  5. ArrayList<Contact> newItems = Contact.createContactsList(20);  
  6.  
  7. // update the existing list 
  8. contacts.addAll(newItems); 
  9. // curSize should represent the first element that got added 
  10. // newItems.size() represents the itemCount 
  11. adapter.notifyItemRangeInserted(curSize, newItems.size()); 

定義大的改變


實際生產中,列表的改變每每更爲複雜一些(例如:對已經存在的數據排序),改變一複雜,就不能精確對改變進行一個分類。這種狀況下,一般,須要在整個適配器上調用 notifyDataSetChanged() 方法來更新整個屏幕,這樣,使用動畫來展現改變的能力,就會被減弱。

support v7 庫的 v24.2.0 版本中,新增了 DiffUtil 類,用於計算新舊數據之間的不一樣。對於比較大的數據列表來講,推薦使用後臺線程執行計算。

要使用 DiffUtil 類,須要實現該類並實現 DiffUtil.Callback 回調,該回調能夠接收新、舊的數據列表:

  1. public class ContactDiffCallback extends DiffUtil.Callback
  2.  
  3. private List<Contact> mOldList; 
  4. private List<Contact> mNewList; 
  5.  
  6. public ContactDiffCallback(List<Contact> oldList, List<Contact> newList)
  7. this.mOldList = oldList; 
  8. this.mNewList = newList; 

  9. @Override 
  10. public int getOldListSize()
  11. return mOldList.size(); 

  12.  
  13. @Override 
  14. public int getNewListSize()
  15. return mNewList.size(); 

  16.  
  17. @Override 
  18. public boolean areItemsTheSame(int oldItemPosition, int newItemPosition)
  19. // add a unique ID property on Contact and expose a getId() method 
  20. return mOldList.get(oldItemPosition).getId() == mNewList.get(newItemPosition).getId(); 

  21.  
  22. @Override 
  23. public boolean areContentsTheSame(int oldItemPosition, int newItemPosition)
  24. Contact oldContact = mOldList.get(oldItemPosition); 
  25. Contact newContact = mNewList.get(newItemPosition); 
  26.  
  27. if (oldContact.getName() == newContact.getName() && oldContact.isOnline() == newContact.isOnline()) { 
  28. return true

  29. return false


接着,要在適配器中實現 swapItems() 方法用於執行差別化比較。比較事後,若是有數據被插入、移除、移動或刪除時,調用 dispatchUpdates() 方法來通知適配器:

  1. public class ContactsAdapter extends 
  2. RecyclerView.Adapter<ContactsAdapter.ViewHolder>
  3.  
  4. public void swapItems(List<Contact> contacts)
  5. // compute diffs 
  6. final ContactDiffCallback diffCallback = new ContactDiffCallback(this.mContacts, contacts); 
  7. final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback); 
  8.  
  9. // clear contacts and add  
  10. this.mContacts.clear(); 
  11. this.mContacts.addAll(contacts); 
  12.  
  13. diffResult.dispatchUpdatesTo(this); // calls adapter's notify methods after diff is computed 


完整的示例代碼請參見 mrmike/DiffUtil-sample

滾動到新的列表項


把列表項插入到列表的前部分,可是當前列表視圖處於列表的後部分,這時,若是你想把列表顯示頂部的位置,能夠經過下列代碼實現:

  1. adapter.notifyItemInserted(0);  
  2. rvContacts.scrollToPosition(0); // index 0 position 

若是把列表項添加到列表的底部,咱們能夠這樣作:

  1. adapter.notifyItemInserted(contacts.size() - 1); // contacts.size() - 1 is the last element position 
  2. rvContacts.scrollToPosition(mAdapter.getItemCount() - 1); // update based on adapter  

實現連續的滾動


當用戶滾動到列表的底部時,想要實現加載數據,把新加載的數據添加到列表的尾部的功能,須要使用 RecyclerViewaddOnScrollListener() 方法,並添加 onLoadMore 方法。具體的實現參見另外一篇教程:EndlessScrollViewScrollListener

配置 RecyclerView


RecyclerView 能夠自定製,它很是的靈活,能夠從如下幾個方面得以體現:

性能


若是數據源是靜態、不會發生變化的,那咱們能夠啓用最優化,來顯著的提高平滑滾動的效果:

  1. recyclerView.setHasFixedSize(true); 

佈局


使用 layout manager 能夠配置列表項的位置。咱們能夠在 LinearLayoutManagerGridLayoutManagerStaggeredGridLayoutManager 這幾個佈局管理器中進行選擇。

列表項在水平或垂直方向進行線性排列的示例:

  1. // Setup layout manager for items with orientation 
  2. // Also supports `LinearLayoutManager.HORIZONTAL` 
  3. LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); 
  4. // Optionally customize the position you want to default scroll to 
  5. layoutManager.scrollToPosition(0); 
  6. // Attach layout manager to the RecyclerView 
  7. recyclerView.setLayoutManager(layoutManager); 

在網格或交錯網格上顯示列表項的示例:

  1. // First param is number of columns and second param is orientation i.e Vertical or Horizontal 
  2. StaggeredGridLayoutManager gridLayoutManager =  
  3. new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL); 
  4. // Attach the layout manager to the recycler view 
  5. recyclerView.setLayoutManager(gridLayoutManager); 

交錯的網格佈局可能以下所示:

enter description here

交錯網格效果展現

要自定義 layout manager,能夠參見 這篇文章

裝飾


咱們可使用與 RecyclerView 相關的一些裝飾來修飾列表項,例如使用 DividerItemDecoration

  1. RecyclerView.ItemDecoration itemDecoration = new  
  2. DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST); 
  3. recyclerView.addItemDecoration(itemDecoration); 

裝飾的做用是在列表項之間顯示分隔:

enter description here

列表項分隔

Grid 空間裝飾


GridStaggeredGrid 中,修飾能夠在顯示的元素之間添加一致的空間。把 SpacesItemDecoration.java 這個修飾拷貝到你的項目中,看一下效果吧。要想知道更多的細節,請參考 這篇教程

動畫


RecyclerView 的列表項進入、添加或刪除時,使用 ImteAnimator 能夠給列表項添加自定義的動畫。DefaultItemAnimator 用於定義默認的動畫,它源碼(源碼地址)中複雜的實現說明了要確保以某個序列執行的動畫效果所須要的必須的邏輯。

目前,在 RecyclerView 上實現列表項動畫的最快方式就是使用第三方庫。wasabeef/recyclerview-animators 庫中包含了大量可使用的動畫。引入方式以下:

  1. repositories { 
  2. jcenter() 

  3.  
  4. //If you are using a RecyclerView 23.1.0 or higher. 
  5. dependencies { 
  6. compile 'jp.wasabeef:recyclerview-animators:2.2.3' 

  7.  
  8. //If you are using a RecyclerView 23.0.1 or below. 
  9. dependencies { 
  10. compile 'jp.wasabeef:recyclerview-animators:1.3.0' 

代碼中的使用:

  1. recyclerView.setItemAnimator(new SlideInUpAnimator()); 

下面是效果:

enter description here

列表項動畫效果

新的動畫接口


support v23.1.0 開始,RecyclerView.ItemAnimator 新增了一個接口。該庫添加了 ItemHolderInfo 類,它的表現與 MoveInfo 相似,可是在動畫過渡狀態之間,它能更爲優雅的傳遞狀態信息。

不一樣佈局的列表項


若是在單個的 RecyclerView 中,你想展現多種類型的列表項,那你應該參這篇文章

enter description here

不一樣類型的列表項

處理 Touch 事件


  1. recyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() { 
  2.  
  3. @Override 
  4. public void onTouchEvent(RecyclerView recycler, MotionEvent event)
  5. // Handle on touch events here 

  6.  
  7. @Override 
  8. public boolean onInterceptTouchEvent(RecyclerView recycler, MotionEvent event)
  9. return false

  10.  
  11. }); 

Snap to Center 效果


在某些狀況下,咱們可能須要一個橫向滾動的 RecyclerView。當用戶滾動時,若是某個列表項被暴露了,咱們想讓它 snap to center,以下圖所示:

enter description here

snap to center

LinearSnapHelper


當用戶滑動時,如要實現上圖的效果,在 support v24.2.0 及以上版本中,可使用內嵌的 LinearSnapHelper 類:

  1. SnapHelper snapHelper = new LinearSnapHelper(); 
  2. snapHelper.attachToRecyclerView(recyclerView); 
相關文章
相關標籤/搜索