前言
recyclerView是Android 5.0 materials design 中的组件之一,是一个用来取代listView的SDK,它的灵活性与可替代性比listview更好,原理和listView相似,都是仅仅维护少量的view来加载展示大量的数据集。下面就开始对它进行介绍。介绍之前,先将实现效果展示出来:
使用recyclerView的好处:
recyclerView封装了viewHolder的回收复用机制,也就是说recyclerView标准化了viewHolder,编写Adapter面向的是viewHolder而不再是view了,复用的逻辑被封装了,写起来也就更方便了。recyclerView提供了一种插拔式的体验,高度的解耦,异常的灵活,针对一个Item的显示RecylerView专门抽取出了相应的类,来控制Item的显示,使其的扩展性非常强。它还可以实现多种效果,例如你想要横向或者纵向的控制滑动列表,可以通过LinearLayoutManager这个类去控制,类似于GridView的展示效果可以用GridLayoutManager这个类,瀑布流的效果可以通过staggeredGridLayoutManager实现,另外你想控制Item的分隔线,可以通过继承RecylerView的ItemDecoration这个类,控制Item增删的动画,可以通过ItemAnimator这个类进行控制,然后针对自己的业务需求去抒写代码。
recyclerView的简单使用:
-
添加依赖:
dependencies { ... compile 'com.android.support:recyclerview-v7:21.0.+' }
2.在xml布局文件中创建一个RecyclerView的布局和item的布局:
<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=".MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/my_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"/>
</RelativeLayout>
创建item布局,通过使用CardView进行包裹达到更好的界面效果展示:
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="10dp"
android:layout_margin="5dp"
android:foreground="?android:attr/selectableItemBackground"
app:cardElevation="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="5dp">
<ImageView
android:id="@+id/item_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher" />
<TextView
android:id="@+id/item_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:text="标题"
android:textSize="18sp" />
</LinearLayout>
</android.support.v7.widget.CardView>
-
编写代码逻辑,在MainActivity中找到recyclerView,设置他的显示样式,并为他设置Adapter.
mRecyclerView = (RecyclerView) findViewById(R.id.recycle_view); //创建默认的线性LinearLayoutManager LinearLayoutManager mLayoutManager = new LinearLayoutManager(this); //gridLayoutManager 多列表 GridLayoutManager gridLayoutManager = new GridLayoutManager(this,3); //StaggeredGridLayoutManager 瀑布流 StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(5,StaggeredGridLayoutManager.HORIZONTAL); mRecyclerView.setLayoutManager(mLayoutManager); //确定每个item的高度都是固定的,提高性能 mRecyclerView.setHasFixedSize(true); //设置适配器 mAdapter = new MyAdapter(this); mRecyclerView.setAdapter(mAdapter); //也可以设置分割线 recyclerView.addItemDecoration(new DividerGridItemDecoration(this )); //设置增加或删除条目的动画 recyclerView.setItemAnimator( new DefaultItemAnimator());
为recyclerView编写Adapter:
/**
* 创建adapter需要实现三个方法:
* 1. onCreateViewHolder():这个方法主要生成为每个Item inflater出一个View,但是该方法返回的是一个ViewHolder。该方法把View直接封装在ViewHolder中,然后我们面向的是ViewHolder这个实例,当然这个ViewHolder需要我们自己去编写,直接省去了当初的convertView.setTag(holder)和convertView.getTag()这些繁琐的步骤。
* 2. onBindViewHolder():这个方法主要用于适配渲染数据到View中。将界面与数据进行绑定,方法提供给你了一个viewHolder,而不是原来的convertView。
* 3. getItemCount():这个方法就类似于BaseAdapter的getCount方法了,即总共有多少个条目。
*/
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private View view;
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
view = View.inflate(parent.getContext(), R.layout.item_adapter, null);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.setData(position);
}
@Override
public int getItemCount() {
return 1000;
}
/**
* 设置viewHolder
*/
public class MyViewHolder extends RecyclerView.ViewHolder {
TextView tvName;
ImageView ivImage;
public MyViewHolder(View itemView) {
super(itemView);
//初始化控件
tvName = (TextView) view.findViewById(R.id.item_textview);
ivImage = (ImageView) view.findViewById(R.id.item_image);
}
}
}
- 这样一个通过RecyclerView来实现界面的数据加载就实现了。
RecyclerView的进阶
给RecyclerView的item添加点击事件:
1.由于RecyclerView没有提供直接添加item点击事件的方法,因此需要通过自定义接口实现item的点击事件。
/**
* 自定义接口,给item设置点击事件
*/
//声明接口的变量
private onRecycleViewItemClickListener mOnItemClickListener = null;
public interface onRecycleViewItemClickListener {
void onItemClick(View view, int position);
}
public void setOnItemClickListener(onRecycleViewItemClickListener onItemClickListener) {
this.mOnItemClickListener = onItemClickListener;
}
2.在MyAdapter类中,通过实现view的onClickListener方法,重写它的onClick()方法;
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> implements View.OnClickListener {
....
@Override
public void onClick(View view) {
//在该处先判空。
if (mOnItemClickListener != null) {
//通过getTag的方式获取到position
mOnItemClickListener.onItemClick(view, (Integer) view.getTag());
}
}
3.在Adapter的实现方法:onCreateViewHolder()中给view添加点击事件,onBindViewHolder()通过setTag的方式设置item的position。
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
view = View.inflate(parent.getContext(), R.layout.item_adapter, null);
//给新创建的view注册点击事件
view.setOnClickListener(this);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.setData(position);
//将position保存在viewItem的tag中,以便在点击的时候获取。
holder.itemView.setTag(position);
}
4.在MainActivity中,通过给adapter设置暴露出来的setOnItemClickListener()方法,来响应点击事件。
//给item设置点击事件
mAdapter.setOnItemClickListener(new MyAdapter.onRecycleViewItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(MainActivity.this,"第"+position+"个item被点击了",Toast.LENGTH_SHORT).show();
}
});
给recyclerView的item添加分割线:
recyclerView有提供添加分割线的方法:通过addItemDecoration()实现。
//添加分割线
mRecyclerView.addItemDecoration(new RecycleViewItemDecoration());
2.自定义类RecycleViewItemDecoration继承RecyclerView.ItemDecoration,并实现它的两个方法getItemOffsets()和onDraw()方法。
/**
* 设置item的分割线
*/
private class RecycleViewItemDecoration extends RecyclerView.ItemDecoration {
private Paint mPaint;
public RecycleViewItemDecoration() {
//构建画笔
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.GRAY);
}
/**
* 实现两个方法
* 确定分割线的位置
* @param outRect
* @param view
* @param parent
* @param state
*/
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
//先确定位置,在每个item的底部留出5px的位置
int position = parent.getChildAdapterPosition(view);
//判断当前item不是第0个位置,就在item的顶部绘制分割线
if(position != 0) {
outRect.top = 5;
}
}
/**
* 绘制分割线,用Canvas在每一个item的头部绘制
* @param c
* @param parent
* @param state
*/
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
int childCount = parent.getChildCount();
//指定绘画的矩形区域
Rect rect = new Rect();
rect.left = parent.getPaddingLeft();
rect.right = parent.getWidth() - parent.getPaddingRight();
for (int i = 1; i < childCount; i++) {
//分割线的底部是itemView的顶部
rect.bottom = parent.getChildAt(i).getTop();
rect.top = rect.bottom - 5;
c.drawRect(rect,mPaint);
}
}
}
给RecyclerView添加头部和底部:
listview添加头部和底部有具体的方法实现,通过addHeaderView()和addFooterView()两个方法就能实现。而recyclerView没有提供具体的方法实现,需要自定义来实现。通过对listview添加头部的方法addHeaderView()的源码进行分析,发现它是在对Adapter进行判空处理之后,判断当前的listview是否已经添加Adapter,然后在原来的adapter的基础上进行了一层包装,其实listview本身也是不支持添加头部和底部的,只是系统为我们设定好了。最主要是需要 清楚HeaderViewListAdapter是如何实现的。
public void addHeaderView(View v, Object data, boolean isSelectable) {
//第一步 数据的初始化和赋值 并添加到头部视图list中
final FixedViewInfo info = new FixedViewInfo();
info.view = v;
info.data = data;
info.isSelectable = isSelectable;
//添加头部集合
mHeaderViewInfos.add(info);
mAreAllItemsSelectable &= isSelectable;
// Wrap the adapter if it wasn't already wrapped.
if (mAdapter != null) {
//第二步 关键点
//给mAdapter赋值成HeaderViewListAdapter对象 并把head、foot视图列表和原来的adapter传进去
//所以这里就是关键 从这里进入看HeaderViewListAdapter类的源码
if (!(mAdapter instanceof HeaderViewListAdapter)) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
}
//第三步 被观察者发出修改通知
// In the case of re-adding a header view, or adding one later on,
// we need to notify the observer.
if (mDataSetObserver != null) {
mDataSetObserver.onChanged();
}
}
}
接下来,就自己实现一下,实现的基本原理是通过getItemViewType()方法返回不同的类型来添加头部和底部。
贴上实现该功能的整段代码,对上面的代码块有部分改动:
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements View.OnClickListener {
private View view;
private Context mContext;
private LayoutInflater mLayoutInflater;
//item类型
public static final int ITEM_TYPE_HEADER = 0;
public static final int ITEM_TYPE_CONTENT = 1;
public static final int ITEM_TYPE_BOTTOM = 2;
//头部View个数
private int mHeaderCount = 1;
//底部View个数
private int mBottomCount = 1;
int icons[] = {R.drawable.g1, R.drawable.g2, R.drawable.g3, R.drawable.g4, R.drawable.g5, R.drawable.g6, R.drawable.g7, R.drawable.g9,
R.drawable.g10, R.drawable.g11, R.drawable.g12, R.drawable.g13, R.drawable.g14, R.drawable.g15, R.drawable.g16, R.drawable.g17, R.drawable.g18, R.drawable.g19,
R.drawable.g20, R.drawable.g21, R.drawable.g22, R.drawable.g23, R.drawable.g24, R.drawable.g25, R.drawable.g26, R.drawable.g27, R.drawable.g28, R.drawable.g29,};
String names[] = {"浏览器", "输入法", "健康", "效率", "教育", "理财",
"阅读", "个性化", "购物", "资讯", "生活", "工具", "出行", "通讯", "拍照", "社交",
"影音", "安全", "休闲", "棋牌", "益智", "射击", "体育", "儿童", "网游", "角色", "策略",
"经营", "竞速"};
public MyAdapter(Context context) {
this.mContext = context;
mLayoutInflater = LayoutInflater.from(context);
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ITEM_TYPE_HEADER) {
return new HeaderViewHolder(mLayoutInflater.inflate(R.layout.rv_headerview, parent, false));
} else if (viewType == mHeaderCount) {
view = mLayoutInflater.inflate(R.layout.item_adapter, parent, false);
view.setOnClickListener(this);
return new MyViewHolder(view);
} else if (viewType == ITEM_TYPE_BOTTOM) {
return new BottomViewHolder(mLayoutInflater.inflate(R.layout.rv_footerview, parent, false));
}
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof HeaderViewHolder) {
} else if (holder instanceof MyViewHolder) {
((MyViewHolder) holder).setData(position);
//将position保存在viewItem的tag中,以便在点击的时候获取。
holder.itemView.setTag(position);
} else if (holder instanceof BottomViewHolder) {
}
}
@Override
public int getItemCount() {
return getContentItemCount() + mHeaderCount + mBottomCount;
}
//填充内容的长度
public int getContentItemCount() {
return names.length;
}
//判断当前item类型
@Override
public int getItemViewType(int position) {
int dataItemCount = getContentItemCount();
if (mHeaderCount != 0 && position < mHeaderCount) {
//头部View
return ITEM_TYPE_HEADER;
} else if (mBottomCount != 0 && position >= (mHeaderCount + dataItemCount)) {
//底部View
return ITEM_TYPE_BOTTOM;
} else {
//内容View
return ITEM_TYPE_CONTENT;
}
}
/**
* 这两个方法是用来判断当前item是头部还是底部,在使用gridLayoutManager的时候调用
* @param position
* @return
*/
public boolean isHeaderView(int position) {
return mHeaderCount != 0 && position < mHeaderCount;
}
public boolean isBottomView(int position) {
return mBottomCount != 0 && position >= (mHeaderCount + getContentItemCount());
}
/**
* 自定义接口,给item设置点击事件
*/
//声明接口的变量
private onRecycleViewItemClickListener mOnItemClickListener = null;
public interface onRecycleViewItemClickListener {
void onItemClick(View view, int position);
}
public void setOnItemClickListener(onRecycleViewItemClickListener onItemClickListener) {
this.mOnItemClickListener = onItemClickListener;
}
@Override
public void onClick(View view) {
if (mOnItemClickListener != null) {
//通过getTag的方式获取到position
mOnItemClickListener.onItemClick(view, (Integer) view.getTag());
}
}
/**
* 设置viewHolder
*/
//中间内容的viewHolder
public class MyViewHolder extends RecyclerView.ViewHolder {
TextView tvName;
ImageView ivImage;
public MyViewHolder(View itemView) {
super(itemView);
//初始化控件
tvName = (TextView) view.findViewById(R.id.item_textview);
ivImage = (ImageView) view.findViewById(R.id.item_image);
}
public void setData(int position) {
//给控件赋值
tvName.setText(names[position % names.length]);
ivImage.setImageDrawable(mContext.getResources().getDrawable(icons[position % icons.length]));
}
}
//头部的ViewHolder
public static class HeaderViewHolder extends RecyclerView.ViewHolder {
public HeaderViewHolder(View itemView) {
super(itemView);
}
}
//底部的ViewHolder
public static class BottomViewHolder extends RecyclerView.ViewHolder {
public BottomViewHolder(View itemView) {
super(itemView);
}
}
}
在activity当中如果RecyclerView使用Grid类型列表在设置Adapter后需要调用这个方法mLayoutManager.setSpanSizeLookup(),根据当前Item类型来判断占据的横向格数。
/**
* 如果RecyclerView使用Grid类型列表在设置Adapter后需要调用这个方法,
* 根据当前Item类型来判断占据的横向格数
*/
mLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return (mAdapter.isHeaderView(position) ||
mAdapter.isBottomView(position)) ? mLayoutManager.getSpanCount() : 1;
}
});
RecyclerView实现列表的上拉加载和下拉刷新:
要实现列表的下拉刷新可以使用Android提供的swipeRefreshLayout来实现下拉刷新或者上拉加载,也可以自定义上拉加载和下拉刷新,
使用swipeRefreshLayout来进行控件的下拉刷新。需要在布局文件中对RecyclerView进行包裹。
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/srf_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycle_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical">
</android.support.v7.widget.RecyclerView>
</android.support.v4.widget.SwipeRefreshLayout>
接下来就是activity中的代码实现,需要先找到控件,然后刷新的监听方法,该处使用了handler来模拟网络请求操作。
//利用swipeRefreshLayout设置下拉刷新
private void setPullToRefresh() {
//设置圈圈的颜色
mRefreshLayout.setColorSchemeColors(Color.RED,Color.BLUE,Color.BLACK);
//设置圈圈的颜色,里面放的是颜色的资源值
// refreshLayout.setColorSchemeColors(R.color.cardview_dark_background,R.color.cardview_light_background);
mRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
Toast.makeText(MainActivity.this,"正在拼命加载中...",Toast.LENGTH_SHORT).show();
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
//停止刷新
mRefreshLayout.setRefreshing(false);
}
},3000);
}
});
}
SwipeRefreshLayout里面需要注意的Api:
1)setOnRefreshListener(OnRefreshListener listener) 设置下拉监听,当用户下拉的时候会去执行回调
2)setColorSchemeColors(int... colors) 设置 进度条的颜色变化,最多可以设置4种颜色
3)setProgressViewOffset(boolean scale, int start, int end) 调整进度条距离屏幕顶部的距离
4)setRefreshing(boolean refreshing) 设置SwipeRefreshLayout当前是否处于刷新状态,一般是在请求数据的时候设置为true,在数据被加载到View中后,设置为false。
总结:
到此关于RecyclerView的一些常见使用方式就介绍完了,总的来说RecyclerView的使用比起listview还是要便捷的多。