RecyclerView 在2014年就已经出来了,15年的时候有了解一下,但是项目中一直没用上,最近看到,发现RecyclerView 出现了很多拓展,它的出现就是为了代替ListView、GridView。所以介绍一下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
.support.v7.widget.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
<android.support.v7.widget.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">
<TextView
android:id="@+id/text_view"
android:padding="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
android.support.v7.widget.CardView>
5、然后是适配器代码
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.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("NormalTextViewHolder", "onClick--> position = " + getPosition());
}
});
}
}
}
至此一个简单的RecyclerView就这样很乖巧的趴在屏幕上,有图有真相:
GridLayoutManager样式:
下面来围绕这个示例,对RecyclerView的其他小伙伴进行介绍:
布局管理器,通过设置不同的布局管理器,来控制这些Item的排列方式。
RecyclerView提供的布局管理器:
使用这个函数来设置mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
给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);
}
}
}
看下效果图:
如果你用RecyclerView,你会发现CursorAdapter这个类没有了,既然没有了,那我们就自己仿照着ListView的CursorAdapter类来实现,具体的代码没什么大的出入,无非就是注册两个观察者去监听数据库数据的变化。
如果对ListView的CursorAdapter使用不了解,可参考下面两篇文章
Android之CursorAdapter用法
Android中CursorAdapter的使用详解
在recycleView中实现CursorAdapter,有两个地方需要注意一下,一个就是hasStableIds() 这个方法RecyclerView.Adapter中不能复写父类的方法,需要在初始化的时候调用setHasStableIds(true); 来完成相同功能,第二个就是notifyDataSetInvalidated() 这个方法没有,统一修改成notifyDataSetChanged() 方法即可。
完整代码见BaseAbstractRecycleCursorAdapter,和ListView的CursorAdapter用法一致,见代码:ItemsFragment.java
https://github.com/CymChad/BaseRecyclerViewAdapterHelper
使用方法:
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,你也可以对其进行自定义
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
-- Customize your theme here. -->
<item name="android:listDivider">@drawable/shape_divider
style>
resources>
下面给出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也是一个抽象类,好在系统为我们提供了一种默认的实现类
借助默认的实现,当Item添加和移除的时候,添加动画效果很简单:
// 设置item动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
系统为我们提供了一个默认的实现,我们为我们的瀑布流添加以上一行代码,效果为:
推荐2个RecyclerView的动画库 :
https://github.com/wasabeef/recyclerview-animators
https://github.com/gabrielemariotti/RecyclerViewItemAnimators
使用方法:在item的xml布局文件中,直接使用CardView即可。
CardView 扩展 FrameLayout 类并让您能够显示卡片内的信息,这些信息在整个平台中拥有一致的呈现方式。CardView 小部件可拥有阴影和圆角。
如果要使用阴影创建卡片,请使用 card_view:cardElevation 属性。CardView 在 Android 5.0(API 级别 21)及更高版本中使用真实高度与动态阴影,而在早期的 Android 版本中则返回编程阴影实现。
使用这些属性自定义 CardView 小部件的外观:
使用CardView很简单,只需要在xml,创建CardView布局,见上面示例的第4点,然后在adapter中使用这个xml即可。
https://github.com/JantXue/AndroidRecyclerViewDemo
参考:
上面图片都是来自,参考博客中,给出的代码实现和图片有些许差异。
还在用ListView?RecyclerView都已经出来一年多了!
Android 自定义RecyclerView 实现真正的Gallery效果
Android RecyclerView 使用完全解析 体验艺术般的控件
创建列表与卡片
RecyclerView使用详解(一)
RecyclerView使用详解(二)
RecyclerView使用详解(三)
关注我的公众号,轻松了解和学习更多技术