Android - RecyclerView 解析
一、RecyclerView 普通列表实现
RecyclerView 可以实现 ListView 的效果,它通过使用 LayoutManager 来确定每一个 item 的排列方式,同时为增加和删除项目提供默认的动画效果。
1. 列表显示
MainActivity:初始化数据并设置适配器
public class MainActivity extends AppCompatActivity {
private RecyclerView main_recycler_view;
private List<String> mData;
@Override
protected void onCreate(Bundle savedInstanceState) {
// ...
initData();
// 设置自定义适配器
main_recycler_view.setAdapter(new RecyclerAdapter(this, mData));
// 设置布局管理器
main_recycler_view.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
// 设置动画
main_recycler_view.setItemAnimator(new DefaultItemAnimator());
// 设置分割线
main_recycler_view.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
}
// 初始化数据
private void initData() {
mData = new ArrayList<>();
for (int i = 'A'; i <= 'z'; i++) {
mData.add("" + (char) i);
}
}
}
Item 布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="80dp">
<LinearLayout
android:layout_width="360dp"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/item_text_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="XXX"
android:textSize="24sp"
android:textColor="@android:color/black"
android:padding="5dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:text="content"
android:textSize="16sp"
android:textColor="@android:color/black"/>
</LinearLayout>
</LinearLayout>
RecyclerAdapter:自定义适配器
// 设置 RecyclerAdapter 继承自 RecyclerView.Adapter<RecyclerAdapter.ViewHolder>
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder> {
private Context mContext;
private List<String> mData;
// 构造方法,传入上下文和列表数据
public RecyclerAdapter(Context context, List<String> data) {
this.mContext = context;
this.mData = data;
}
// 加载 Item 的布局项
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = View.inflate(mContext, R.layout.item_recycler_view, null);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
// 获得当前数据对象并设置显示
String data = mData.get(position);
holder.item_text_title.setText(data);
}
@Override
public int getItemCount() {
return mData.size();
}
// ViewHolder 内部类
class ViewHolder extends RecyclerView.ViewHolder {
TextView item_text_title;
public ViewHolder(View itemView) {
super(itemView);
item_text_title = (TextView) itemView.findViewById(R.id.item_text_title);
}
}
}
主页面布局以及内容项布局可以按自己的喜好设置。以下是 LayoutManager 进行各种不同设置时的运行结果。



2. Item 点击事件
有两种方法可以实现 Item 的点击事件,且都可以传入数据。
第一种,改写 ViewHolder 内部类,为其添加点击事件。
ViewHolder 类实现 OnclickListener 接口,itemView 设置该点击事件。
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
// 在 bindData() 中传入数据(这里只是 String,一般为自定义数据对象)
private String mData;
// 视图对象
private TextView item_text_title;
public ViewHolder(View itemView) {
super(itemView);
// 为 itemView 设置点击事件
itemView.setOnClickListener(this);
item_text_title = (TextView) itemView.findViewById(R.id.item_text_title);
}
// 将点击的数据对象传入并设置视图对象的显示
public void bindData(String data) {
mData = data;
item_text_title.setText(mData);
}
@Override
public void onClick(View v) {
Toast.makeText(mContext, "你点击了" + mData, Toast.LENGTH_SHORT).show();
}
}
之后在 adapter 的 onBindViewHolder(...) 中调用新添加的 bindData() 方法。
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.bindData(mData.get(position));
}
第二种,添加接口,使外部可以回调内部的点击事件。
首先在自定义的 RecyclerAdapter 中添加一个接口以及对应方法。
private OnMyItemClickListener listener;
interface OnMyItemClickListener {
void onItemClick(int position);
}
// 用于外部设置 RecyclerAdapter 中的接口,重写其方法。
public void setOnMyItemClickListener(OnMyItemClickListener l) {
this.listener = l;
}
改写 ViewHolder 内的构造方法。
public ViewHolder(View itemView) {
super(itemView);
item_text_title = (TextView) itemView.findViewById(R.id.item_text_title);
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) {
listener.onItemClick(getLayoutPosition());
}
}
});
}
在主页面中为 RecyclerAdapter 设置接口,重写 onItemClick() 方法。
adapter.setOnMyItemClickListener(new RecyclerAdapter.OnMyItemClickListener() {
@Override
public void onItemClick(int position) {
Toast.makeText(MainActivity.this, mData.get(position) + " Clicked!", Toast.LENGTH_SHORT).show();
}
});
3. 添加和删除 Item
在 adapter 中加入两个函数:addItem(position) 以及 removeItem(position)
在函数中,先对数据集合进行操作,再通知适配器数据已经更改。
注:这里使用 notifyItemInserted(position) 与 notifyItemRemoved (position) 通知适配器,而非 ListView 中常用的 adapter.notifyDataSetChanged()
// 添加 Item 项
public void addItem(int position) {
mData.add(position, "XXX");
notifyItemInserted(position);
}
// 删除 Item 项
public void removeItem(int position) {
mData.remove(position);
notifyItemRemoved(position);
}
设置 ToolBar,加入“添加”以及“删除”两个按钮进行操作,效果如下:

因为我们已经在刚开始设置了默认的动画效果:
main_recycler_view.setItemAnimator(new DefaultItemAnimator());
所以添加或者删除 Item 项时会有很不错的效果~

二、分类型 RecyclerView 的实现
分类型 RecyclerView 可以通过代码实现界面的多样化,如下图所示,一共三部分:最上方为轮播图片(Banner实现),中间是 GridView,下方是 ViewPager。
实现这样的效果,在总的布局文件中只需要定义一个 <RecyclerView> 即可。

