如何使用RecyclerView

1. Overview

 

    这是一篇翻译文章,自己作为android工程师以来,第一次翻译国外网站的文章,所以不免会有一些遗漏和错误。

    原文请参考

    RecyclerView 是一种新的ViewGroup,它的存在是为了像其他的viewGroup一样提供以adapter 为基础的view。它将成为ListViewGridView的继承者(这也是我翻译这篇文章的原因)。这个类是由support-v7提供的支持;之所以这么说的原因有:recyclerView 拥有更加可以拓展的framework,尤其是在它支持了实现水平和垂直的layout。当你的数据集会根据用户的action或者网络事件而进行change的时候要使用RecyclerView

如果你想要使用RecyclerView,你需要进行下面的工作:

  1. . RecyclerView.Adapter-用于处理数据集和绑定它到一个view
  2. .LayoutManager- 用于定位items
  3. .ItemAnimator- 为通用的操作(如:增加和移除item)进行动画处理

另外,它为listView添加和删除添加的动画效果和现在目前的实现是完全不同的。RecyclerView也在开始加强ViewHolderPattern

这个动作目前是一个值得推荐的经验,而且现在已经深深的在新的framework中集成了。

更多的细节请看这个link

1.1 与ListView 对比

RecyclerView 不同于它的父本ListView是因为一下的功能:

  • . Required VIewHolder in Adapters- ListView adapters不要求使用ViewHolder模式去增加性能(一般开发者会用到viewHolder),相反,RecylcerView是被要求使用ViewHolder的。
  • .Customizable Item Layouts ListView 能够且只能够布局垂直Linear而且这个不能由用户定制。相反,RecyclerView因为拥有RecylerView.LayoutManger所以可以允许所有的itemlayouts包括水平liststaggeredgrids
  • . easy item animations listView 并不包含特别提供的产品来提供对于item增减时可以产生动画的内容。相反,RecyclerView拥有RecyclerView.ItemAnimator类来处理itemanimations
  • .Manual Data Source Listview拥有类似与ArrayAdapterCursorAdapter等不同的Adapters用于处理arrays database结果集。相反,RecyclerView.Adapter要求用户实现来支持数据adapter
  • .Manual Item Decoration ListView 拥有androiddivider属性来快速的间隔list中的各item。相对的,RecyclerView要求使用RecyclerViewItemDecoration对象来实现更多的手动的divider修饰。
  • . Manual Click Detection Listview 拥有一个AdapterView.OnItemClickListener接口来绑定按键事件。相应的,REcyclerVIew只支持RecyclerView.OnItemTouchListener,它将管理分散的touch事件,但是RecyclerView却没有创建好了的clickhanding

2. RecyclerView 的组成

2.1 LayoutManagers

一个RecyclerView需要拥有一个layout Manager 和一个Adapter来进行实例化。一个layoutmanager 将itemviews插入到RecyclerView同时决定什么时候重用那些相当用户不在可见的itemviews。
RecyclerView 提供下面这些build-inlayout managers:
  •   LinearLayoutManager 显示在垂直和水平滚动的items
  •   GridLayoutManger 显示在grid中的items
  •   StaggerdGridLayoutManager 显示在staggered grid 中的items
在创建一个用户的layout manager,extend RecyclerView.LyaoutManager类.
这个link 里面是 Dave Smith’s talk 关于custom layout manager
 

2.2 RecyclerVIew.Adapter

RecyclerView 包含一个新品质的adapter。它类似于一个你已经使用过的adpter,但又具备了一些特殊的特征,例如,要求ViewHolder.你将必须override两个主要的方法:一个是inflate这个view和它的viewholder,另一个是bind 数据给这个view. 好消息是第一个方法只是在我们真的需要创建一个新view的时候。不需要检查它是否被回收了。

 

2.3 ItemAnimator

RecyclerView.ItemAnimator将使ViewGroup给adapter提醒的修改(如:增删选择)产生动画效果。DefaultItemAnimator将被用来作为基本的默认动画,而且有非常好的作用效果。可以看这个后面相关的section来获取更多的信息
 

3. Using the RecyclerView

使用一个RecyclerView 有以下几个关键的步骤:

1.            增加RecyclerView支持库到gradle build file

2.            定义一个modelclass 作为datasource

3.            添加一个RecyclerView给你的activity来显示这个items

4.            创建一个customrow layout XML 文件来可视化一个item

5.            创建一个RecyclerView.AdapterViewHolder 来提供给item

