RecyclerView是android5.0以后出现的一个Material Design风格的控件,可以用来实现Listview、GridView、瀑布流等效果;
RecyclerView的基本使用
1、引入依赖库
compile 'com.android.support:recyclerview-v7:25.3.1'
2、xml文件中使用
.support.v7.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
3、获取控件并设置布局管理器和适配器
recyclerView = (RecyclerView) findViewById(R.id.rv);
//设置布局管理器
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
这里需要注意在setAdapter之前必须要先setLayoutManager,否则会没有效果;系统提供了下面几种布局管理器:
//线性管理器,支持横向、纵向。
recyclerView.setLayoutManager(new LinearLayoutManager(this));
//网格布局管理器
recyclerView.setLayoutManager(new GridLayoutManager(this, 4));
//瀑布就式布局管理器
recyclerView.setLayoutManager(new StaggeredGridLayoutManager());
3.1、setAdapter设置适配器
class ViewAdapter extends RecyclerView.Adapter<ViewAdapter.Holder>{
@Override
public ViewAdapter.Holder onCreateViewHolder(ViewGroup parent, int viewType) {
//加载条目布局
View view = LayoutInflater.from(BaseUserActivity.this).inflate(R.layout.recycler_item, parent, false);
return new Holder(view);
}
@Override
public void onBindViewHolder(final ViewAdapter.Holder holder, final int position) {
//设定数据
holder.tv.setText(datas.get(position));
}
@Override
public int getItemCount() {
//返回整个RecyclerView条目数
return datas.size();
}
class Holder extends RecyclerView.ViewHolder{
TextView tv;
public Holder(View itemView) {
super(itemView);
tv = (TextView) itemView.findViewById(R.id.tv);
}
}
}
会发现和ListView的适配器不一样,RecyclerView的适配器必须有一个ViewHolder,简单的线性列表效果就实现了,但是会发现并没有分割线,还有就是每个条目没有点击和长按事件;是的RecyclerView只负责复用和释放,其他的效果需要自己去实现,可以看出其高度的解耦;
4、添加分割线
//添加分割线
recyclerView.addItemDecoration();
需要传入一个ItemDecoration对象,而ItemDecoration是一个abstract类,实际上传入的是子类对象,就写一个类去extend ItemDecoration,
public class DividerGridItemDecoration extends RecyclerView.ItemDecoration{
private Drawable mDivider;
private int[] attrs = new int[]{
android.R.attr.listDivider
};
public DividerGridItemDecoration(Context context, int drawableResourceId) {
// TypedArray typedArray = context.obtainStyledAttributes(attrs);
// mDivider = typedArray.getDrawable(0);
// typedArray.recycle();
//解析Drawable
mDivider = ContextCompat.getDrawable(context, drawableResourceId);
}
}
ItemDecoration里面有下面这些方法,不过有些是已经过时的,extends ItemDecoration的时候需要去复写onDraw(),getItemOffsets();方法;
public static abstract class ItemDecoration {
public void onDraw(Canvas c, RecyclerView parent, State state) {
onDraw(c, parent);
}
@Deprecated
public void onDraw(Canvas c, RecyclerView parent) {
}
public void onDrawOver(Canvas c, RecyclerView parent, State state) {
onDrawOver(c, parent);
}
@Deprecated
public void onDrawOver(Canvas c, RecyclerView parent) {
}
@Deprecated
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
outRect.set(0, 0, 0, 0);
}
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
parent);
}
}
4.1、复写getItemOffsets()得到偏移量
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
// 四个方向的偏移值
int right = mDivider.getIntrinsicWidth();
int bottom = mDivider.getIntrinsicHeight();
int position = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
if (isLastColumn(position, parent)) {// 是否是最后一列
right = 0;
}
if (isLastRow(position, parent)) {// 是否是最后一行
bottom = 0;
}
outRect.set(0, 0, right, bottom);
}
4.2、onDraw方法进行绘制
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
//绘制垂直间隔线(垂直的矩形)
drawVertical(c, parent);
//绘制水平间隔线
drawHorizontal(c, parent);
}
4.2.1、绘制垂直间隔线(垂直的矩形)
private void drawVertical(Canvas c, RecyclerView parent) {
//绘制垂直间隔线(垂直的矩形)
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int left = child.getRight() + params.rightMargin;
int right = left + mDivider.getIntrinsicWidth();
int top = child.getTop() - params.topMargin;
int bottom = child.getBottom() + params.bottomMargin;
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
4.2.2、绘制水平间隔线
private void drawHorizontal(Canvas c, RecyclerView parent) {
// 绘制水平间隔线
//获取RecyclerView的条目数
int childCount = parent.getChildCount();
//遍历条目数进行绘制
for (int i = 0; i < childCount; i++) {
//得到每一个条目view
View child = parent.getChildAt(i);
//利用RecyclerView.LayoutParams获取Margin值
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
//计算 left right top bottom值
int left = child.getLeft() - params.leftMargin;
//mDivider.getIntrinsicWidth()获取Drawable的宽度
int right = child.getRight() + params.rightMargin + mDivider.getIntrinsicWidth();
int top = child.getBottom() + params.bottomMargin;
//mDivider.getIntrinsicHeight()获取Drawable的高度
int bottom = top + mDivider.getIntrinsicHeight();
//设定 left right top bottom值
mDivider.setBounds(left, top, right, bottom);
//对Drawable进行绘制
mDivider.draw(c);
}
}
5、添加条目点击和长按事件
条目点击和长按事件这里是采用接口回调的方式实现的。
/**
* Created by Administrator on 2017/5/28.
* 点击事件接口
*/
public interface OnItemClickListener {
void onItemClick(Object obj, int position);
}
/**
* Created by Administrator on 2017/5/28.
* 长按点击事件接口
*/
public interface OnLongClickListener {
boolean onItemLongClick(Object obj, int position);
}
然后在adapter里面设置条目点击和长按事件的监听并提供set相应方法进行回调监听;
//条目点击事件
if(mItemClickListener!=null){
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mItemClickListener.onItemClick(datas.get(position),position);
}
});
}
//长按点击事件
if(mItemLoogClickListener!=null){
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return mItemLoogClickListener.onItemLongClick(datas.get(position),position);
}
});
}
提供外界调用的方法;
//使用接口回调点击事件
private OnItemClickListener mItemClickListener;
public void setOnItemClickListener(OnItemClickListener itemClickListener){
this.mItemClickListener=itemClickListener;
}
//使用接口回调点击事件
private OnLongClickListener mItemLoogClickListener;
public void setOnItemLongClickListener(OnLongClickListener itemLongClickListener){
this.mItemLoogClickListener=itemLongClickListener;
}
这样子条目分割线、条目点击和长按事件就添加上去了;
效果如下:
RecyclerView万能adapter的封装
RecyclerView使用一次就需求去写一个adapter,去实现getItemCount、onBindViewHolder、onCreateViewHolder方法,这样子重复写的代码还是挺过的,所以就对RecyclerView的adapter进行封装,封装成一个万能的adapter,主要涉及到ViewHolder和adapter封装;
ViewHolder的封装
/**
* Created by Administrator on 2017/5/28.
* RecyclerView通用ViewHolder
*/
public class ViewHolder extends RecyclerView.ViewHolder {
//用于缓存已找的界面
private SparseArray mView;
public ViewHolder(View itemView) {
super(itemView);
mView=new SparseArray<>();
}
public T getView(int viewId){
//对已有的view做缓存
View view=mView.get(viewId);
//使用缓存的方式减少findViewById的次数
if(view==null){
view=itemView.findViewById(viewId);
mView.put(viewId,view);
}
return (T) view;
}
//通用的功能进行封装 设置文本 设置条目点击事件 设置图片
public ViewHolder setText(int viewId ,CharSequence text){
TextView view = getView(viewId);
view.setText(""+text);
//希望可以链式调用
return this;
}
/**
*设置本地图片
* @param viewId
* @param resId
* @return
*/
public ViewHolder setImageResource(int viewId,int resId){
ImageView iv=getView(viewId);
iv.setImageResource(resId);
return this;
}
/**
* 加载图片资源路径
* @param viewId
* @param imageLoader
* @return
*/
public ViewHolder setImagePath(int viewId,HolderImageLoader imageLoader){
ImageView iv=getView(viewId);
imageLoader.loadImage(iv,imageLoader.getPath());
return this;
}
public abstract static class HolderImageLoader{
private String mPath;
public HolderImageLoader(String path){
this.mPath=path;
}
/**
* 需要去复写这个方法加载图片
* @param iv
* @param path
*/
public abstract void loadImage(ImageView iv,String path);
public String getPath(){
return mPath;
}
}
}
adapter的封装
public abstract class RecyclerCommonAdapter extends RecyclerView.Adapter{
//条目布局
private int mLayoutId;
private List mData;
private Context mContext;
private LayoutInflater mInflater;
private MulitiType mTypeSupport;
public RecyclerCommonAdapter(Context context,List data,int layoutId){
this.mContext=context;
mInflater=LayoutInflater.from(mContext);
this.mData=data;
this.mLayoutId=layoutId;
}
//需要多布局
public RecyclerCommonAdapter(Context context,List data,MulitiType typeSupport){
this(context,data,-1);
this.mTypeSupport=typeSupport;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if(mTypeSupport!=null){
//需要多布局
mLayoutId=viewType;
}
//创建view
View view = mInflater.inflate(mLayoutId, parent, false);
return new ViewHolder(view);
}
@Override
public int getItemViewType(int position) {
//多布局问题
if(mTypeSupport!=null){
return mTypeSupport.getLayoutId(mData.get(position));
}
return super.getItemViewType(position);
}
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
//绑定数据
bindData(holder,mData.get(position),position);
//条目点击事件
if(mItemClickListener!=null){
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mItemClickListener.onItemClick(mData.get(position),position);
}
});
}
//长按点击事件
if(mItemLoogClickListener!=null){
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return mItemLoogClickListener.onItemLongClick(mData.get(position),position);
}
});
}
}
/**
* 将必要参数传递出去
* @param holder
* @param data
* @param position
*/
protected abstract void bindData(ViewHolder holder, DATA data, int position);
@Override
public int getItemCount() {
return mData.size();
}
//使用接口回调点击事件
private OnItemClickListener mItemClickListener;
public void setOnItemClickListener(OnItemClickListener itemClickListener){
this.mItemClickListener=itemClickListener;
}
//使用接口回调点击事件
private OnLongClickListener mItemLoogClickListener;
public void setOnItemLongClickListener(OnLongClickListener itemLongClickListener){
this.mItemLoogClickListener=itemLongClickListener;
}
}
这样子在写adapter的时候,extends RecyclerCommonAdapter,就只需要去复写一个构造方法和bindData()方法就可以了;
public class RecycleAdapter extends RecyclerCommonAdapter<String> {
public RecycleAdapter(Context context, List data, int layoutId) {
super(context, data, layoutId);
}
@Override
protected void bindData(ViewHolder holder, String s, int position) {
//绑定数据
holder.setText(R.id.id_num,s);
}
}
实现起来的效果和上面是一样的,但是是不是感觉代码少了很多,不需要每次都去复写getItemCount、onBindViewHolder、onCreateViewHolder方法,也不需要每次都去写一个ViewHolder,这样可以大大提高开发速度。
RecyclerView多条目的实现
RecyclerView多条目的实现其实在RecyclerView万能adapter封装的时候就已经将其封装进去了,不过不管是ListView的多条目效果还是RecyclerView多条目效果去复写下面这个方法;
//获取条目的类型
@Override
public int getItemViewType(int position) {
//多布局问题
if(mTypeSupport!=null){
return mTypeSupport.getLayoutId(mData.get(position));
}
return super.getItemViewType(position);
}
在extends RecyclerCommonAdapter的时候使用多布局的构造方法
//需要多布局
public RecyclerCommonAdapter(Context context,List data,MulitiType typeSupport){
this(context,data,-1);
this.mTypeSupport=typeSupport;
}
根据数据类型去判断加载相应的布局
private class CommonAdapter extends RecyclerCommonAdapter<ChatData> {
public CommonAdapter(Context context, List data) {
super(context, data, new MulitiType() {
@Override
public int getLayoutId(ChatData item) {
if(item.isMe==1){
return R.layout.item_chat_me;
}
return R.layout.item_chat_friend;
}
});
}
@Override
protected void bindData(ViewHolder holder, ChatData chatData, int position) {
holder.setText(R.id.chat_text,chatData.chatContent);
}
}
ScrollView和RecyclerView嵌套使用
都知道ScrollView和ListView嵌套使用,如果不做处理的话,会导致ListView只能显示一个条目,原因和解决在 ScrollView嵌套 ListView显示不全(http://blog.csdn.net/wangwo1991/article/details/73662365)播客中有;但是ScrollView和RecyclerView嵌套使用就不会出现RecyclerView显示不全的现象,系统已经做了很好的处理;
找到ScrollView源码里面的onMeasure()方法,
//mLayout为空会对根据宽高模式进行测量
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
/**
* Used when onMeasure is called before layout manager is set
*/
void defaultOnMeasure(int widthSpec, int heightSpec) {
// calling LayoutManager here is not pretty but that API is already public and it is better
// than creating another method since this is internal.
final int width = LayoutManager.chooseSize(widthSpec,
getPaddingLeft() + getPaddingRight(),
ViewCompat.getMinimumWidth(this));
final int height = LayoutManager.chooseSize(heightSpec,
getPaddingTop() + getPaddingBottom(),
ViewCompat.getMinimumHeight(this));
setMeasuredDimension(width, height);
}
/**
* Chooses a size from the given specs and parameters that is closest to the desired size
* and also complies with the spec.
*
* @param spec The measureSpec
* @param desired The preferred measurement
* @param min The minimum value
*
* @return A size that fits to the given specs
*/
public static int chooseSize(int spec, int desired, int min) {
final int mode = View.MeasureSpec.getMode(spec);
final int size = View.MeasureSpec.getSize(spec);
switch (mode) {
case View.MeasureSpec.EXACTLY:
//返回widthSpec或者heightSpec 也就是宽或者高
return size;
case View.MeasureSpec.AT_MOST:
//
return Math.min(size, Math.max(desired, min));
case View.MeasureSpec.UNSPECIFIED:
default:
return Math.max(desired, min);
}
}
通过上面这些处理ScrollView和RecyclerView嵌套使用的时候不会出现RecyclerView条目不全。
源码地址:
http://download.csdn.net/download/wangwo1991/9940634