您的位置:首页 > 移动开发 > Android开发

一步步打造Android RecyclerView万能适配器

2016-12-21 15:10 597 查看
转载请注明出处:

http://blog.csdn.net/u014702332/article/details/53785136

本文出版[扫地僧的博客]

基础部分

引入support包

要使用RecyclerView首先需要引入support包:compile ‘com.android.support:recyclerview-v7:25.1.0’

首先查看效果图,依次是线性线性垂直布局,水平布局,网络布局,瀑布流布局









在布局文件中添加这个文件

然后把这个控件添加到布局文件中

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_my_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="king.com.recyler.RecyclerhorizontalActivity">

<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>


代码中使用并声明这个控件

RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

线性布局
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);   //水平布局,可以左右滑动
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);     //垂直布局,可以上下滑动,类似于ListView

//网络布局,
GridLayoutManager layoutManager = new GridLayoutManager(this,5);  //后面这个参数,表示网格有多少列,当前表示5列

//瀑布流布局
StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);  // 前一个参数表示有多少列,后一个参数表示瀑布流的方式;我这里表示有3列,瀑布流方向是垂直方向

recyclerView.setLayoutManager(layoutManager);
FruitAdapter adapter = new FruitAdapter(FruitBean.getFruitBeans(),R.layout.fruit_item_horizontal);
recyclerView.setAdapter(adapter);


构造数据

public class FruitBean{

public int id;
public String name;
public int resourceId;

/**
* 创建数据
* @return
*/
public static List<FruitBean> getFruitBeans(){

List<FruitBean> fruitBeanList = new ArrayList<>();
for(int i = 0;i<100;i++){

FruitBean bean = new FruitBean();
bean.id = i;
switch (i%10){
case 0:
bean.name = "apple";
bean.resourceId = R.drawable.apple_pic;
break;
case 1:
bean.name = "banana";
bean.resourceId = R.drawable.banana_pic;
break;
case 2:
bean.name = "cherry";
bean.resourceId = R.drawable.cherry_pic;
break;
case 3:
bean.name = "grape";
bean.resourceId = R.drawable.grape_pic;
break;
case 4:
bean.name = "mango";
bean.resourceId = R.drawable.mango_pic;
break;
case 5:
bean.name = "orange";
bean.resourceId = R.drawable.orange_pic;
break;
case 6:
bean.name = "pear";
bean.resourceId = R.drawable.pear_pic;
break;
case 7:
bean.name = "pineapple";
bean.resourceId = R.drawable.pineapple_pic;
break;
case 8:
bean.name = "strawberry";
bean.resourceId = R.drawable.strawberry_pic;
break;
case 9:
bean.name = "watermelon";
bean.resourceId = R.drawable.watermelon_pic;
break;
default:
bean.name = "apple";
bean.resourceId = R.drawable.apple_pic;
break;
}
fruitBeanList.add(bean);

}

return fruitBeanList;
}

/**
* 创建数据,流式布局需要使用的。
* @return
*/
public static List<FruitBean> getStaggeredFruitBeans(){

List<FruitBean> fruitBeanList = new ArrayList<>();
for(int i = 0;i<100;i++){

FruitBean bean = new FruitBean();
bean.id = i;
switch (i%10){
case 0:
bean.name = "apple";
bean.resourceId = R.drawable.apple_pic;
break;
case 1:
bean.name = "banana";
bean.resourceId = R.drawable.banana_pic;
break;
case 2:
bean.name = "cherry";
bean.resourceId = R.drawable.cherry_pic;
break;
case 3:
bean.name = "grape";
bean.resourceId = R.drawable.grape_pic;
break;
case 4:
bean.name = "mango";
bean.resourceId = R.drawable.mango_pic;
break;
case 5:
bean.name = "orange";
bean.resourceId = R.drawable.orange_pic;
break;
case 6:
bean.name = "pear";
bean.resourceId = R.drawable.pear_pic;
break;
case 7:
bean.name = "pineapple";
bean.resourceId = R.drawable.pineapple_pic;
break;
case 8:
bean.name = "strawberry";
bean.resourceId = R.drawable.strawberry_pic;
break;
case 9:
bean.name = "watermelon";
bean.resourceId = R.drawable.watermelon_pic;
break;
default:
bean.name = "apple";
bean.resourceId = R.drawable.apple_pic;
break;
}

//这里处理了一下名字长度,区别流式布局
Random random = new Random();
int length = random.nextInt(10)+1;

StringBuilder sb = new StringBuilder();
for (int j=0;j<length;j++){
sb.append(bean.name);
}
bean.name = sb.toString();
fruitBeanList.add(bean);

}

return fruitBeanList;
}


创建数据适配器

public class FruitAdapter extends RecyclerView.Adapter {
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return null;
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

}

@Override
public int getItemCount() {
return 0;
}
}


