RecyclerView 教程,全面了解各种使用方法

2025-07-03 00:58:10
刚才有个朋友问我,博主发生什么事了,给我发了几张截图,我一看,哦,原来是有个年轻人看了文章,说是,博主,我能白嫖你的文章,我说...

刚才有个朋友问我,博主发生什么事了,给我发了几张截图,我一看,哦,原来是有个年轻人看了文章,说是,博主,我能白嫖你的文章,我说年轻人,点个赞再走,他说不点,我说点一个,他说不点,我说点一个,他说不点,我说我这文章对你有用,他不服气,说要先看看。我说可以,很快啊,看完后,上来就是一个复制,一个粘贴,一个网页关闭,我大意了啊。按传统博客的三连为止,他已经输了啊。 后来他说他是乱点的,这可不是乱点的啊,训练有素。我劝年轻人好好点赞,耗子尾汁,谢谢朋友们

前言

RecyclerView 在2014年就已经出来了,15年的时候有了解一下,但是项目中一直没用上,最近看到,发现RecyclerView 出现了很多拓展,它的出现就是为了代替ListView、GridView。所以介绍一下RecyclerView该如何使用,及梳理一下这些拓展应该怎么用,是个什么效果。

RecyclerView 源码深入分析,读懂这两篇文章,以后遇到问题,再也不用百度了

整体流程、measure、layout 详解 ——深入分析RecyclerView源码(一) 缓存 ——深入分析RecyclerView源码(二)

RecyclerView

RecyclerView 比 ListView 更高级且更具灵活性。 它是一个用于显示庞大数据集的容器,可通过保持有限数量的视图进行非常有效的滚动操作。 如果您有数据集合,其中的元素将因用户操作或网络事件而在运行时发生改变,请使用 RecyclerView 。

从它类名上看,RecyclerView代表的意义是,我只管Recycler View,也就是说RecyclerView只管回收与复用View,其他的你可以自己去设置。可以看出其高度的解耦,给予你充分的定制自由(所以你才可以轻松的通过这个控件实现ListView,GirdView,瀑布流等效果)。

在ListView中 改变列表某一个item数据,然后刷新列表,会回到最顶部,而RecyclerView可以保持原来滑动的位置不变。

要实现一个RecyclerView,会接触到它的几个小伙伴,其中1、2是必须的。剩下的3、4、5三项,可以让RecyclerView更好看、效果更好。

想要控制其item们的排列方式,请使用布局管理器LayoutManager

如果要创建一个适配器,请使用RecyclerView.Adapter

想要控制Item间的间隔,请使用RecyclerView.ItemDecoration

想要控制Item增删的动画,请使用RecyclerView.ItemAnimator

CardView 扩展 FrameLayout 类并让您能够显示卡片内的信息,这些信息在整个平台中拥有一致的呈现方式。CardView 小部件可拥有阴影和圆角。

如果要使用 RecyclerView 小部件,必须指定一个Adapter和一个LayoutManager。

在下面会详解这些类的使用

非常简单的一个示例

这里先给出一个简单的例子,感受一下怎么样使用RecyclerView

1、首先要用这个控件,你需要在gradle文件中添加包的引用

compile 'com.android.support:cardview-v7:21.0.3'

compile 'com.android.support:recyclerview-v7:21.0.3'

2、 然后是在XML文件用使用RecyclerView

xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:id="@+id/recycler_view"

android:layout_centerVertical="true"

android:layout_centerHorizontal="true"/>

3、在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

// mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(2, OrientationHelper.VERTICAL));//这里用线性宫格显示 类似于瀑布流

mRecyclerView.setAdapter(new NormalRecyclerViewAdapter(this));

}

@Override

public boolean onCreateOptionsMenu(Menu menu) {

getMenuInflater().inflate(R.menu.menu_main, menu);

return true;

}

@Override

public boolean onOptionsItemSelected(MenuItem item) {

int id = item.getItemId();

if (id == R.id.action_settings) {

return true;

}

return super.onOptionsItemSelected(item);

}

}

4、Adapter 的Item的xml代码,使用CardView

xmlns:card_view="http://schemas.android.com/apk/res-auto"

xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_margin="8dp"

android:id="@+id/cv_item"

card_view:cardCornerRadius="4dp">

android:id="@+id/text_view"

android:padding="20dp"

android:layout_width="match_parent"

android:layout_height="wrap_content"/>

