一 前言
RecyclerView是谷歌V7包下新增的控件,用来替代ListView的使用,在RecyclerView标准化了ViewHolder类似于ListView中convertView用来做视图缓.
先来说说RecyclerView的有点就是,他可以通过设置LayoutManager来快速实现listview、gridview、瀑布流的效果,而且还可以设置横向和纵向显示,添加动画效果也非常简单(自带了ItemAnimation,可以设置加载和移除时的动画,方便做出各种动态浏览的效果),也是官方推荐使用的.
RecyclerView相关类:
- RecyclerView.Adapter 可以托管数据集合,为每一项Item创建视图并且绑定数据
- RecyclerView.ViewHolder 承载Item视图的子布局
- RecyclerView.LayoutManager 负责Item视图的布局的显示管理
- RecyclerView.ItemDecoration 给每一项Item视图添加子View,可以进行画分隔线之类的东西
- RecyclerView.ItemAnimator 负责处理数据添加或者删除时候的动画效果
二 使用
首先要用这个控件,你需要在gradle文件中添加包的引用
compile 'com.android.support:recyclerview-v7:21.0.3'
使用RecyclerView和Listview在xml文件中使用的方式一样,下面讲在Activity中怎么使用:
public class MainActivity extends ActionBarActivity {
@InjectView(R.id.recycler_view)
RecyclerView mRecyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.inject(this);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));//这里用线性显示 类似于listview
// mRecyclerView.setLayoutManager(new GridLayoutManager(this, 2));//这里用线性宫格显示 类似于grid view ,2是两列的意思
// mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(2, OrientationHelper.VERTICAL));//这里用线性宫格显示 类似于瀑布流
mRecyclerView.setAdapter(new NormalRecyclerViewAdapter(this));
//设置Item增加、移除动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
}
}
然后是适配器的代码:
public class NormalRecyclerViewAdapter extends RecyclerView.Adapter<NormalRecyclerViewAdapter.NormalTextViewHolder> {
private final LayoutInflater mLayoutInflater;
private final Context mContext;
private String[] mTitles;
public NormalRecyclerViewAdapter(Context context) {
mTitles = context.getResources().getStringArray(R.array.titles);
mContext = context;
mLayoutInflater = LayoutInflater.from(context);
}
//只需要覆写下面的三个方法就可以了
@Override
public NormalTextViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new NormalTextViewHolder(mLayoutInflater.inflate(R.layout.item_text, parent, false));
}
@Override
public void onBindViewHolder(NormalTextViewHolder holder, int position) {
holder.mTextView.setText(mTitles[position]);
}
@Override
public int getItemCount() {
return mTitles == null ? 0 : mTitles.length;
}
public static class NormalTextViewHolder extends RecyclerView.ViewHolder {
@InjectView(R.id.text_view)
TextView mTextView;
NormalTextViewHolder(View view) {
super(view);
ButterKnife.inject(this, view);
//这里是View的点击事件,也就是条目的点击事件
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("NormalTextViewHolder", "onClick--> position = " + getPosition());
}
});
}
}
}
前面介绍了基本的使用方法,实际开发中可能需要显示多类型条目,控制RecyclerView的显示,主要就是在Adapter中,如果要显示多类型条目,在ListView中的写法是这样的:
@Override
public int getItemViewType(int position) {
return super.getItemViewType(position);
}
@Override
public int getViewTypeCount() {
return super.getViewTypeCount();
}
其中getItemViewType方法是用来获取当前项Item(position参数)是哪种类型的布局,getViewTypeCount方法是用来获取当前listview总共有多少种类型的布局。
如果你用RecyclerView,你会发现getViewTypeCount这个方法没有了,只有一个getItemViewType方法,用法和listview没有任何区别,这里要注意的就是这个函数onCreateViewHolder(ViewGroup parent, int viewType)这里的第二个参数就是View的类型,可以根据这个类型判断去创建不同item的ViewHolder。
public class MultipleItemAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public static enum ITEM_TYPE { //定义一个类型的枚举
ITEM_TYPE_IMAGE,
ITEM_TYPE_TEXT
}
private final LayoutInflater mLayoutInflater;
private final Context mContext;
private String[] mTitles;
public MultipleItemAdapter(Context context) {
mTitles = context.getResources().getStringArray(R.array.titles);
mContext = context;
mLayoutInflater = LayoutInflater.from(context);
}
//这里判断不同的类型加载不同的布局
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ITEM_TYPE.ITEM_TYPE_IMAGE.ordinal()) {
return new ImageViewHolder(mLayoutInflater.inflate(R.layout.item_image, parent, false));
} else {
return new TextViewHolder(mLayoutInflater.inflate(R.layout.item_text, parent, false));
}
}
//这里去加载不同的数据
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof TextViewHolder) {
((TextViewHolder) holder).mTextView.setText(mTitles[position]);
} else if (holder instanceof ImageViewHolder) {
((ImageViewHolder) holder).mTextView.setText(mTitles[position]);
}
}
//用来获取当前项Item(position参数)是哪种类型的布局
@Override
public int getItemViewType(int position) {
return position % 2 == 0 ? ITEM_TYPE.ITEM_TYPE_IMAGE.ordinal() : ITEM_TYPE.ITEM_TYPE_TEXT.ordinal();
}
@Override
public int getItemCount() {
return mTitles == null ? 0 : mTitles.length;
}
//然后分别是不同的点击事件
public static class TextViewHolder extends RecyclerView.ViewHolder {
@InjectView(R.id.text_view)
TextView mTextView;
TextViewHolder(View view) {
super(view);
ButterKnife.inject(this, view);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("TextViewHolder", "onClick--> position = " + getPosition());
}
});
}
}
public static class ImageViewHolder extends RecyclerView.ViewHolder {
@InjectView(R.id.text_view)
TextView mTextView;
@InjectView(R.id.image_view)
ImageView mImageView;
ImageViewHolder(View view) {
super(view);
ButterKnife.inject(this, view);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("ImageViewHolder", "onClick--> position = " + getPosition());
}
});
}
}
}
这样多item类型就处理完成了.
使用2:分割线的显示
想要RecyclerView显示分割线的话,就需要自己去继承抽象类ItemDecoration,在里面画不同的分割线,下面是鸿洋博客里的一个使用系统属性画出来的分割线
package com.zhy.sample.demo_recyclerview;
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* limitations under the License.
*/
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.State;
import android.util.Log;
import android.view.View;
/**
* This class is from the v7 samples of the Android SDK. It's not by me!
* <p/>
* See the license above for details.
*/
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
private Drawable mDivider;
private int mOrientation;
public DividerItemDecoration(Context context, int orientation) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
setOrientation(orientation);
}
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}
@Override
public void onDraw(Canvas c, RecyclerView parent) {
Log.v("recyclerview - itemdecoration", "onDraw()");
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
public void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext());
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
public void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}
调用的方法就是mRecyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));
在RecyclerView设置Adapter后调用
使用3:删除添加数据时的动画显示,数据的更新
在RecyclerView中,更新数据,如果直接使用notifyDataSetChange()的话,没有动画效果,如果需要动画效果,需要使用notifyItemInserted(position)与notifyItemRemoved(position)
,下面是演示代码,在Adapter中添加下面的方法:
public void addData(int position) {
mDatas.add(position, "Insert One");
notifyItemInserted(position);
}
public void removeData(int position) {
mDatas.remove(position);
notifyItemRemoved(position);
}
当然了只提供了一种动画,那么我们肯定可以去自定义各种nice的动画效果。
分割线和动画效果,github上有很多,如果需要很炫酷的效果,可以取github上面查找
RecyclerViewItemAnimators
使用4:条目的监听
在ListView中可以直接设置ItemOnclick,但是在RecyclerView中,并没有提供这样的接口,这就需要我们自己去实现,参照Google Simple中的例子,是这样写的,在Viewholder中处理:
/**
* Provide views to RecyclerView with data from mDataSet.
*/
public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder> {
private static final String TAG = "CustomAdapter";
private String[] mDataSet;
/**
* Provide a reference to the type of views that you are using (custom ViewHolder)
*/
public static class ViewHolder extends RecyclerView.ViewHolder {
private final TextView textView;
public ViewHolder(View v) {
super(v);
// Define click listener for the ViewHolder's View.
//在这里设置点击事件
v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "Element " + getAdapterPosition() + " clicked.");
}
});
textView = (TextView) v.findViewById(R.id.textView);
}
public TextView getTextView() {
return textView;
}
}
/**
* Initialize the dataset of the Adapter.
*
* @param dataSet String[] containing the data to populate views to be used by RecyclerView.
*/
public CustomAdapter(String[] dataSet) {
mDataSet = dataSet;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
// Create a new view.
View v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.text_row_item, viewGroup, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, final int position) {
Log.d(TAG, "Element " + position + " set.");
viewHolder.getTextView().setText(mDataSet[position]);
}
@Override
public int getItemCount() {
return mDataSet.length;
}
}
还有一种方式是鸿洋博客里的那种,自己去定义一个接口,通过回调的方式去实现条目的点击效果:
在Adapter中的代码如下:
//...
public interface OnItemClickLitener
{
void onItemClick(View view, int position);
void onItemLongClick(View view , int position);
}
private OnItemClickLitener mOnItemClickLitener;
public void setOnItemClickLitener(OnItemClickLitener mOnItemClickLitener)
{
this.mOnItemClickLitener = mOnItemClickLitener;
}
@Override
public void onBindViewHolder(final MyViewHolder holder, final int position)
{
holder.tv.setText(mDatas.get(position));
// 如果设置了回调,则设置点击事件
if (mOnItemClickLitener != null)
{
holder.itemView.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
int pos = holder.getLayoutPosition();
mOnItemClickLitener.onItemClick(holder.itemView, pos);
}
});
holder.itemView.setOnLongClickListener(new OnLongClickListener()
{
@Override
public boolean onLongClick(View v)
{
int pos = holder.getLayoutPosition();
mOnItemClickLitener.onItemLongClick(holder.itemView, pos);
return false;
}
});
}
}
//...
在Activity中的调用:
mAdapter.setOnItemClickLitener(new OnItemClickLitener()
{
@Override
public void onItemClick(View view, int position)
{
Toast.makeText(HomeActivity.this, position + " click",
Toast.LENGTH_SHORT).show();
}
@Override
public void onItemLongClick(View view, int position)
{
Toast.makeText(HomeActivity.this, position + " long click",
Toast.LENGTH_SHORT).show();
mAdapter.removeData(position);
}
});
参考的博客地址有:
RecyclerView的使用详解
Android RecyclerView 使用完全解析 体验艺术般的控件
第三方的一些开源控件:
实现RecyclerView下拉刷新和上拉加载更多
酷炫的RecyclerView开源库整理