这是一篇翻译文章,自己作为android工程师以来,第一次翻译国外网站的文章,所以不免会有一些遗漏和错误。
原文请参考
RecyclerView 是一种新的ViewGroup,它的存在是为了像其他的viewGroup一样提供以adapter 为基础的view。它将成为ListView和GridView的继承者(这也是我翻译这篇文章的原因)。这个类是由support-v7提供的支持;之所以这么说的原因有:recyclerView 拥有更加可以拓展的framework,尤其是在它支持了实现水平和垂直的layout。当你的数据集会根据用户的action或者网络事件而进行change的时候要使用RecyclerView。
如果你想要使用RecyclerView,你需要进行下面的工作:
另外,它为listView添加和删除添加的动画效果和现在目前的实现是完全不同的。RecyclerView也在开始加强ViewHolderPattern
这个动作目前是一个值得推荐的经验,而且现在已经深深的在新的framework中集成了。
更多的细节请看这个link:
RecyclerView 不同于它的父本ListView是因为一下的功能:
一个RecyclerView需要拥有一个layout Manager 和一个Adapter来进行实例化。一个layoutmanager 将itemviews插入到RecyclerView同时决定什么时候重用那些相当用户不在可见的itemviews。
RecyclerView 提供下面这些build-inlayout managers:
在创建一个用户的layout manager,extend RecyclerView.LyaoutManager类.
这个link 里面是 Dave Smith’s talk 关于custom layout manager
RecyclerView 包含一个新品质的adapter。它类似于一个你已经使用过的adpter,但又具备了一些特殊的特征,例如,要求ViewHolder.你将必须override两个主要的方法:一个是inflate这个view和它的viewholder,另一个是bind 数据给这个view. 好消息是第一个方法只是在我们真的需要创建一个新view的时候。不需要检查它是否被回收了。
RecyclerView.ItemAnimator将使ViewGroup给adapter提醒的修改(如:增删选择)产生动画效果。DefaultItemAnimator将被用来作为基本的默认动画,而且有非常好的作用效果。可以看这个后面相关的section来获取更多的信息
使用一个RecyclerView 有以下几个关键的步骤:
1. 增加RecyclerView支持库到gradle build file
2. 定义一个modelclass 作为datasource
3. 添加一个RecyclerView给你的activity来显示这个items
4. 创建一个customrow layout XML 文件来可视化一个item
5. 创建一个RecyclerView.Adapter和ViewHolder 来提供给item
6. 绑定这个adapter给datasource 来populatethe RecyclerView.
这些步骤在下面会有详细的阐述
确认recyclerView支持库在你的app/build.gradle中列出了了:
dependencies { ... compile 'com.android.support:recyclerview-v7:23.2.1' }
每个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; } }
在activity的layout 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>
我们在创建adapter之前,我们需要先创建在每一row中需要使用的XMLlayout 文件。现在我们试图穿件一个水平的Linearlayout,它带有一个textVIew用于显示名字,和一个button,来提醒这个person:
这个layout文件可以在res/layout/item_contact.xml 创建,而且它将提供给每行item。注意你应该为属性layout_height使用wrap_content,因为在先于版本23.2.1的RecyclerView忽略了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中。
我们将在这里创建一个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和创建这个holder,onBindViewHolder用于设置基于数据的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完成后,所有剩余的工作是绑定数据从adapter到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更加顺畅的滚动。
不像ListView,RecyclerView没有直接添加或者删除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. |
我们能够如下在activity和fragment中使用它们
// 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());
如果我们给要在list的前面插入元素而且想让这个元素保持在最前面,我们可以将位置滚动到1st 元素
adapter.notifyItemInserted(0); rvContacts.scrollToPosition(0); // index 0 position如果我们要在list的最后添加items而且想要滚动到底部作为item的添加。我们可以提醒这个adapter一个添加的元素已经被添加,而且能够调用RecyclerView的smoothScrollToPosition():
adapter.notifyItemInserted(contacts.size() - 1); // contacts.size() - 1 is the last element positionrvContacts.scrollToPosition(mAdapter.getItemCount() - 1); // update based on adapter
为了实现集成更多的数据,并且像用户滚动到list的底部一样滚动到底部,可以使用RecyclerView中的addOnScrollListener()并且添加一个onLoadMore方法leveraging theEndlessScrollViewScrollListener文档。
RecyclerVIew 是一个非常的具有弹性和用户定制化的控件。一些可用的options可以在下面看到。
在所有的item是同样的高度和宽度的情况下,我们可以使得滚动更加的平滑:
recyclerView.setHasFixedSize(true);
每个item的位置是被layout manager 来进行配置的。默认情况下,我们可以选择LinearLayoutManager,GridLayoutManager,和StaggerdGridLayoutManager。Linear 显示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提供的一样。
我们可以使用多种装饰品来为recyclerview装饰这些item就像DividerItemDecoration一样:
RecyclerView.ItemDecoration itemDecoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST); recyclerView.addItemDecoration(itemDecoration);
这个decorator显示这个分界线的效果图如下:
装饰可以同时被用来添加在grid layout或staggerd grid中items周围的相关空间。可以CopySpacesItemDecoration.java decorator 到你的project同时使用addItemDecoration方法将其应用到RecyclerView。参考staggered grid tutorial 获取更多的细节。
RecyclerView支持用户动画来为item 提供enter,move,或者删除,是通过ItemAnimator来实现这一点的。默认的动画效果是被 DefaultItemAnimator 定义的,它复杂的实现方式(看 source code)显示了它的动画效果可以在特殊的操作中(remove, move,add)中同样可以表现的非常好。
目前,最快的实现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="" />
从为了支持RecyclerView的supportv23.1.0 library开始,同时也为ItemAnimator接口提供了一个新的接口。这个老的接口已经被deprecated to SimpleItemAnimator。这个lib 添加了一个 ItemHolderInfo 类,他的表现和被DefaultItemAnimator定义MoveInfo类相似,但是被使用的更加普遍以在animationtransition states中传递状态信息。很可能在下一个版本的defaultItemAnimator中将简化的使用这个新的类和新定制的接口。
如果你想inflate多种类型的row到一个单独的RecyclerVIew请看link中的 guide这个将可以帮助我们将包含多种不同类型的items添加到一个single list,效果图如下:
在single list中包含非常多不同类型的items的时候,这个设计时非常有用的。
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; } });
最早的解决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中。
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”效果,我们可以给row的rootlayout设置android:background属性成为?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>
这个将带来如下图所示的效果:
在实际的运用中,你可能在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-level的clickhandlers
SwipeRefreshLayout 应该在当RecyclerVIew像通过向下拉的动作刷新内容的时候使用到。请参考我们的关于RecyclerView with SwipeRefreshLayout 的详细的detailguide,一步一步的实现下来刷新。
•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