上面这些代码其实很好理解,创建一个FruitAdapter 继承自RecyclerView.Adapter,这个时候会提示我们需要实现三个方法:

onCreateViewVolder看名字知道要创建一个ViewHolder,然后并返回

onBindViewHolder 给ViewHolder 绑定数据

getItemCount不需要做任何解释。

接下来我们需要显示ViewHolder中的控件

/**
* ViewHolder用于存储列表项中显示的控件,
*/
static class FruitViewHolder extends RecyclerView.ViewHolder{
ImageView ivImg;
TextView tvNo,tvName;

/**
* itemView 就是控件外层布局,
* @param itemView
*/
public FruitViewHolder(View itemView) {
super(itemView);
ivImg = (ImageView) itemView.findViewById(R.id.iv_img);
tvNo = (TextView) itemView.findViewById(R.id.tv_no);
tvName = (TextView) itemView.findViewById(R.id.tv_name);
}
}


创建了一个FruitViewHolder 继承自 RecyclerView.ViewHolder 然后这里会提示需要实现一个构造方法。而构造方法中的View 正是我们要显示控件的根布局,

这里我们就可以通过根布局来创建相应的控件。

我们之前创建的FruiAdapter 中都是用RecyclerView.ViewHolder 这是所有ViewHolder的基类,如果我们需要绑定FruiBean的数据类型,就必须给FruitAdapter中ViewHolder 指定ViewHolder类型,所以我们的代码更改后变成如下:

package king.com.recyler;

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.List;

/**
* Created by liuking on 16/12/20.
* 创建Adapter
*/

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.FruitViewHolder> {

private List<FruitBean> fruitBeanList;
private int mLayoutId;

public FruitAdapter(List<FruitBean> list,int layoutId){
this.fruitBeanList = list;
this.mLayoutId = layoutId;
}

/**
* 构建一个ViewHolder布局
* @param parent
* @param viewType
* @return
*/
@Override
public FruitAdapter.FruitViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(mLayoutId,parent,false);
FruitViewHolder holder = new FruitViewHolder(view);   //创建一个View对象
return holder;
}

/**
* 绑定数据
* @param holder
* @param position
*/
@Override
public void onBindViewHolder(FruitViewHolder holder, int position) {
final FruitBean bean = fruitBeanList.get(position);
holder.ivImg.setImageResource(bean.resourceId);
holder.tvName.setText(bean.name);
holder.tvNo.setText("编号:"+bean.id);
}

@Override
public int getItemCount() {
return fruitBeanList.size();
}

/**
* ViewHolder用于存储列表项中显示的控件,
*/
static class FruitViewHolder extends RecyclerView.ViewHolder{
ImageView ivImg;
TextView tvNo,tvName;

/**
* itemView 就是控件外层布局,
* @param itemView
*/
public FruitViewHolder(View itemView) {
super(itemView);
ivImg = (ImageView) itemView.findViewById(R.id.iv_img);
tvNo = (TextView) itemView.findViewById(R.id.tv_no);
tvName = (TextView) itemView.findViewById(R.id.tv_name);
}
}
}


设置点击事件

至此基础工作已经全部完成了,但我们会发现有些问题。所有的item 都不能点击,这是为什么呢,像ListView每个item都可以点击,为什么RecyclerView就不能点击? 其实Listview 的设计并不是很完美,如果我想点击ListView中的某个item中的button呢,虽然可以 实现,但相对来说是比较麻烦的,而RecyclerView就直接放弃了这样做法,让开发者自己去设定控件每一个点击事件,轻松实现点击:

/**
* 构建一个ViewHolder布局
* @param parent
* @param viewType
* @return
*/
@Override
public FruitAdapter.FruitViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(mLayoutId,parent,false);
final FruitViewHolder holder = new FruitViewHolder(view);   //创建一个View对象

holder.mItemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int position = holder.getAdapterPosition();
FruitBean fb = fruitBeanList.get(position);
Toast.makeText(parent.getContext(), "click item"+position, Toast.LENGTH_SHORT).show();
}
});