6.            绑定这个adapterdatasource populatethe RecyclerView.

这些步骤在下面会有详细的阐述

3.1 Installation

确认recyclerView支持库在你的app/build.gradle中列出了了:

 

dependencies {
    ...
    compile 'com.android.support:recyclerview-v7:23.2.1'
}

3.2 定义一个Model

每个RecyclerView 都是有一个数据集的作为背景的。由此,我们将定义一个Contact 类,这个类将代表需要被RecylcerView使用的数据模型。

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;
    }
}

3.3 创建一个RecycleerView 的layout

activitylayout 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>
<p><span style="font-size:14px;">在<span style="font-family:Calibri;">layout</span>的<span style="font-family:Calibri;">preview</span>窗口我们将看到在<span style="font-family:Calibri;">activity</span>中的<span style="font-family:Calibri;">RecylcerView</span>。</span></p>

3.4 创建Custom Row Layout

我们在创建adapter之前,我们需要先创建在每一row中需要使用的XMLlayout 文件。现在我们试图穿件一个水平的Linearlayout,它带有一个textVIew用于显示名字,和一个button,来提醒这个person:
 

如何使用RecyclerView_第1张图片

这个layout文件可以在res/layout/item_contact.xml 创建,而且它将提供给每行item。注意你应该为属性layout_height使用wrap_content,因为在先于版本23.2.1RecyclerView忽略了layout参数。可以参考这个link for 更多的信息。

<?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>
在定义了custom item layout之后,我们可以创建一个adapter来放置数据到这个recyclerview中。

3.5 Creating the RecyclerView.Adapter

我们将在这里创建一个adapter,这个将从实际上将数据填充到RecyclerView中。这个Adapter的角色是将一个position上的对象转变从list上面的一个item并插入之。

然而,这个adapter要求存在一个“ViewHolder”对象,这个对象描述和提供进入每一行的所有的views。我们可以在ConatactsAdapter.java中提供基本的空adapter holder

 

// Create the basic adapter extending from RecyclerView.Adapter
// Note that we specify the custom ViewHolder which gives us access to our views
public class ContactsAdapter extends 
    RecyclerView.Adapter<ContactsAdapter.ViewHolder> {

    // Provide a direct reference to each of the views within a data item
    // Used to cache the views within the item layout for fast access
    public static class ViewHolder extends RecyclerView.ViewHolder {
        // Your holder should contain a member variable
        // for any view that will be set as you render a row
        public TextView nameTextView;
        public Button messageButton;

        // We also create a constructor that accepts the entire item row
        // and does the view lookups to find each subview
        public ViewHolder(View itemView) {
            // Stores the itemView in a public final member variable that can be used
            // to access the context from any ViewHolder instance.
            super(itemView);

            nameTextView = (TextView) itemView.findViewById(R.id.contact_name);
            messageButton = (Button) itemView.findViewById(R.id.message_button);
        }
} 
}
现在我们已经定义了基本的adapter和ViewHolder,我们现在需要填充adapter。首先,让我们保存一个变量用于contacts list,同时通过构造器传递这个list
public class ContactsAdapter extends 
    RecyclerView.Adapter<ContactsAdapter.ViewHolder> {

    // ... view holder defined above...

    // Store a member variable for the contacts
    private List<Contact> mContacts;
    // Store the context for easy access
    private Context mContext;

    // Pass in the contact array into the constructor
    public ContactsAdapter(Context context, List<Contact> contacts) {
        mContacts = contacts;
        mContext = context;
    }

    // Easy access to the context object in the recyclerview
    private Context getContext() {
       return mContext;
    }
}

每个adapter拥有3个基本方法:onCreateViewHolder用来inflate一个item layout和创建这个holderonBindViewHolder用于设置基于数据的view的属性,getItemCount来获取items的数量。我们需要implement所有的这三个来完成adapter

public class ContactsAdapter extends 
    RecyclerView.Adapter<ContactsAdapter.ViewHolder> {

    // ... constructor and member variables

    // Usually involves inflating a layout from XML and returning the holder
    @Override
    public ContactsAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Context context = parent.getContext();
        LayoutInflater inflater = LayoutInflater.from(context);

        // Inflate the custom layout
        View contactView = inflater.inflate(R.layout.item_contact, parent, false);

        // Return a new holder instance
        ViewHolder viewHolder = new ViewHolder(contactView);
        return viewHolder;
    }
    
    // Involves populating data into the item through holder
    @Override
    public void onBindViewHolder(ContactsAdapter.ViewHolder viewHolder, int position) {
        // Get the data model based on position
        Contact contact = mContacts.get(position);

        // Set item views based on your views and data model
        TextView textView = viewHolder.nameTextView;
        textView.setText(contact.getName());
        Button button = viewHolder.messageButton;
        button.setText("Message");
    }

    // Returns the total count of items in the list
    @Override
    public int getItemCount() {
        return mContacts.size();
    }
}