5、然后是适配器代码

public class NormalRecyclerViewAdapter extends RecyclerView.Adapter {

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.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

Log.d("NormalTextViewHolder", "onClick--> position = " + getPosition());

}

});

}

}

}

至此一个简单的RecyclerView就这样很乖巧的趴在屏幕上,有图有真相:

LinearLayoutManager样式:

GridLayoutManager样式:

下面来围绕这个示例,对RecyclerView的其他小伙伴进行介绍:

二、LayoutManager

布局管理器,通过设置不同的布局管理器,来控制这些Item的排列方式。

RecyclerView提供的布局管理器:

LinearLayoutManager 以垂直或水平滚动列表方式显示项目。GridLayoutManager 在网格中显示项目。StaggeredGridLayoutManager 在分散对齐网格中显示项目。

使用这个函数来设置mRecyclerView.setLayoutManager(new LinearLayoutManager(this));

三、Adapter

给recycleView提供数据的类,使用方法如下

mRecyclerView.setAdapter(new NormalRecyclerViewAdapter(this));

可以看到数据适配器与BaseAdapter比较发生了相当大的变化,主要有3个方法:

getItemCount() 获取总的条目数 onCreateViewHolder() 创建ViewHolder onBindViewHolder() 将数据绑定至ViewHolder

可见,RecyclerView对ViewHolder也进行了一定的封装,但是如果你仔细观察,你会发出一个疑问,ListView里面有个getView,它返回View为Item的布局,那么RecyclerView这个Item的样子在哪控制?

其实是这样的,我们创建的ViewHolder必须继承RecyclerView.ViewHolder,这个RecyclerView.ViewHolder构造时必须传入一个View,这个View相当于我们ListView getView中的convertView (即:inflate的item布局需要传入)。

还有一点,ListView中convertView是复用的,在RecyclerView中,是把ViewHolder类作为缓存的单位了,然后convertView作为ViewHolder的成员变量保持在ViewHolder中,也就是说,假设屏幕显示10个条目,则会创建10个ViewHolder缓存起来,每次复用的是ViewHolder,所以他把getView这个方法变为了onCreateViewHolder。

可以对Adapter进行一些个性化操作,实现不同的功能,下面给出两个示例:

瀑布式布局

ok,接下来准备看大招,如果让你去实现个瀑布流,最起码不是那么随意就可以实现的吧?但是,如果使用RecyclerView,分分钟的事。 那么如何实现?还是使用StaggeredGridLayoutManage,只需要在Adapter的onBindViewHolder()为我们的item设置个随机的高度,下面仅给出Adapter的代码(下面会给出全部工程代码):

public class WaterpallStaggeredAdapter extends

RecyclerView.Adapter {

private List mDatas;

private LayoutInflater mInflater;

private List mHeights;

public interface OnItemClickListener {

void onItemClick(View view, int position);

void onItemLongClick(View view, int position);

}

private OnItemClickListener mOnItemClickListener;

public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) {

this.mOnItemClickListener = mOnItemClickListener;

}

public WaterpallStaggeredAdapter(Context context, List datas) {

mInflater = LayoutInflater.from(context);

mDatas = datas;

mHeights = new ArrayList();

for (int i = 0; i < mDatas.size(); i++) {

mHeights.add((int) (100 + Math.random() * 300));

}

}

@Override

public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

MyViewHolder holder = new MyViewHolder(mInflater.inflate(

R.layout.item_staggered_home, parent, false));

return holder;

}

@Override

public void onBindViewHolder(final MyViewHolder holder, final int position) {

LayoutParams lp = holder.tv.getLayoutParams();

lp.height = mHeights.get(position);

holder.tv.setLayoutParams(lp);

holder.tv.setText(mDatas.get(position));

// 如果设置了回调,则设置点击事件

if (mOnItemClickListener != null) {

holder.itemView.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

int pos = holder.getLayoutPosition();

mOnItemClickListener.onItemClick(holder.itemView, pos);

}

});

holder.itemView.setOnLongClickListener(new OnLongClickListener() {

@Override

public boolean onLongClick(View v) {

int pos = holder.getLayoutPosition();

mOnItemClickListener.onItemLongClick(holder.itemView, pos);

removeData(pos);

return false;

}

});

}

}

@Override

public int getItemCount() {

return mDatas.size();

}

