使用 RecyclerView
android
RecyclerView
tutorial
概述
RecyclerView
是一個 ViewGroup
,它用於渲染任何基於適配器的 View
。它被官方定義爲 ListView
和 GridView
的取代者,是在 Support V7
包中引入的。使用該組件的一個理由是:它有一個更易於擴展的框架,尤爲是它提供了橫向和縱向兩個方向滾動的能力。當數據集合根據用戶的操做或網絡狀態的變化而變化時,你很須要這個控件。java
要使用 RecyclerView
,須要如下的幾個元素:android
RecyclerView.Adapter
:用於處理數據集合,並把數據綁定到視圖上git
LayoutManager
:用於定位列表項github
ItemAnimator
:用於讓常見操做(例如添加或移除列表項)變得活潑web
此處,當 RecyclerView
添加或移除列表項時,它還提供了動畫支持,動畫操做在當前的實現下,是很是困難的事情。而且,ViewHolder
在 RecyclerView
中被深度集成,再也不只是一個推薦方式。網絡
與 ListView 的對比
由於如下幾個理由,RecyclerView
與它的前輩 ListView
是不相同的:app
適配器中須要 ViewHolder :ListView
中,要提高性能,你能夠不實現 ViewHolder
,能夠嘗試其它的選擇;可是在 RecyclerView
的適配器中,ViewHolder
是必需要使用的。框架
自定義列表項佈局 :ListView
只能把列表項以線性垂直的方式進行安排,而且不能自定義;RecyclerView
的 RecyclerView.LayoutManager
類,可讓任何列表項在水平方向排列,或是以交錯的網格模式排列。less
簡單的列表項動畫 :關於添加或移除列表項的操做,ListView
並無添加任何的規定;對於 RecyclerView
來講,它有一個 RecyclerView.ItemAnimator
類,能夠用來處理列表項的動畫。ide
手動的數據源 :對於不一樣類型的數據源來講,ListView
有着不一樣的適配器與之對應,例如 ArrayAdapter
,CursorAdapter
。與此相反,RecyclerAdapter
須要開發者本身實現提供給適配器的數據。
手動的列表項裝飾 :ListView
有 android: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
須要遵循下面的關鍵步驟:
在 gradle
構建文件中添加 RecyclerView
的支持庫
定義做爲數據源使用的 model
類
在 Activity
中添加 RecyclerView
建立用於展示列表項的自定義行佈局文件
把數據源綁定到適配器中,用於填充 RecyclerView
安裝
在 app/build.gradle
文件中申明:
- dependencies {
- ...
- compile 'com.android.support:support-v4:24.2.1'
- compile 'com.android.support:recyclerview-v7:24.2.1'
- }
定義 Model
每一個 RecyclerView
都有一個數據源支持。在本示例中,將定義一個 Contact
類,用於定義 RecyclerView
顯示的數據模型:
- public class Contact {
- private String mName;
- private boolean mOnline;
-
- public Contact(String name, boolean online) {
- mName = name;
- mOnline = online;
- }
-
- public String getName() {
- return mName;
- }
-
- public boolean isOnline() {
- return mOnline;
- }
-
- private static int lastContactId = 0;
-
- public static ArrayList<Contact> createContactsList(int numContacts) {
- ArrayList<Contact> contacts = new ArrayList<Contact>();
-
- for (int i = 1; i <= numContacts; i++) {
- contacts.add(new Contact("Person " + ++lastContactId, i <= numContacts / 2));
- }
-
- return contacts;
- }
- }
在佈局文件中建立 RecycleView
在 res/layout/activity_user.xml
文件中,添加 RecyclerView
:
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
-
- <android.support.v7.widget.RecyclerView
- android:id="@+id/rvContacts"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
-
- </RelativeLayout>
建立自定義的行佈局
在建立適配器以前,先定義列表用於每行顯示數據的佈局。當前的列表項是一個水平的線性佈局,佈局中有一個文本框、一個按鈕:
行佈局文件保存在 res/layout/item_contact.xml
中,列表中每一行都會用到它。
注意:LinearLayout 的 layout_height 參數的值應該設置爲 wrap_content,這是由於早於 23.2.1
版本的 RecyclerView
將會忽略佈局參數。
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="10dp"
- android:paddingBottom="10dp"
- >
-
- <TextView
- android:id="@+id/contact_name"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- />
-
- <Button
- android:id="@+id/message_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingLeft="16dp"
- android:paddingRight="16dp"
- android:textSize="10sp"
- />
- </LinearLayout>
建立 RecyclerView.Adapter
接着,建立一個適配器,用於把數據填充到 RecyclerView
中。適配器的角色是:用於把某個位置上的對象轉換爲列表項,並顯示。
固然,RecyclerView
的適配器須要 ViewHolder
對象,用於描述和訪問每一個列表項中的全部視圖。在 ContactsAdapter.java
類中,先建立一個基本的、沒有實際內容的適配器:
-
-
- public class ContactsAdapter extends
- RecyclerView.Adapter<ContactsAdapter.ViewHolder> {
-
-
-
- public static class ViewHolder extends RecyclerView.ViewHolder {
-
-
- public TextView nameTextView;
- public Button messageButton;
-
-
-
- public ViewHolder(View itemView) {
-
-
- super(itemView);
-
- nameTextView = (TextView) itemView.findViewById(R.id.contact_name);
- messageButton = (Button) itemView.findViewById(R.id.message_button);
- }
- }
- }
適配器和 ViewHolder
已經定義好了,接下來該填充它們了。首先,定義一個成員變量,用於存儲聯繫人數據,它在構造方法中被賦值:
- public class ContactsAdapter extends
- RecyclerView.Adapter<ContactsAdapter.ViewHolder> {
-
-
-
-
- private List<Contact> mContacts;
-
- private Context mContext;
-
-
- public ContactsAdapter(Context context, List<Contact> contacts) {
- mContacts = contacts;
- mContext = context;
- }
-
-
- private Context getContext() {
- return mContext;
- }
- }
每一個適配器都有三個主要的方法:1. onCreateViewHolder
:實例化列表項佈局、建立 Holder
;2. onBindViewHolder
:基於數據設置視圖屬性;3. getItemCount
:檢測列表項的數量。爲了完成適配器,須要實現它們:
- public class ContactsAdapter extends
- RecyclerView.Adapter<ContactsAdapter.ViewHolder> {
-
-
-
-
- @Override
- public ContactsAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- Context context = parent.getContext();
- LayoutInflater inflater = LayoutInflater.from(context);
-
-
- View contactView = inflater.inflate(R.layout.item_contact, parent, false);
-
-
- ViewHolder viewHolder = new ViewHolder(contactView);
- return viewHolder;
- }
-
-
- @Override
- public void onBindViewHolder(ContactsAdapter.ViewHolder viewHolder, int position) {
-
- Contact contact = mContacts.get(position);
-
-
- TextView textView = viewHolder.nameTextView;
- textView.setText(contact.getName());
- Button button = viewHolder.messageButton;
- button.setText("Message");
- }
-
-
- @Override
- public int getItemCount() {
- return mContacts.size();
- }
- }
把適配器綁定到 RecyclerView
在 Activity
中,給 RecyclerView
填充用於測試的用戶數據:
- public class UserListActivity extends AppCompatActivity {
-
- ArrayList<Contact> contacts;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
-
-
- RecyclerView rvContacts = (RecyclerView) findViewById(R.id.rvContacts);
-
-
- contacts = Contact.createContactsList(20);
-
- ContactsAdapter adapter = new ContactsAdapter(this, contacts);
-
- rvContacts.setAdapter(adapter);
-
- rvContacts.setLayoutManager(new LinearLayoutManager(this));
-
- }
- }
編譯運行應用,你將看到相似於下圖的結果。若是建立了足夠多的元素,列表就能夠滾動了,你將會發現它的滾動遠比 ListView
流暢的多。
通知適配器
與 ListView
不一樣的是,經過 RecyclerView
的適配器,沒有辦法直接添加或移除列表項。你須要作的是:直接操做數據源,而後通知適配器數據源發生了變化。同時,每當添加或移除元素時,老是應該對已經存在的列表進行操做。例如,下面這句的改變,並不會影響適配器,這是由於適配器保存了舊數據的引用,可是生成的新數據的引用發生了變化。
-
- contacts = Contact.createContactsList(5);
相反,你須要在已經存在的引用上直接進行操做:
-
- contacts.addAll(Contact.createContactsList(5));
若是要給適配器通知不一樣類型的改變,可使用下面的這些方法:
方法 |
描述 |
notifyItemChanged(int pos) |
某個位置上元素改變的通知 |
notifyItemInserted(int pos) |
某個位置上插入元素的通知 |
notifyItemRemoved(int pos) |
某個位置上元素移除的通知 |
notifyDataSetChanged() |
數據集改變的通知 |
在 Activity
或 Fragment
中,應該這樣使用:
-
- contacts.add(0, new Contact("Barney", true));
-
- adapter.notifyItemInserted(0);
每當咱們想給 RecyclerView
添加或移除數據時,咱們須要顯式地通知適配器正在發生的事件是什麼。與 ListView
的適配器不一樣,RecyclerView
的適配器不該該依賴於 notifyDataSetChanged()
,應該使用粒度更細的動做。
若是你打算更新一個已經存在的數據列表,請在改變以前,務必要獲取當前元素的數量。例如,應該調用適配器的 getItemCount()
方法來記錄改變前的索引:
-
- int curSize = adapter.getItemCount();
-
-
- ArrayList<Contact> newItems = Contact.createContactsList(20);
-
-
- contacts.addAll(newItems);
-
-
- adapter.notifyItemRangeInserted(curSize, newItems.size());
定義大的改變
實際生產中,列表的改變每每更爲複雜一些(例如:對已經存在的數據排序),改變一複雜,就不能精確對改變進行一個分類。這種狀況下,一般,須要在整個適配器上調用 notifyDataSetChanged()
方法來更新整個屏幕,這樣,使用動畫來展現改變的能力,就會被減弱。
在 support v7
庫的 v24.2.0
版本中,新增了 DiffUtil
類,用於計算新舊數據之間的不一樣。對於比較大的數據列表來講,推薦使用後臺線程執行計算。
要使用 DiffUtil
類,須要實現該類並實現 DiffUtil.Callback
回調,該回調能夠接收新、舊的數據列表:
- public class ContactDiffCallback extends DiffUtil.Callback {
-
- private List<Contact> mOldList;
- private List<Contact> mNewList;
-
- public ContactDiffCallback(List<Contact> oldList, List<Contact> newList) {
- this.mOldList = oldList;
- this.mNewList = newList;
- }
- @Override
- public int getOldListSize() {
- return mOldList.size();
- }
-
- @Override
- public int getNewListSize() {
- return mNewList.size();
- }
-
- @Override
- public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
-
- return mOldList.get(oldItemPosition).getId() == mNewList.get(newItemPosition).getId();
- }
-
- @Override
- public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
- Contact oldContact = mOldList.get(oldItemPosition);
- Contact newContact = mNewList.get(newItemPosition);
-
- if (oldContact.getName() == newContact.getName() && oldContact.isOnline() == newContact.isOnline()) {
- return true;
- }
- return false;
- }
- }
接着,要在適配器中實現 swapItems()
方法用於執行差別化比較。比較事後,若是有數據被插入、移除、移動或刪除時,調用 dispatchUpdates()
方法來通知適配器:
- public class ContactsAdapter extends
- RecyclerView.Adapter<ContactsAdapter.ViewHolder> {
-
- public void swapItems(List<Contact> contacts) {
-
- final ContactDiffCallback diffCallback = new ContactDiffCallback(this.mContacts, contacts);
- final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);
-
-
- this.mContacts.clear();
- this.mContacts.addAll(contacts);
-
- diffResult.dispatchUpdatesTo(this);
- }
- }
完整的示例代碼請參見 mrmike/DiffUtil-sample。
滾動到新的列表項
把列表項插入到列表的前部分,可是當前列表視圖處於列表的後部分,這時,若是你想把列表顯示頂部的位置,能夠經過下列代碼實現:
- adapter.notifyItemInserted(0);
- rvContacts.scrollToPosition(0);
若是把列表項添加到列表的底部,咱們能夠這樣作:
- adapter.notifyItemInserted(contacts.size() - 1);
- rvContacts.scrollToPosition(mAdapter.getItemCount() - 1);
實現連續的滾動
當用戶滾動到列表的底部時,想要實現加載數據,把新加載的數據添加到列表的尾部的功能,須要使用 RecyclerView
的 addOnScrollListener()
方法,並添加 onLoadMore
方法。具體的實現參見另外一篇教程:EndlessScrollViewScrollListener。
配置 RecyclerView
RecyclerView
能夠自定製,它很是的靈活,能夠從如下幾個方面得以體現:
性能
若是數據源是靜態、不會發生變化的,那咱們能夠啓用最優化,來顯著的提高平滑滾動的效果:
- recyclerView.setHasFixedSize(true);
佈局
使用 layout manager
能夠配置列表項的位置。咱們能夠在 LinearLayoutManager
、GridLayoutManager
和 StaggeredGridLayoutManager
這幾個佈局管理器中進行選擇。
列表項在水平或垂直方向進行線性排列的示例:
-
-
- LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
-
- layoutManager.scrollToPosition(0);
-
- recyclerView.setLayoutManager(layoutManager);
在網格或交錯網格上顯示列表項的示例:
-
- StaggeredGridLayoutManager gridLayoutManager =
- new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
-
- recyclerView.setLayoutManager(gridLayoutManager);
交錯的網格佈局可能以下所示:
要自定義 layout manager
,能夠參見 這篇文章。
裝飾
咱們可使用與 RecyclerView
相關的一些裝飾來修飾列表項,例如使用 DividerItemDecoration
:
- RecyclerView.ItemDecoration itemDecoration = new
- DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST);
- recyclerView.addItemDecoration(itemDecoration);
裝飾的做用是在列表項之間顯示分隔:
Grid 空間裝飾
在 Grid
或 StaggeredGrid
中,修飾能夠在顯示的元素之間添加一致的空間。把 SpacesItemDecoration.java 這個修飾拷貝到你的項目中,看一下效果吧。要想知道更多的細節,請參考 這篇教程 。
動畫
當 RecyclerView
的列表項進入、添加或刪除時,使用 ImteAnimator
能夠給列表項添加自定義的動畫。DefaultItemAnimator
用於定義默認的動畫,它源碼(源碼地址)中複雜的實現說明了要確保以某個序列執行的動畫效果所須要的必須的邏輯。
目前,在 RecyclerView
上實現列表項動畫的最快方式就是使用第三方庫。wasabeef/recyclerview-animators 庫中包含了大量可使用的動畫。引入方式以下:
- repositories {
- jcenter()
- }
-
- //If you are using a RecyclerView 23.1.0 or higher.
- dependencies {
- compile 'jp.wasabeef:recyclerview-animators:2.2.3'
- }
-
- //If you are using a RecyclerView 23.0.1 or below.
- dependencies {
- compile 'jp.wasabeef:recyclerview-animators:1.3.0'
- }
代碼中的使用:
- recyclerView.setItemAnimator(new SlideInUpAnimator());
下面是效果:
新的動畫接口
從 support v23.1.0
開始,RecyclerView.ItemAnimator
新增了一個接口。該庫添加了 ItemHolderInfo
類,它的表現與 MoveInfo
相似,可是在動畫過渡狀態之間,它能更爲優雅的傳遞狀態信息。
不一樣佈局的列表項
若是在單個的 RecyclerView
中,你想展現多種類型的列表項,那你應該參這篇文章。
處理 Touch 事件
- recyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
-
- @Override
- public void onTouchEvent(RecyclerView recycler, MotionEvent event) {
-
- }
-
- @Override
- public boolean onInterceptTouchEvent(RecyclerView recycler, MotionEvent event) {
- return false;
- }
-
- });
Snap to Center 效果
在某些狀況下,咱們可能須要一個橫向滾動的 RecyclerView
。當用戶滾動時,若是某個列表項被暴露了,咱們想讓它 snap to center
,以下圖所示:
LinearSnapHelper
當用戶滑動時,如要實現上圖的效果,在 support v24.2.0
及以上版本中,可使用內嵌的 LinearSnapHelper
類:
- SnapHelper snapHelper = new LinearSnapHelper();
- snapHelper.attachToRecyclerView(recyclerView);