adapter完成后,所有剩余的工作是绑定数据从adapterRecyclerView

3.6 Binding the Adapter to the RecyclerView

在我们的activyt中,我们创建了一个用户集用于在recyclerview中显示。


 

public class UserListActivity extends AppCompatActivity {

     ArrayList<Contact> contacts;

     @Override
     protected void onCreate(Bundle savedInstanceState) {
         // ...
         // Lookup the recyclerview in activity layout
         RecyclerView rvContacts = (RecyclerView) findViewById(R.id.rvContacts);

         // Initialize contacts
         contacts = Contact.createContactsList(20);
         // Create adapter passing in the sample user data
         ContactsAdapter adapter = new ContactsAdapter(this, contacts);
         // Attach the adapter to the recyclerview to populate items
         rvContacts.setAdapter(adapter);
         // Set layout manager to position the items
         rvContacts.setLayoutManager(new LinearLayoutManager(this));
         // That's all!
     }
}

最后,编译运行,将看到下面图像类似的结果。如果你创建了足够多的items,当scroll这个list,这个views将循环利用而且相对listView更加顺畅的滚动。


3.7 Notifying the Adapter

不像ListViewRecyclerView没有直接添加或者删除item的方法。你需要直接修改原始数据并且通知Adapter相关的任何修改。同时,在你添加或者删除元素的时候,需要同时修改已经存在的list。例如,重新初始化下面的contactslist将不会产生任何对adapter的效果,因为它拥有一个对oldlist memory的一个引用。

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

Instead,你需要直接对已经存在的引用进行操作:

// add to the existing list
Contacts.addAll(Contact.createContactsList(5));
有很多接口用于提醒adapter关于不同的修改:

 

Method Description

notifyItemChanged(int pos)      

Notifythat item at position has changed.
notifyItemInserted(intpos) Notifythat item reflected at position has been newly inserted.
notifyItemRemoved(intpos)

Notify that items previously located at position has beenremoved from the data set.

notifyDataSetChanged()                             

Notify that the dataset has changed. Use only as lastresort.


我们能够如下在activityfragment中使用它们

// Add a new contact
contacts.add(0, new Contact("Barney", true));
// Notify the adapter that an item was inserted at position 0
Adapter.nofityItemInserted(0);

每当我们想从RecyclerView添加或者删除items的时候,我们需要清楚的通知adapter这个事件。不像ListViewadapter,一个RecyclerViewadapter不能够依赖notifyDataSetChanged(),而是需要使用更加细粒度的action。可以参考如下API文档。

另外,如果你试图update一个存在的list,在进行任何修改前,确定你获取到了当前item的数量。例如,在adapter上面的getItemCount()应该被调用来记录即将修改的第一个index

// record this value before making any changes to the existing list
int curSize = adapter.getItemCount();

// replace this line with wherever you get new records
ArrayList<Contact> newItems = Contact.createContactsList(20); 

// update the existing list
contacts.addAll(newItems);
// curSize should represent the first element that got added
// newItems.size() represents the itemCount
Adapter.nofityItemRangeInserted(curSize,newItems,size());

3.8 滚动到新的items

如果我们给要在list的前面插入元素而且想让这个元素保持在最前面,我们可以将位置滚动到1st 元素
adapter.notifyItemInserted(0);  
rvContacts.scrollToPosition(0);   // index 0 position

如果我们要在list的最后添加items而且想要滚动到底部作为item的添加。我们可以提醒这个adapter一个添加的元素已经被添加,而且能够调用RecyclerViewsmoothScrollToPosition()

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

3.9 Implementing Endless Scrolling

 
为了实现集成更多的数据,并且像用户滚动到list的底部一样滚动到底部,可以使用RecyclerView中的addOnScrollListener()并且添加一个onLoadMore方法leveraging theEndlessScrollViewScrollListener文档。

4. Configuring the RecyclerView

RecyclerVIew 是一个非常的具有弹性和用户定制化的控件。一些可用的options可以在下面看到。