总 RecyclerView 及其 adapter
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/home_recycle_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
Adapter 代码:
public class HomeFragmentAdapter extends RecyclerView.Adapter {
// 定义 3 种类型的编码
private static final int BANNER = 0;
private static final int CHANNEL = 1;
private static final int ACT = 2;
// 当前类型
private int currentType = BANNER;
private Context context;
private JsonResult.ResultBean result; // 联网获得的 json 数据
private LayoutInflater layoutInflater;
public HomeFragmentAdapter(Context context, JsonResult.ResultBean result) {
this.context = context;
this.result = result;
layoutInflater = LayoutInflater.from(context);
}
//根据 RecyclerView 中的类型返回不同的 ViewHolder
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == BANNER) {
return new BannerViewHolder(context, layoutInflater.inflate(R.layout.banner_view_pager, null));
} else if (viewType == CHANNEL) {
return new ChannelViewHolder(context, layoutInflater.inflate(R.layout.channel_item, null));
} else if (viewType == ACT) {
return new ActViewHolder(context, layoutInflater.inflate(R.layout.act_item, null));
}
return null;
}
// 根据 position 判断 RecyclerView 类型并绑定数据
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (getItemViewType(position) == BANNER) {
BannerViewHolder bannerViewHolder = (BannerViewHolder) holder;
bannerViewHolder.setData(result.getBanner_info());
} else if (getItemViewType(position) == CHANNEL) {
ChannelViewHolder channelViewHolder = (ChannelViewHolder) holder;
channelViewHolder.setData(result.getChannel_info());
} else if (getItemViewType(position) == ACT) {
ActViewHolder actViewHolder = (ActViewHolder) holder;
actViewHolder.setData(result.getAct_info());
}
}
// 获取当前 RecyclerView 的类型
@Override
public int getItemViewType(int position) {
switch (position) {
case BANNER:
currentType = BANNER;
break;
case CHANNEL:
currentType = CHANNEL;
break;
case ACT:
currentType = ACT;
break;
}
return currentType;
}
// 一共有 3 种不同类型的 RecyclerView
@Override
public int getItemCount() {
return 3;
}
}
1. BannerViewHolder
Banner 是一个第三方库,用于实现 Android 的图片轮播,效果如下所示。
github 地址:Banner

布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.youth.banner.Banner
android:id="@+id/home_banner"
android:layout_width="match_parent"
android:layout_height="150dp"
app:indicator_height="5dp"
app:indicator_width="5dp"
app:is_auto_play="true">
</com.youth.banner.Banner>
</LinearLayout>
代码:
public class BannerViewHolder extends RecyclerView.ViewHolder {
private Context mContext;
private Banner banner;
public BannerViewHolder(Context context, View itemView) {
super(itemView);
this.mContext = context;
this.banner = (Banner) itemView.findViewById(R.id.home_banner);
}
// 设置数据并开启轮播
public void setData(List<JsonResult.ResultBean.BannerInfoBean> banner_info) {
List<String> imageUrls = new ArrayList<>();
for (int i = 0; i < banner_info.size(); i++) {
imageUrls.add(banner_info.get(i).getImage());
}
banner.setImages(imageUrls); // 设置图片 URL 的集合
banner.setImageLoader(new GlideImageLoader());
banner.setBannerAnimation(Transformer.Accordion); // 设置动画效果
banner.start();
}
// 图片加载类
private class GlideImageLoader extends ImageLoader {
@Override
public void displayImage(Context context, Object path, ImageView imageView) {
// 使用 Glide 加载图片
Glide.with(context).load(path).into(imageView);
}
}
}
2. ChannelViewHolder
可以看出,整个频道模块就是一个 numColumns 为 5 的 GridView。
因此在 ChannelViewHolder 中初始化视图对象后要为此 GridView 设置适配器。

布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="170dp"
android:background="#fff"
android:orientation="vertical">
<GridView
android:id="@+id/home_gv_channel"
android:layout_width="wrap_content"
android:layout_height="150dp"
android:numColumns="5">
</GridView>
</LinearLayout>
ChannelViewHolder 代码:
public class ChannelViewHolder extends RecyclerView.ViewHolder {
private Context mContext;
private GridView home_gv_channel;
public ChannelViewHolder(Context context, View itemView) {
super(itemView);
this.mContext = context;
home_gv_channel = (GridView) itemView.findViewById(R.id.home_gv_channel);
}
public void setData(List<JsonResult.ResultBean.ChannelInfoBean> channel_info) {
ChannelAdapter adapter = new ChannelAdapter(mContext, channel_info);
home_gv_channel.setAdapter(adapter);
}
}
Adapter 代码:
public class ChannelAdapter extends BaseAdapter {
private Context mContext;
private List<ChannelInfoBean> channel_info;
public ChannelAdapter(Context context, List<ChannelInfoBean> channel_info) {
this.mContext = context;
this.channel_info = channel_info;
}
@Override
public int getCount() { return channel_info.size(); }
@Override
public Object getItem(int position) { return channel_info.get(position); }
@Override
public long getItemId(int position) { return 0; }
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = View.inflate(mContext, R.layout.item_channel, null);
viewHolder.iv_channel = (ImageView) convertView.findViewById(R.id.iv_channel);
viewHolder.tv_channel = (TextView) convertView.findViewById(R.id.tv_channel);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
ChannelInfoBean data = channel_info.get(position);
Glide.with(mContext).load(data.getImage()).into(viewHolder.iv_channel);
viewHolder.tv_channel.setText(data.getChannel_name());
return convertView;
}
private static class ViewHolder {
ImageView iv_channel;
TextView tv_channel;
}
}