public void addData(int position) {

mDatas.add(position, "Insert One");

mHeights.add((int) (100 + Math.random() * 300));

notifyItemInserted(position);

}

public void removeData(int position) {

mDatas.remove(position);

notifyItemRemoved(position);

}

class MyViewHolder extends ViewHolder {

TextView tv;

public MyViewHolder(View view) {

super(view);

tv = (TextView) view.findViewById(R.id.id_num);

}

}

}

看下效果图:

CursorAdapter(使用LoaderManager和CursorLoader实现)

如果你用RecyclerView,你会发现CursorAdapter这个类没有了,既然没有了,那我们就自己仿照着ListView的CursorAdapter类来实现,具体的代码没什么大的出入,无非就是注册两个观察者去监听数据库数据的变化。

如果对ListView的CursorAdapter使用不了解,可参考下面两篇文章 Android之CursorAdapter用法 Android中CursorAdapter的使用详解

在recycleView中实现CursorAdapter,有两个地方需要注意一下,一个就是hasStableIds() 这个方法RecyclerView.Adapter中不能复写父类的方法,需要在初始化的时候调用setHasStableIds(true); 来完成相同功能,第二个就是notifyDataSetInvalidated() 这个方法没有,统一修改成notifyDataSetChanged() 方法即可。

完整代码见BaseAbstractRecycleCursorAdapter,和ListView的CursorAdapter用法一致,见代码:ItemsFragment.java

关于RecyclerView的Adapter 各种解决方案

https://github.com/CymChad/BaseRecyclerViewAdapterHelper

四、ItemDecoration 添加分割线

使用方法:

mRecyclerView.addItemDecoration(new DividerItemDecoration(this,

DividerItemDecoration.VERTICAL_LIST));

ItemDecoration类很好的实现了RecyclerView添加分割线(当使用LayoutManager为LinearLayoutManager时)。 该实现类可以看到通过读取系统主题中的 Android.R.attr.listDivider作为Item间的分割线,并且支持横向和纵向。

该分割线是系统默认的,你可以在theme.xml中找到该属性的使用情况。

在styles.xml找使用的android:listDivider的xml——shape_divider,你也可以对其进行自定义

下面给出DividerItemDecoration的代码

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, RecyclerView.State state) {

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

}

}

}

来看看使用分割线和没有使用分割线的区别:

五、ItemAnimator

删除和添加的动画效果

ItemAnimator也是一个抽象类,好在系统为我们提供了一种默认的实现类

借助默认的实现,当Item添加和移除的时候,添加动画效果很简单:

// 设置item动画

mRecyclerView.setItemAnimator(new DefaultItemAnimator());

系统为我们提供了一个默认的实现,我们为我们的瀑布流添加以上一行代码,效果为:

如果是GridLayoutManager呢?动画效果为:

推荐2个RecyclerView的动画库 : https://github.com/wasabeef/recyclerview-animators https://github.com/gabrielemariotti/RecyclerViewItemAnimators

六、CardView

使用方法:在item的xml布局文件中,直接使用CardView即可。

CardView 扩展 FrameLayout 类并让您能够显示卡片内的信息,这些信息在整个平台中拥有一致的呈现方式。CardView 小部件可拥有阴影和圆角。

如果要使用阴影创建卡片,请使用 card_view:cardElevation 属性。CardView 在 Android 5.0(API 级别 21)及更高版本中使用真实高度与动态阴影,而在早期的 Android 版本中则返回编程阴影实现。

使用这些属性自定义 CardView 小部件的外观:

如果要在您的布局中设置圆角半径,请使用 card_view:cardCornerRadius 属性。如果要在您的代码中设置圆角半径,请使用 CardView.setRadius 方法。如果要设置卡片的背景颜色,请使用 card_view:cardBackgroundColor 属性。

CardView示例图:

使用CardView很简单,只需要在xml,创建CardView布局,见上面示例的第4点,然后在adapter中使用这个xml即可。

源代码:

https://github.com/JantXue/AndroidRecyclerViewDemo

参考: 上面图片都是来自,参考博客中,给出的代码实现和图片有些许差异。

还在用ListView?RecyclerView都已经出来一年多了! Android 自定义RecyclerView 实现真正的Gallery效果 Android RecyclerView 使用完全解析 体验艺术般的控件 创建列表与卡片 RecyclerView使用详解(一) RecyclerView使用详解(二) RecyclerView使用详解(三)

关注我的公众号,轻松了解和学习更多技术