4.1 Performance

在所有的item是同样的高度和宽度的情况下,我们可以使得滚动更加的平滑:   

recyclerView.setHasFixedSize(true);

4.2 Layouts

每个item的位置是被layout manager 来进行配置的。默认情况下,我们可以选择LinearLayoutManagerGridLayoutManager,和StaggerdGridLayoutManagerLinear 显示items要么是水平的要么就是垂直的:

 

// Setup layout manager for items
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
// Control orientation of the items
// also supports LinearLayoutManager.HORIZONTAL
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
// Optionally customize the position you want to default scroll to
layoutManager.scrollToPosition(0);
// Attach layout manager to the RecyclerView
recyclerView.setLayoutManager(layoutManager);

使用grid 或者 staggerd grid(交错的grid)也可以有类似的操作:

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

例如,一个staggerdgrid 将有如下的显示:

我们可以创建用户的layout mangers,就像这个link提供的一样。

4.3 Decorations

我们可以使用多种装饰品来为recyclerview装饰这些item就像DividerItemDecoration一样:

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

这个decorator显示这个分界线的效果图如下:

如何使用RecyclerView_第2张图片

4.3.2 Grid Spacing Decorations

装饰可以同时被用来添加在grid layoutstaggerd griditems周围的相关空间。可以CopySpacesItemDecoration.java decorator 到你的project同时使用addItemDecoration方法将其应用到RecyclerView。参考staggered grid tutorial 获取更多的细节。

4.4 Animators

RecyclerView支持用户动画来为item 提供entermove,或者删除,是通过ItemAnimator来实现这一点的。默认的动画效果是被 DefaultItemAnimator 定义的,它复杂的实现方式(看 source code)显示了它的动画效果可以在特殊的操作中(remove moveadd)中同样可以表现的非常好。

目前,最快的实现RecyclerView的动画效果的是使用第3方的lib。这个 Third-partyrecyclerview-animators libray 包含了很多动画以提供你使用而无需创建你自己的。只需要简单的修改你的app/build.gradle:

repositories {
    jcenter()
}

dependencies {
compile 'jp.wasabeef:recyclerview-animators:2.2.0'
}

接着,我们就可以使用任何已经定义的动画来修改RecyclerView 的行为了:

recyclerView.setItemAnimator(new SlideInUpAnimator());
<img src="https://i.imgur.com/v0VyQS8.gif" alt="" />

4.4.2 New ItemAnimator interface

从为了支持RecyclerViewsupportv23.1.0 library开始,同时也为ItemAnimator接口提供了一个新的接口。这个老的接口已经被deprecated to SimpleItemAnimator。这个lib 添加了一个 ItemHolderInfo 类,他的表现和被DefaultItemAnimator定义MoveInfo类相似,但是被使用的更加普遍以在animationtransition states中传递状态信息。很可能在下一个版本的defaultItemAnimator中将简化的使用这个新的类和新定制的接口。

4.5 Heterogeneous Views

如果你想inflate多种类型的row到一个单独的RecyclerVIew请看link中的 guide这个将可以帮助我们将包含多种不同类型的items添加到一个single list,效果图如下:

如何使用RecyclerView_第3张图片

在single list中包含非常多不同类型的items的时候,这个设计时非常有用的。

4.6 Handling Touch Events

RecyclerView 允许我们处理touch events使用下面的代码:

recyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
    @Override
    public void onTouchEvent(RecyclerView recycler, MotionEvent event) {
        // Handle on touch events here
    }
    @Override
    public boolean onInterceptTouchEvent(RecyclerView recycler, MotionEvent event) {
        return false;
    }
});

4.7 Attaching Click Handlers to Items

4.7.1 Attaching Click Listeners with Decorators

最早的解决RecyclerView 的处理item click handlers 的解决方案是使用一个decorator类来管理itemclick listener。在拥有了这个 Clever ItemClickSupportdecorator后,添加一个clickhandler可以用下面的方式:

ItemClickSupport.addTo(mRecyclerView).setOnItemClickListener(
  new ItemClickSupport.OnItemClickListener() {
      @Override
      public void onItemClicked(RecyclerView recyclerView, int position, View v) {
          // do it
      }
} 
);
在掩护下,这个接口的封装会在下面有详细的描述。如果你使用了上面的这些code,你你讲不需要下面的任何的手动clickhanding。这个技术最原始的来历是在这个link中。

4.7.2 Simple Click Handler within ViewHolder

