在 Android 开发中,RecyclerView 已成为列表展示的常用组件。但当列表需要承载多种不同类型的 item——例如首页的 Banner、广告、文本、图片、新闻混排时,实现难度会显著增加。不少开发者习惯通过 ViewType 进行区分,但在逻辑复杂、类型增多的情况下,这种方式往往导致代码臃肿、维护困难。下面我们将探讨这一问题,并介绍如何优雅地封装 Adapter。
01.先看看实际需求
以某个 APP 首页为例,它通常包含 Banner 区、广告区、文本内容、图片内容、新闻内容等模块。
RecyclerView 虽然可以通过 ViewType 区分不同 item 来满足需求,但仍存在一些明显的问题,例如:

- 当列表项过多、逻辑复杂时,Adapter 内部的代码量庞大,逻辑混乱,后期难以维护。
- 每次新增列表都需要编写新的 Adapter,重复劳动,开发效率低下。
- Adapter 无法复用:若多个页面包含多种类型,就必须为每个页面单独编写 Adapter。
- 局部刷新较为麻烦。例如广告区本身是一个九宫格 RecyclerView,点击后需要局部刷新当前数据,实现起来比较繁琐。
02.adapter实现多个type
常见的多 Item 列表实现方式:根据不同的 ViewType 分别处理 item。如果业务逻辑复杂,该类代码量会非常庞大。版本迭代引入新需求时,修改代码费力,后期维护成本高。
主要操作步骤:
- 在
onCreateViewHolder中依据 viewType 参数(即getItemViewType的返回值)判断需要创建的 ViewHolder 类型。 - 在
onBindViewHolder方法中对 ViewHolder 的具体类型进行判断,分别为不同类型的 ViewHolder 绑定数据并处理逻辑。
代码如下所示:
public class HomePageAdapter extends RecyclerView.Adapter {
public static final int TYPE_BANNER = 0;
public static final int TYPE_AD = 1;
public static final int TYPE_TEXT = 2;
public static final int TYPE_IMAGE = 3;
public static final int TYPE_NEW = 4;
private List mData;
public void setData(List data) { mData = data; }
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType){
case TYPE_BANNER:
return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_banner_layout,null));
case TYPE_AD:
return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_ad_item_layout,null));
case TYPE_TEXT:
return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_text_item_layout,null));
case TYPE_IMAGE:
return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_image_item_layout,null));
case TYPE_NEW:
return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_news_item_layout,null));
}
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
int type = getItemViewType(position);
switch (type){
case TYPE_BANNER: // banner 逻辑处理
break;
case TYPE_AD: // 广告逻辑处理
break;
case TYPE_TEXT: // 文本逻辑处理
break;
case TYPE_IMAGE: // 图片逻辑处理
break;
case TYPE_NEW: // 视频逻辑处理
break;
// ... 此处省去N行代码
}
}
@Override
public int getItemViewType(int position) {
if(position == 0){
return TYPE_BANNER; // banner在开头
}else {
return mData.get(position).type; // type 的值为TYPE_AD,TYPE_IMAGE,TYPE_AD,等其中一个
}
}
@Override
public int getItemCount() {
return mData == null ? 0 : mData.size();
}
public static class BannerViewHolder extends RecyclerView.ViewHolder{
public BannerViewHolder(View itemView) { super(itemView); //绑定控件 }
}
public static class NewViewHolder extends RecyclerView.ViewHolder{
public VideoViewHolder(View itemView) { super(itemView); //绑定控件 }
}
public static class AdViewHolder extends RecyclerView.ViewHolder{
public AdViewHolder(View itemView) { super(itemView); //绑定控件 }
}
public static class TextViewHolder extends RecyclerView.ViewHolder{
public TextViewHolder(View itemView) { super(itemView); //绑定控件 }
}
public static class ImageViewHolder extends RecyclerView.ViewHolder{
public ImageViewHolder(View itemView) { super(itemView); //绑定控件 }
}
}
03.这样写的弊端
上述实现方式存在三个显著缺陷:
- 类型检查与类型转型:由于
onCreateViewHolder中针对不同类型创建了各自的 ViewHolder,因此在onBindViewHolder中需要对不同 ViewHolder 进行数据绑定和逻辑处理,这必然导致使用instanceof进行类型检查与转型。 - 不利于扩展:当前需求是列表中存在 5 种布局类型。但如果需求变化,极端情况下数据源来自服务器,由 model 决定列表布局类型。此时每当 model 变更或类型增多,都需要修改 Adapter 大量代码,并且 Adapter 必须知道特定 model 在列表中的位置(position)——除非与服务端约定 model 位置固定,这显然不现实。
- 不利于维护:这是上一点的延伸。随着列表中布局类型的增加或调整,
getItemViewType、onCreateViewHolder、onBindViewHolder中的代码都需要相应变更或新增,导致 Adapter 代码臃肿混乱,维护成本不断提高。
04.如何优雅实现adapter封装
核心目标有三个:避免类的类型检查与类型转型、增强 Adapter 的扩展性、提升 Adapter 的可维护性。当列表中类型增加或减少时,Adapter 中主要改动的是 getItemViewType、onCreateViewHolder、onBindViewHolder 这三个方法,因此我们从此入手进行优化。
既然可能包含多个 type 类型的视图,那么能否将诸如 Banner、广告、文本、视频、新闻等视为一个 HeaderView 来统一处理呢?
在 getItemViewType 方法中:减少 if 等逻辑判断,简化代码,可以直接使用 hashCode 作为 type 标识。通过创建列表布局类型,返回的不再是简单的布局类型标识,而是布局对应的 hashCode 值。
private ArrayList headers = new ArrayList<>();
public interface InterItemView {
/** 创建view */
View onCreateView(ViewGroup parent);
/** 绑定view */
void onBindView(View headerView);
}
@Deprecated
@Override
public final int getItemViewType(int position) {
if (headers.size()!=0){
if (position= 0){
return footers.get(i).hashCode();
}
}
return getViewType(position-headers.size());
}
在 onCreateViewHolder 方法中:getItemViewType 返回的是布局的 hashCode 值,即 onCreateViewHolder(ViewGroup parent, int viewType) 参数中的 viewType。
@NonNull
@Override
public final BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = createViewByType(parent, viewType);
if (view!=null){
return new BaseViewHolder(view);
}
final BaseViewHolder viewHolder = OnCreateViewHolder(parent, viewType);
setOnClickListener(viewHolder);
return viewHolder;
}
private View createViewByType(ViewGroup parent, int viewType){
for (InterItemView headerView : headers){
if (headerView.hashCode() == viewType){
View view = headerView.onCreateView(parent);
StaggeredGridLayoutManager.LayoutParams layoutParams;
if (view.getLayoutParams()!=null) {
layoutParams = new StaggeredGridLayoutManager.LayoutParams(view.getLayoutParams());
} else {
layoutParams = new StaggeredGridLayoutManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
layoutParams.setFullSpan(true);
view.setLayoutParams(layoutParams);
return view;
}
}
for (InterItemView footerView : footers){
if (footerView.hashCode() == viewType){
View view = footerView.onCreateView(parent);
StaggeredGridLayoutManager.LayoutParams layoutParams;
if (view.getLayoutParams()!=null) {
layoutParams = new StaggeredGridLayoutManager.LayoutParams(view.getLayoutParams());
} else {
layoutParams = new StaggeredGridLayoutManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
layoutParams.setFullSpan(true);
view.setLayoutParams(layoutParams);
return view;
}
}
return null;
}
在 onBindViewHolder 方法中:可以看到,添加 header 类型视图后,通过 onBindView 进行数据绑定即可。
@Override
public final void onBindViewHolder(BaseViewHolder holder, int position) {
holder.itemView.setId(position);
if (headers.size()!=0 && position=0){
footers.get(i).onBindView(holder.itemView);
return ;
}
OnBindViewHolder(holder,position-headers.size());
}
如何使用?下面以 Banner 类型为例,展示了如何解耦之前 Adapter 中的复杂操作:
InterItemView interItemView = new InterItemView() {
@Override
public View onCreateView(ViewGroup parent) {
BannerView header = new BannerView(HeaderFooterActivity.this);
header.setHintView(new ColorPointHintView(HeaderFooterActivity.this,Color.YELLOW, Color.GRAY));
header.setHintPadding(0, 0, 0, (int) AppUtils.convertDpToPixel(8, HeaderFooterActivity.this));
header.setPlayDelay(2000);
header.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
(int) AppUtils.convertDpToPixel(200, HeaderFooterActivity.this)));
header.setAdapter(new BannerAdapter(HeaderFooterActivity.this));
return header;
}
@Override
public void onBindView(View headerView) {}
};
adapter.addHeader(interItemView);
封装后的优势十分明显:
- 拓展性——Adapter 无需关心不同列表类型在列表中的具体位置,因此可以随意增加或减少列表类型,操作便捷。同时,类型视图的布局创建与数据绑定都不需要在 Adapter 中处理,实现了充分解耦。
- 可维护性——不同的列表类型通过 Adapter 添加 headerView 来处理,即使添加多个 headerView,彼此之间互不影响,代码简洁,维护成本低。