holder.ivImg.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int position = holder.getAdapterPosition();
FruitBean fb = fruitBeanList.get(position);
Toast.makeText(parent.getContext(), "click item"+fb.name, Toast.LENGTH_SHORT).show();

}
});
return holder;
}

/**
* ViewHolder用于存储列表项中显示的控件,
*/
static class FruitViewHolder extends RecyclerView.ViewHolder{
View mItemView;
ImageView ivImg;
TextView tvNo,tvName;

/**
* itemView 就是控件外层布局,
* @param itemView
*/
public FruitViewHolder(View itemView) {
super(itemView);
mItemView = itemView;
ivImg = (ImageView) itemView.findViewById(R.id.iv_img);
tvNo = (TextView) itemView.findViewById(R.id.tv_no);
tvName = (TextView) itemView.findViewById(R.id.tv_name);
}
}


我们发现这个Adapter里面其实有很多冗余代码,如果我以后还要写其它的adapter就会产生一堆冗余代码,因此我们需要封装一层,以前我自己封装过ListView中的Adapter,RecyclerView.Adapter也差不多,

打造常用RecyclerAdapter与RecyclerView.ViewHoder基类

1. recyclerView.ViewHoder类构建

首先我们需要创建一个继承于RecyclerView.ViewHolder的类

public class RVViewHolder extends RecyclerView.ViewHolder {

private Context mContext;

public RVViewHolder(Context context, View itemView) {
super(itemView);
this.mContext = context;
}

/**
* 通过viewId获取控件
*
* @param viewId
* @return
*/
public <T extends View> T getView(int viewId) {
View view = itemView.findViewById(viewId);
return (T) view;
}

public RVViewHolder setImageRes(int viewId, int imgRes) {
ImageView view = getView(viewId);
view.setImageResource(imgRes);
return this;
}

public RVViewHolder setImageUrl(int viewId, String imageUrl) {
ImageView view = getView(viewId);
return this;
}

public RVViewHolder setText(int viewId, String text) {
TextView tv = getView(viewId);
tv.setText(text);
return this;
}

public RVViewHolder setTextColor(int viewId, int textColor) {
TextView view = getView(viewId);
view.setTextColor(textColor);
return this;
}

public RVViewHolder setTextSize(int viewId, float size) {
TextView view = getView(viewId);
view.setTextSize(size);
return this;
}

public RVViewHolder setTextColorRes(int viewId, int textColorRes) {
TextView view = getView(viewId);
view.setTextColor(mContext.getResources().getColor(textColorRes));
return this;
}

public RVViewHolder setVisible(int viewId, boolean visible) {
View view = getView(viewId);
view.setVisibility(visible ? View.VISIBLE : View.GONE);
return this;
}

public RVViewHolder setVisiblePlaceHolder(int viewId, boolean visible) {
View view = getView(viewId);
view.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
return this;
}

/**
* 功能描述:设置各个子控件的点击事件
* 作者: hg_liuzl@qq.com
* 时间:2016/12/21 13:28
* 参数:
*/
public RVViewHolder setOnClickListener(int viewId, Object o, View.OnClickListener listener) {
View view = getView(viewId);
view.setTag(o);
view.setOnClickListener(listener);
return this;
}
}


上面代码说明:

创建RecyclerView.ViewHolder子类

获取每一个子类控件

常用的文字与图片设置方法

子控件的点击事件,我这里传了3个参数,控件的Id,对象,监听,一般用到点击事件的时候,都需要绑定数据

2. recyclerViewAdapter 基类构造

先上代码

package king.com.recyler;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import java.util.List;

/**
* @work: 构建RecyclerView.Adapter基类
* @author: hg_liuzl(hg_liuzl@qq.com)
* @date: created at 2016/12/21 11:14
*/

public abstract class BaseRVAdapter<T> extends RecyclerView.Adapter<RVViewHolder> {
protected List<T> mList;      //数据集合
protected int resLayout;      //布局资源
protected Context mContext;
protected IRecyclerViewListener recyclerViewListener;

public BaseRVAdapter(Context context, List<T> mList, int resLayout) {
this.mContext = context;
this.mList = mList;
this.resLayout = resLayout;
}

@Override
public RVViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(resLayout, parent, false);
final RVViewHolder viewHolder = new RVViewHolder(mContext, view);
return viewHolder;
}

@Override
public void onBindViewHolder(final RVViewHolder viewHolder, int position) {
bindAction(viewHolder, mList.get(viewHolder.getLayoutPosition()));

/**绑定事件 一定要在这里绑定**/

if (null != recyclerViewListener) {
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {

recyclerViewListener.onItemClick(viewHolder.itemView, viewHolder.getLayoutPosition());
}
});

viewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
recyclerViewListener.onItemLongClick(viewHolder.itemView, viewHolder.getLayoutPosition());
return false;
}
});
}

bindData(viewHolder, mList.get(position));
}

/**
* 绑定数据
**/
public abstract void bindData(RVViewHolder viewHolder, T bean);

/**
* 绑定动作
**/
public abstract void bindAction(RVViewHolder viewHolder, T bean);

@Override
public int getItemCount() {
if (null != mList) {
return mList.size();
}
return 0;
}

/**
* 设置点击事件
*
* @param mIRecyclerViewListener
*/
public void setIRecyclerViewListener(IRecyclerViewListener mIRecyclerViewListener) {
this.recyclerViewListener = mIRecyclerViewListener;
}

public interface IRecyclerViewListener {

void onItemClick(View view, int position);

void onItemLongClick(View view, int position);
}
}


代码解读:

1. 首先创建RecyclerAdapter的子类的抽象类,把一些需要实现的方法给子类去实现

2. 添加构造方法,其中构造函数包括Context,list,布局资源 这样我们可以根据自己的需要传入相应数据类型,以及布局

3. 在onBindViewHolder中添加数据以及绑定事件,另外事件的回调函数一定要判断不为空才可以调用回调方法

给Item 添加回调,使item可以点击以及长按

3. recycler的getAdapterPostion与getLayoutPosition()坑点说明

根据官方说明getAdapterPosition是在变化的时候就能获取到position,而getLayoutPosition是在布局完成后可以获取到position,一般我们需要获取某个position的时候,肯定是在布局完成后获取,所以建议使用getLayoutPosition来获取position

另外也不要在onCreateView中获取某个实体类,很容易造成数组越界,因为这个时候getAdapterPosition为-1 在创建之前布局还没有完成,

在子类中继承万能RecyclerViewAdapter

package king.com.recyler;

import android.content.Context;
import android.view.View;
import android.widget.Toast;

import java.util.List;

/**
*
* @work: ${功能介绍}
* @author: hg_liuzl(hg_liuzl@qq.com)
* @date: created at 2016/12/21 12:48
*/

public class FruitAdapter2 extends BaseRVAdapter implements View.OnClickListener {

public FruitAdapter2(Context context, List mList, int resLayout) {
super(context, mList, resLayout);
}

@Override
public void bindAction(RVViewHolder viewHolder, Object o) {
viewHolder.setOnClickListener(R.id.tv_name, o, this);
}

@Override
public void bindData(RVViewHolder viewHolder, Object o) {
final FruitBean bean = (FruitBean) o;
viewHolder.setText(R.id.tv_name, bean.name);
viewHolder.setText(R.id.tv_no, "编号:" + bean.id);
viewHolder.setImageRes(R.id.iv_img, bean.resourceId);
}

@Override
public void onClick(View view) {
final FruitBean bean = (FruitBean) view.getTag();
switch (view.getId()) {
case R.id.tv_name:
Toast.makeText(mContext, "你点击了Item中的" + bean.id + "----name是" + bean.name, Toast.LENGTH_SHORT).show();
break;
}
}
}


代码解读:

首先基础FruitAdapter继承于BaseRVAdapter,并实现其2个抽象方法

在bindAction方法中绑定子控件的点击事件,在bindData方法中绑定数据

另外实现OnClick接口,来完成点击事件

最后使用我们创建的万能基类实际调用:

/**
* 垂直布局
*/
public class RecyclerVerticalActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_recycler_view);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
FruitAdapter2 adapter = new FruitAdapter2(this, FruitBean.getFruitBeans(), R.layout.fruit_item_vertical);
recyclerView.setAdapter(adapter);
adapter.setIRecyclerViewListener(recyclerViewListener);

}

private BaseRVAdapter.IRecyclerViewListener recyclerViewListener = new BaseRVAdapter.IRecyclerViewListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(RecyclerVerticalActivity.this, position + " click",
Toast.LENGTH_SHORT).show();
}

@Override
public void onItemLongClick(View view, int position) {
Toast.makeText(RecyclerVerticalActivity.this, position + " longClick",
Toast.LENGTH_SHORT).show();
}
};
}


可以看到适配器用的是FruitAdapter2

给Adaapter绑定回调方法,使item可以点击

最后再看看效果



代码地址
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Android 封装