- 简单使用步骤
RecyclerView与ListView的异同
RecyclerView和ListView一样是用于展示大量数据集的部件,两者都能够回收和复用不可见的view来节约资源提高性能。与ListView不同的是,RecyclerView具有更好的灵活性,这主要得益于其插件化和充分解耦的设计:
- RecyclerView不关心items有没有摆放正确、每个item的布局如何等等,它只负责view的回收与复用
- 所有与布局绘制和数据展示有关的任务全都交给相关的内部类完成
RecyclerView与ListView的主要差异:
- 使用ViewHolder - RecyclerView要求使用ViewHolder来提升性能;ListView中没有强制要求使用ViewHolder
- 多种item布局 - RecyclerView中可以通过设置LayoutManager来实现3中item布局;ListView只有纵向线性布局
- 设置item动画 - RecyclerView可以使用itemAnimator类来控制item的添加、删除时动画;ListView没有提供相关便利类
- 数据的适配 - RecyclerView需要继承RecyclerView.Adapter来自行创建数据适配器;ListView则可以根据不同的数据类型选择现成的适配器如ArrayAdapter、CursorAdapter等
-
设置decoration - RecyclerView通过继承RecyclerView.ItemDecoration来自定义divider;ListView则可直接通过
android:divider
属性设置divider -
点击事件 - RecyclerView没有click监听器,而是提供了一个RecyclerView.OnItemTouchListener接口来对触摸事件进行管理,我们可以自行对触摸事件进行判断和拦截来完成点击、长按等实现,当然也可以在adapter中对itemView直接设置点击事件;ListView提供了
setOnItemClickListener()
方法直接处理点击事件
RecyclerView的使用
使用RecyclerView时一般会用到一下几个RecyclerView的内部类:
类名 | 作用 |
---|---|
Adapter | 为每一个item填充布局和数据 |
ViewHolder | 保存每个item的子view |
LayoutManager | 管理items在屏幕上的位置 |
ItemDecoration | 为items添加分隔 |
ItemAnimator | 管理items添加、删除和复用时的动画 |
简单使用步骤
-
在
app/build.gradle
中添加依赖dependencies { ... compile 'com.android.support:appcompat-v7:25.0.0' ... }
-
添加数据
项目中使用presenter为view层提供的
List<Bean>
集合 -
在xml布局文件中添加RecyclerView
<?xml version="1.0" encoding="utf-8"?> ... <android.support.v7.widget.RecyclerView android:id="@+id/recyclerview" android:layout_width="match_parent" android:layout_height="match_parent"/> ...
创建item的布局
-
创建adapter继承自
RecyclerView.Adapter
,并添加内部类ViewHolder继承自RecyclerView.ViewHolder
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.MyViewHolder>{ ... ... class MyViewHolder extends RecyclerView.ViewHolder { ImageView mImageView; TextView mTextView; MyViewHolder(View itemView) { super(itemView); mImageView = (ImageView) itemView.findViewById(R.id.imageview); mTextView = (TextView) itemView.findViewById(R.id.textview); } } }
-
为adapter添加成员变量和构造器
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.MyViewHolder>{ // 保存一个context对象方便调用 private Context mContext; // 数据集合可以在构造器中传入,也可以写一个set方法让adapter在恰当的时候获取 private List<Bean> mBeanList; public MyRecyclerViewAdapter(Context context) { this.mContext = context; } ... ... }
-
复写
RecyclerView.Adapter
中的三个方法public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.ViewHolder> { ... // 获取itemView的布局,创建并返回其ViewHolder @Override public MyRecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(context); // 创建itemView对象 View view = inflater.inflate(R.layout.item_view, parent, false); // 返回itemView的ViewHolder实例 ViewHolder viewHolder = new ViewHolder(view); return viewHolder; } // 使数据与ViewHolder中的组件绑定 @Override public void onBindViewHolder(MyRecyclerViewAdapter.ViewHolder viewHolder, int position) { // 通过position获取当前位置对应的数据 Bean bean = mBeanList.get(position); // viewHolder中各组件从bean中获取数据 ... } // 获取item总个数 @Override public int getItemCount() { return mBeanList.size(); } }
-
在activity或fragment中使用RecyclerView
public class MyActivity extends Activity { private RecyclerView mRecyclerView; private MyRecyclerViewAdapter mAdapter; private LinearLayoutManager mLayoutManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.my_activity); mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view); // 设置LayoutManager,RecyclerView提供了3中LayoutManager:linear, grid, staggeredGrid mLayoutManager = new LinearLayoutManager(this); mRecyclerView.setLayoutManager(mLayoutManager); // 设置adapter mAdapter = new MyRecyclerViewAdapter(this); mRecyclerView.setAdapter(mAdapter); } ... }
-
更新adapter中的数据集合
... mAdapter.setData(beanList); mAdapter.notifyDataSetChanged(); ...
使用
notifyDataSetChanged()
来更新数据时会使用默认动画DefaultItemAnimator
,所以如果自定义了item动画的话最好使用notifyItemChanged(int position)
或者notifyItemRangeChanged(int positionStart, int itemCount)
来通知adapter
关于Item点击事件的监听
一种简单的方式就是在onBindViewHolder
时调用view的setOnClickListener()
方法;或者定义ViewHolder时让其操作OnClickListener
接口:
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.MyViewHolder>{
... ...
class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
ImageView mImageView;
TextView mTextView;
MyViewHolder(View itemView) {
super(itemView);
mImageView = (ImageView) itemView.findViewById(R.id.imageview);
mTextView = (TextView) itemView.findViewById(R.id.textview);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View view) {
// 处理点击事件
}
}
}
如果需要在activity或者fragment中处理点击事件,则可以在adapter中设计一个接口供外部调用:
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.MyViewHolder>{
... ...
// listener接口成员变量
private OnItemClickListener listener;
// 定义listener接口
public interface OnItemClickListener {
void onItemClick(View itemView, int position);
}
// 定义set方法供外部使用listener接口
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
class MyViewHolder extends RecyclerView.ViewHolder {
ImageView mImageView;
TextView mTextView;
MyViewHolder(View itemView) {
super(itemView);
mImageView = (ImageView) itemView.findViewById(R.id.imageview);
mTextView = (TextView) itemView.findViewById(R.id.textview);
itemView.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view) {
if (listener != null) {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
listener.onItemClick(itemView, position);
}
}
}
});
}
}
}
在activity或fragment中使用:
...
mAdapter.setOnItemClickListener(new MyRecyclerViewAdapter.OnItemClickListener(){
@Override
public void onItemClick(View view, int position) {
// 处理点击
}
});
...
关于滚动事件的监听
RecyclerView的滚动事件可以使用addOnScrollListener
方法监听:
...
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
// 滚动完成时的回调
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
lastVisibleItem = mLayoutManager.findLastVisibleItemPosition();
}
// 滚动状态变化时的回调
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItem + 1 >= mLayoutManager.getItemCount() && !isLoadingMore) {
isLoadingMore = true;
mGankNewsPresenter.loadMoreData(type);
}
}
});
...
滚动的过程一般分为2种:
- 静止 -> 手指拖拽 -> 静止
- 静止 -> 手指拖拽 -> 惯性滚动 -> 静止
对应到onScrollStateChanged
中的newState值:
//静止,没有滚动
public static final int SCROLL_STATE_IDLE = 0;
//正在被外部拖拽,一般为用户正在用手指滚动
public static final int SCROLL_STATE_DRAGGING = 1;
//自动滚动开始
public static final int SCROLL_STATE_SETTLING = 2;
所以上面onScrollStateChanged
中的条件可以翻译为:滚动停止 && 倒数第二个item已经可见 && 不在加载过程中
onScrolled
中dx和dy的含义:
int dx // 水平滚动距离,dx > 0 为手指向左滑动,列表从右边滚入
int dy // 垂直滚动距离,dy > 0 为手指向上滑动,列表从下方滚入
设置Decoration
google提供了一个RecyclerView.ItemDecoration的实现类DividerItemDecoration作为默认的divider,使用方法如下:
DividerItemDecoration itemDecoration = new DividerItemDecoration(getHoldingActivity(), DividerItemDecoration.VERTICAL);
mRecyclerView.addItemDecoration(itemDecoration);
DividerItemDecoration实现主要包括三个方法:
onDraw(Canvas c, RecyclerView parent, State state)
onDrawOver(Canvas c, RecyclerView parent, State state)
getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
其绘制过程大致为:
- 获取一个drawable资源
mDivider
和绘制方向mOrientation
- 在
onDraw()
或onDrawOver()
中确定画布canvas
和资源mDivider
的边界,然后将mDivider
绘制在画布上。onDraw()
和onDrawOver()
的区别在于前者的绘制发生在item的绘制之前,后者发生在item绘制之后 -
getItemOffsets()
中为item设置上下左右的padding值。若绘制时使用的是onDraw()
则一般预留出与mDivider
的高或者宽相当的padding,为的是防止mDivider
被后来绘制的item挡住;若使用onDrawOver()
则看心情设置吧,反正都不会被挡住 ┑( ̄Д  ̄)┍
在最新版的DividerItemDecoration中还提供了一个setDrawable(Drawable drawable)
方法,方便我们自己定制divider的样式。比如我在res/drawable
目录下新建一个divider_drawable.xml
文件:
<?xml version="1.0" encoding="utf-8"?>
<inset
xmlns:android="http://schemas.android.com/apk/res/android"
android:insetLeft="16dp"
android:insetRight="16dp">
<shape
android:shape="rectangle">
<size android:height="1dp"/>
<solid android:color="@color/colorPrimaryDark"/>
</shape>
</inset>
然后在构造decoration时用这个文件替换默认的divider资源文件:
DividerItemDecoration itemDecoration = new DividerItemDecoration(getHoldingActivity(), DividerItemDecoration.VERTICAL);
DividerItemDecoration.setDrawable(getResources().getDrawable(R.drawable.divider_drawable));
mRecyclerView.addItemDecoration(itemDecoration);
替换后效果如下:
可以看到由于默认的getItemOffsets()
设定了bottom的padding值,所以在divider的左右两边露出了RecyclerView下面一层的背景色。对于这种情况,可以仿造默认的DividerItemDecoration自己继承RecyclerView.ItemDecoration实现一个decoration,将绘制方法改为onDrawOver()
,并在getItemOffsets()
中不设置padding值,就可以让divider绘制在item的上方。
当然设置divider还有一种更简单的方法,直接在item的布局文件中添加一个ImageView画一条线就好了( ̄Д ̄)ノ
设置Animation
同样的google也提供了一个默认的动画DefaultItemAnimation,可以使用setItemAnimation()
方法来设置。
我们也可以继承RecyclerView.ItemAnimation来自己定义item动画。这里推荐一个第三方动画库recyclerview-animatiors,简单好用可拓展。
参考资料 & 推荐阅读:
RecyclerView整体理解和使用
http://www.grokkingandroid.com/first-glance-androids-recyclerview/
https://guides.codepath.com/android/using-the-recyclerview#attaching-click-listeners-with-decorators
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html
http://www.jianshu.com/p/12ec590f6c76
点击事件
http://www.jianshu.com/p/f2e0463e5aef
滚动事件
http://blog.devwiki.net/index.php/2016/06/13/RecyclerView-Scroll-Listener.html
完整项目在我的github上,如果碰巧能帮到您不妨去点个star吧 ( ̄∇ ̄)