翻译自http://www.vogella.com/tutorials/AndroidRecyclerView/article.html
1 在安卓中的列表(List)和网格(Grid)
1.1 在安卓中使用列表和网格
在手机应用中,以列表或者网格的形式展示元素是一个非常重要的模式。用户可以看到条目(item)的集合并且可以滚动他们。这个条目的集合可以是一个列表、一个网格或者是代表数据的其他的结构。就像如下的图片:
用户通过触摸时间或者是工具栏和这些条目的集合进行交互。每一个条目都可以被选中。选中这个条目可能会显示详情或者是更新工具栏。如下图:
1.2 使用ReclclerView
RecyclerView类就是用来支持显示数据集合的。
这是一个有安卓系统提供的,现在化的ListView和GridView版本。RecyclewView处理了之前空间所存在的问题。它强制了一种编码风格可以让结果有一种好的表现形式。并且它为增加或者删除元素提供了默认的动画。
RecyclerView 允许不同的布局去显示不同的条目。
ReclclerView使用ViewHolder去存储view的引用。 一个ViewHolder类是一个adapter里的静态内部类,维持了不同view的引用。使用这些引用可以省去findViewById()所花费的时间。
1.3 Adapter
一个Adapter负责管理数据模型,并且把它适配到个子的实体。它集成自RecyclerView.Adapter,并且通过RecyclerView.setAdapter方法分配到指定的recycler view。适配器的输入可以是任意的java类,但必须通过getItemCount()返回条目的个数。
适配器为不用的元素填充布局。在onCreateViewHolder 这个方法之后这项活就完成了。每当有一个实体在RecyclerView中可见,这个函数就返回一个ViewHolder 对象。
这个实例被用作访问在被填充的布局中的视图。onCreateViewHolder 方法只有在新的视图必须被创建的时候才会调用。
每一个可见状态的实体都会通过适配器被填入正确的数据模型。一旦数据条目变得课件,适配器就会为刚刚填充的控件指派何时的数据。这项工作由onBindViewHolder 方法完成。
举个例子,一个列表的实体有可能是这样的:嘴边有一个图片,中间由两个文字。比如这样:
布局文件
android:contentDescription="TODO"
android:src="@drawable/ic_launcher" />
<TextView
android:id="@+id/secondLine"
android:layout_width="fill_parent"
android:layout_height="26dip"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_toRightOf="@id/icon"
android:ellipsize="marquee"
android:singleLine="true"
android:text="Description"
android:textSize="12sp" />
<TextView
android:id="@+id/firstLine"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_above="@id/secondLine"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_alignWithParentIfMissing="true"
android:layout_toRightOf="@id/icon"
android:gravity="center_vertical"
android:text="Example application"
android:textSize="16sp" />
</RelativeLayout>
1.4 Gradle 依赖
RecyclerView是单独的控件,在API7+中可以使用,在Gradle中加入依赖
dependencies {
...
compile "com.android.support:recyclerview-v7:23.0.1"
}
1.5 默认的布局管理者
layout manager 决定了数据在RecyclerView中如何战术。RecyclerView库提供了如下的manager;
- LinearLayoutManager 展示了水平或者垂直的滚动列表
- GridLayoutManager 在网格中展示条目
- StaggeredGridLayoutManager 在挫列的网格中展示条目
1.6 相关联的类
- Adapter(必须) 提供数据:为各自的实体创建视图
- ViewHolder(必须 )维持了所有被数据填充的实体的视图的引用
- LayoutManager(必须,但默认的实现就可以):同ViewHolder
- ItemDecoration(默认行为,可以重写):一个实体的周围的装饰
- ItemAnimator(默认行为可以重写):条目增加删除重新排序所产生动画
1.7 处理点击事件
触摸事件,比如说点击,应该在recycler view中被view处理。如果触发了其他对象(比如说activity或者fragment),你应该通过adapter的构造方法传递进来,这样允许adapter存储对象的引用,然后调用它的方法(回调函数)
1.8 使用不同的布局
adapter需要为每一行条目创建布局,根布局一般是ViewGroup (layout manager),包含了很多view,比如说ImageView 或者TextView,下图是奇偶行显示不同的效果
getItemViewType 方法可以让 recycler view 去决定哪一种应该被对象使用。如果这个类型需要的话,框架会子的自动调用onCreateViewHolder 。在 onCreateViewHolder 方法中,你应该填充正确的布局并且返回一个合适的view holder。
1.9 动画
想要自定义动画需要继承RecyclerView.ItemAnimator并且调用RecyclerView.setItemAnimator() 方法,
1.10 过滤和排序
需要在adapter中处理逻辑。
1.11 更新数据
notifyItemInserted(position)在增加一条数据的时候可以通知view去把它加入到何时的位置。
notifyItemRemoved(position)在删除的时候可以通知,
2 练习:一个简单的例子
最终目标
布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="${relativePackage}.${activityClass}" >
<!-- A RecyclerView with some commonly used attributes -->
<android.support.v7.widget.RecyclerView
android:id="@+id/my_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_marginBottom="12dp"
android:layout_marginRight="12dp"
android:elevation="2dp"
android:src="@drawable/ic_add_circle" />
</RelativeLayout>
提示:ImageView有android:elevation属性,会让安卓画一个阴影,
没一个条目的布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:padding="6dip" >
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_alignParentBottom="true"
android:layout_alignParentTop="true"
android:layout_marginRight="6dip"
android:contentDescription="TODO"
android:src="@drawable/ic_launcher" />
<TextView
android:id="@+id/secondLine"
android:layout_width="fill_parent"
android:layout_height="26dip"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_toRightOf="@id/icon"
android:ellipsize="marquee"
android:singleLine="true"
android:text="Description"
android:textSize="12sp" />
<TextView
android:id="@+id/firstLine"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_above="@id/secondLine"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_alignWithParentIfMissing="true"
android:layout_toRightOf="@id/icon"
android:gravity="center_vertical"
android:text="Example application"
android:textSize="16sp" />
</RelativeLayout>
创建如下的类
package com.vogella.android.recyclerview;
import java.util.ArrayList;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.TextView;
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private ArrayList<String> mDataset;
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
public class ViewHolder extends RecyclerView.ViewHolder {
// each data item is just a string in this case
public TextView txtHeader;
public TextView txtFooter;
public ViewHolder(View v) {
super(v);
txtHeader = (TextView) v.findViewById(R.id.firstLine);
txtFooter = (TextView) v.findViewById(R.id.secondLine);
}
}
public void add(int position, String item) {
mDataset.add(position, item);
notifyItemInserted(position);
}
public void remove(String item) {
int position = mDataset.indexOf(item);
mDataset.remove(position);
notifyItemRemoved(position);
}
// Provide a suitable constructor (depends on the kind of dataset)
public MyAdapter(ArrayList<String> myDataset) {
mDataset = myDataset;
}
// Create new views (invoked by the layout manager)
@Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
// create a new view
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.rowlayout, parent, false);
// set the view's size, margins, paddings and layout parameters
ViewHolder vh = new ViewHolder(v);
return vh;
}
// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
// - get element from your dataset at this position
// - replace the contents of the view with that element
final String name = mDataset.get(position);
holder.txtHeader.setText(mDataset.get(position));
holder.txtHeader.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
remove(name);
}
});
holder.txtFooter.setText("Footer: " + mDataset.get(position));
}
// Return the size of your dataset (invoked by the layout manager)
@Override
public int getItemCount() {
return mDataset.size();
}
}
Activity
public class MyActivity extends Activity {
private RecyclerView mRecyclerView;
private RecyclerView.Adapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_activity);
mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
mRecyclerView.setHasFixedSize(true);
// use a linear layout manager
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
// specify an adapter (see also next example)
mAdapter = new MyAdapter(myDataset);
mRecyclerView.setAdapter(mAdapter);
}
...
}