RecyclerView 不像ListView 拥有setOnItemClickListener一样拥有一些特殊的前置的给itemsattach click handlers 的方法。为了实现同样的效果(代替上面的使用decorator utility),我们可以将clickevent attach 到我们的adapter中的ViewHolder中:

public class ContactsAdapter extends RecyclerView.Adapter<ContactsAdapter.ViewHolder> {
    // ...
    // Used to cache the views within the item layout for fast access
    public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        public TextView tvName;
        public TextView tvHometown;
        private Context context;
        public ViewHolder(Context context, View itemView) {
            super(itemView);
            this.tvName = (TextView) itemView.findViewById(R.id.tvName);
            this.tvHometown = (TextView) itemView.findViewById(R.id.tvHometown);
            // Store the context
            this.context = context;
            // Attach a click listener to the entire row view
            itemView.setOnClickListener(this);
        }
        // Handles the row being being clicked
        @Override
        public void onClick(View view) {
            int position = getLayoutPosition(); // gets item position
            User user = users.get(position);
            // We can access the data within the views
            Toast.makeText(context, tvName.getText(), Toast.LENGTH_SHORT).show();
        }
    }
// ...
}

如果我们想在press item时显示“selected”效果,我们可以给rowrootlayout设置androidbackground属性成为?android:attr/selectableItemBackground:

<?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="match_parent"
    android:background="?android:attr/selectableItemBackground"> 
<!-- ... -->
</LinearLayout>


这个将带来如下图所示的效果:

4.7.3 Attaching Click Handlers using Listeners

在实际的运用中,你可能在RecyclerView中想给view设置clickhandlers,但是将click的逻辑定义在Activity或者Fragment中。为了达到这个目的,在adapter中创建一个 custom listener(http://guides.codepath.com/android/Creating-Custom-Listeners)然后firethe events upwards to an interface implementation defined within the parent :
public class ContactsAdapter extends RecyclerView.Adapter<ContactsAdapter.ViewHolder> {
    // ...

    /***** Creating OnItemClickListener *****/
    
    // Define listener member variable    
    private static OnItemClickListener listener;
    // Define the listener interface
    public interface OnItemClickListener {
        void onItemClick(View itemView, int position);
    }
    // Define the method that allows the parent activity or fragment to define the listener
    public void setOnItemClickListener(OnItemClickListener listener) {
        this.listener = listener;
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TextView tvName;
        public TextView tvHometown;

        public ViewHolder(final View itemView) {
            super(itemView);
            this.tvName = (TextView) itemView.findViewById(R.id.tvName);
            this.tvHometown = (TextView) itemView.findViewById(R.id.tvHometown);
            // Setup the click listener
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // Triggers click upwards to the adapter on click
                    if (listener != null)
                        listener.onItemClick(itemView, getLayoutPosition());
                }
            });
        }
    }
    // ...
}
然后,我们可以attach a click handler adapter,如下:
// In the activity or fragment
ContactsAdapter adapter = ...;
adapter.setOnItemClickListener(new ContactsAdapter.OnItemClickListener() {
    @Override
    public void onItemClick(View view, int position) {
        String name = users.get(position).name;
        Toast.makeText(UserListActivity.this, name + " was clicked!", Toast.LENGTH_SHORT).show();
}
});
可以参考这个stackoverflow post来看看如何在RecyclerView中设置一个item-levelclickhandlers

5. Implementing Pull to Refresh

SwipeRefreshLayout 应该在当RecyclerVIew像通过向下拉的动作刷新内容的时候使用到。请参考我们的关于RecyclerView with SwipeRefreshLayout 的详细的detailguide,一步一步的实现下来刷新。


6. References

•https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html
•http://www.grokkingandroid.com/first-glance-androids-recyclerview/
•http://www.grokkingandroid.com/statelistdrawables-for-recyclerview-selection/
•http://www.bignerdranch.com/blog/recyclerview-part-1-fundamentals-for-listview-experts/
•https://developer.android.com/training/material/lists-cards.html
•http://antonioleiva.com/recyclerview/
•https://code.tutsplus.com/tutorials/getting-started-with-recyclerview-and-cardview-on-android--cms-23465
•https://code.tutsplus.com/tutorials/introduction-to-the-new-lollipop-activity-transitions--cms-23711


 
 
 


 

 
 


 

 


 

 



 

 


 

 

 

 


 


 



 



 

 

 

 




 

 

 

 

 

 

 

 

你可能感兴趣的:(android,RecyclerView)