资讯动态

RecyclerView与ListView适配器封装实践:从ViewHolder到加载更多全攻略

发布时间:2026/10/18 16:28:26 来源:尧图企业网站定制
真要说起来RecyclerView和ListView的封装其实是个老生常谈的话题但只要你的项目还在用原生列表控件这个问题就永远绕不过去。尤其是当你手头同时管着两三个项目每个项目里都有列表页每个列表页的适配器代码都长得差不多又各自为政的时候你就会意识到封装不是锦上添花而是纯纯的刚需。我最早被这个问题折磨是在接手一个历史项目的时候。项目里ListView和RecyclerView混用适配器从BaseAdapter到RecyclerView.Adapter都有里面还直接塞了数据加载逻辑、点击事件甚至有把网络回调写进ViewHolder里的。当时我就觉得这玩意要是再不梳理一下后面接手的兄弟大概率会在某个深夜问候前同事全家。所以这篇文章不打算讲那种大而全的框架设计也不搞什么高深到看不懂的抽象层次就讲我在实际操作中是怎么一步步封装ListView和RecyclerView的以及封装过程中踩过的那些坑。目标很明确让你看完之后能直接动手给自己项目里的列表控件做一个干净利落的封装。1. 列表控件封装的整体思路拆解封装列表控件第一步不是写代码而是想清楚你到底要解决什么问题。抛开业务功能差异不谈ListView和RecyclerView在使用上有一个共同的痛点适配器里全是样板代码。比如一个最简单的人员列表最原始的写法就是这样public class PersonAdapter extends BaseAdapter { private Context context; private ListPerson persons; private LayoutInflater inflater; public PersonAdapter(Context context, ListPerson persons) { this.context context; this.persons persons; this.inflater LayoutInflater.from(context); } Override public int getCount() { return persons null ? 0 : persons.size(); } Override public Object getItem(int position) { return persons.get(position); } Override public long getItemId(int position) { return position; } Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView null) { convertView inflater.inflate(R.layout.item_person, parent, false); holder new ViewHolder(); holder.nameTv convertView.findViewById(R.id.tv_name); holder.ageTv convertView.findViewById(R.id.tv_age); convertView.setTag(holder); } else { holder (ViewHolder) convertView.getTag(); } Person person persons.get(position); holder.nameTv.setText(person.getName()); holder.ageTv.setText(String.valueOf(person.getAge())); return convertView; } static class ViewHolder { TextView nameTv; TextView ageTv; } }这段代码放在一个Demo里没什么问题但你有十个八个这样的列表页你就会发现getView里面反复在做同一件事情判空convertView、加载布局、findViewById、setTag、绑定数据。 RecyclerView虽然写法变成了onCreateViewHolder和onBindViewHolder但本质上还是这一套流程。区别只是RecyclerView强制了你用ViewHolder不至于让你像ListView那样在性能上翻车。封装的思路其实就是把这一套固定的流程抽出来把变化的部分留成接口或者泛型参数。我需要做的就是把“加载布局”和“绑定数据”这两件事抽象出来其他东西全部收敛到基类里。基于这个想法我当时的封装拆成了三层结构基类适配器层负责ViewHolder的创建复用、数据集合的管理、Item点击事件的回调。通用ViewHolder层把findViewById和控件缓存封装掉统一管理itemView。回调接口层让外部只需要关心每条Item的数据和UI绑定关系。这个三层结构的好处是职责边界非常清楚。底层做缓存复用中层做视图管理上层做业务绑定。后面不管你是列表还是网格是单类型还是多类型都是在这个骨架上去扩展不需要动底层逻辑。2. 通用ViewHolder与基类Adapter的封装实现2.1 通用ViewHolder的设计ViewHolder的核心作用是什么缓存itemView里的子控件避免每次绑定数据都去findViewById一遍。那封装的第一步就是把这一层做到极致让业务代码不再感知ViewHolder具体的创建和缓存过程。这里我首选的设计是用一个Map来按ID缓存控件public class CommonViewHolder { private final SparseArrayView views new SparseArray(); private final View convertView; private CommonViewHolder(View convertView) { this.convertView convertView; } public static CommonViewHolder create(View convertView, ViewGroup parent, int layoutId) { if (convertView null) { convertView LayoutInflater.from(parent.getContext()) .inflate(layoutId, parent, false); CommonViewHolder holder new CommonViewHolder(convertView); convertView.setTag(holder); return holder; } return (CommonViewHolder) convertView.getTag(); } SuppressWarnings(unchecked) public T extends View T getView(int viewId) { View view views.get(viewId); if (view null) { view convertView.findViewById(viewId); views.put(viewId, view); } return (T) view; } public View getConvertView() { return convertView; } }这里用了SparseArray而不是HashMap原因很简单SparseArray是Android官方推荐的优化方案它避免了整数键的装箱拆箱操作在数据量少的时候性能反而比HashMap更好。这种细节在列表滚动的频繁调用场景下是有意义的。getView方法里SparseArray也是做缓存第一次findViewById之后就把控件放进去下次直接从缓存取。这样做的性能提升可能比你在bindData里少写两行if判断还明显而且代码写起来更舒服。有了CommonViewHolderListView的适配器就可以这样写public abstract class CommonAdapterT extends BaseAdapter { private final LayoutInflater inflater; private ListT dataList; public CommonAdapter(Context context, ListT dataList) { this.inflater LayoutInflater.from(context); this.dataList dataList null ? new ArrayList() : dataList; } Override public int getCount() { return dataList.size(); } Override public T getItem(int position) { return dataList.get(position); } Override public long getItemId(int position) { return position; } public abstract int getLayoutId(int position, T item); public abstract void bindData(CommonViewHolder holder, T item, int position); Override public View getView(int position, View convertView, ViewGroup parent) { T item getItem(position); CommonViewHolder holder CommonViewHolder.create(convertView, parent, getLayoutId(position, item)); bindData(holder, item, position); return holder.getConvertView(); } }getLayoutId被设计成带参数是有讲究的。因为实际业务里一个ListView同一屏可能出现多种条目样式比如列表里混着标签类型的item和内容类型的item。如果getLayoutId是固定返回一个布局那多类型就没办法实现了。这里把position和item都传进去子类可以根据数据自行判断该用哪个布局。2.2 RecyclerView版的基类AdapterRecyclerView的封装思路和ListView类似但有几个地方不同。首先是它强制你用ViewHolder所以CommonViewHolder需要直接继承RecyclerView.ViewHolder这个类的构造参数就是ItemView恰好可以让我们直接把convertView传进去。public class RecyclerViewHolder extends RecyclerView.ViewHolder { private final SparseArrayView views new SparseArray(); public RecyclerViewHolder(View itemView) { super(itemView); } SuppressWarnings(unchecked) public T extends View T getView(int viewId) { View view views.get(viewId); if (view null) { view itemView.findViewById(viewId); views.put(viewId, view); } return (T) view; } }对应的基类Adapterpublic abstract class CommonRvAdapterT extends RecyclerView.AdapterRecyclerViewHolder { protected ListT dataList; public CommonRvAdapter(ListT dataList) { this.dataList dataList null ? new ArrayList() : dataList; } NonNull Override public RecyclerViewHolder onCreateViewHolder(NonNull ViewGroup parent, int viewType) { int layoutId getLayoutId(viewType); View itemView LayoutInflater.from(parent.getContext()) .inflate(layoutId, parent, false); return new RecyclerViewHolder(itemView); } Override public void onBindViewHolder(NonNull RecyclerViewHolder holder, int position) { T item dataList.get(position); bindData(holder, item, position); } Override public int getItemViewType(int position) { return getViewType(position, dataList.get(position)); } Override public int getItemCount() { return dataList.size(); } protected abstract int getLayoutId(int viewType); protected abstract void bindData(RecyclerViewHolder holder, T item, int position); protected int getViewType(int position, T item) { return 0; } }这里getItemViewType默认返回0子类如果有多类型需求就重写getViewType。getLayoutId只在onCreateViewHolder里用viewType去拿所以单类型情况下子类只需要关心getLayoutId和bindData两个方法。使用起来比ListView那个版本要清爽很多public class PersonListAdapter extends CommonRvAdapterPerson { public PersonListAdapter(ListPerson dataList) { super(dataList); } Override protected int getLayoutId(int viewType) { return R.layout.item_person; } Override protected void bindData(RecyclerViewHolder holder, Person person, int position) { holder.getView(R.id.tv_name).setText(person.getName()); holder.getView(R.id.tv_age).setText(String.valueOf(person.getAge())); } }说句实在话写到这一步之后我基本上就告别了在子类里再去覆盖onBindViewHolder这种操作了。后面新增一个列表页只需要新建一个Adapter继承CommonRvAdapter实现getLayoutId和bindData三分钟搞定。代码量至少减少一半以上。3. 封装点击事件与多类型列表的支持3.1 点击事件的统一回调列表Item的点击事件是使用频率最高的交互之一。如果不封装那么每个Adapter里都要创建一个OnClickListener然后在bindData或者getView里给每个需要点击的控件setOnClickListener再把position通过holder.getAdapterPosition()拿回来。这个流程不难但重复率极高。我的做法是在基类里加一个setOnItemClickListener接口然后在onBindViewHolder里统一处理public interface OnItemClickListenerT { void onItemClick(View view, T item, int position); }基类中增加字段和设置方法protected OnItemClickListenerT itemClickListener; public void setOnItemClickListener(OnItemClickListenerT listener) { this.itemClickListener listener; }然后在onBindViewHolder里给itemView设置点击监听Override public void onBindViewHolder(NonNull RecyclerViewHolder holder, int position) { T item dataList.get(position); holder.itemView.setOnClickListener(v - { if (itemClickListener ! null) { itemClickListener.onItemClick(v, item, holder.getAdapterPosition()); } }); bindData(holder, item, position); }这里有个细节不要用position去绑定点击事件因为在RecyclerView的Item复用机制下position可能因为增删Item而变得不准确。在回调触发时才通过holder.getAdapterPosition()获取当前真实位置才是安全的。ListView那边则可以直接用position因为数据源和列表是同步的。如果你的列表里Item内部还有子控件的点击事件比如“收藏”按钮LongClick等那也可以在基类再提供一些辅助接口比如OnItemChildClickListener和OnItemLongClickListener。但我的经验是这类接口不要无脑往上堆按需添加就行。一个基类如果接口太多往后用起来反而累赘。3.2 多类型列表的封装策略前面提过基类的getViewType默认返回0当同一个列表需要展示多种类型数据时有两种做法。第一种是重写getViewType和getLayoutId。这是最标准、最直观的方式private static final int TYPE_TEXT 0; private static final int TYPE_IMAGE 1; Override protected int getViewType(int position, FeedInfo item) { if (item.getType() FeedInfo.TYPE_TEXT) { return TYPE_TEXT; } return TYPE_IMAGE; } Override protected int getLayoutId(int viewType) { if (viewType TYPE_IMAGE) { return R.layout.item_feed_image; } return R.layout.item_feed_text; }第二种是我后来在项目里用的更灵活的方式就是把“判断类型”这件事交给数据本身去处理。具体怎么做呢给数据源定义一个接口让每个数据对象自己告诉适配器它对应的是哪种类型public interface MultiTypeItem { int getDataType(); }公共Adapter里增加一个重载构造方法public abstract class CommonMultiTypeAdapterT extends MultiTypeItem extends RecyclerView.AdapterRecyclerViewHolder { private ListT dataList; public CommonMultiTypeAdapter(ListT dataList) { this.dataList dataList; } Override public int getItemViewType(int position) { return dataList.get(position).getDataType(); } NonNull Override public RecyclerViewHolder onCreateViewHolder(NonNull ViewGroup parent, int viewType) { int layoutId getLayoutId(viewType); View itemView LayoutInflater.from(parent.getContext()) .inflate(layoutId, parent, false); return new RecyclerViewHolder(itemView); } }然后在外部维护一个layoutId和viewType的映射关系就能实现完全基于数据的多类型渲染。这种思路在面对那种不断变化类型的列表时尤其好用因为后续新增类型你只需要增加一个数据实现类不用去改Adapter。不过说实话如果只是两三种固定类型用第一种方法就够了。第二种适合做动态运营位、动态卡片这类需求过度设计也是一种负担。4. 列表的加载更多、空布局与头部布局那些事4.1 RecyclerView的加载更多封装真到了业务层面最影响开发效率的不是基础绑定而是加载更多、空布局、Header/Footer这些在列表开发中绕不开的实际问题。加载更多的常规做法是给Adapter加一个“底部状态Item”。这个Item只有三种状态加载中、上拉加载、没有更多了。这三态分别对应三种UI展示转圈动画、文字提示、尾巴样式。我在基类里的实现是这样的public static final int STATE_DEFAULT 0; public static final int STATE_LOADING 1; public static final int STATE_FINISH 2; public static final int STATE_FAIL 3; private int loadState STATE_DEFAULT; private boolean hasFooter false;增加设置状态的方法public void setLoadState(int state) { this.loadState state; this.hasFooter (state ! STATE_DEFAULT); notifyDataSetChanged(); } public void setLoadFailed() { this.loadState STATE_FAIL; notifyDataSetChanged(); }然后在基类的getItemCount里把Footer这个位置算进去Override public int getItemCount() { return dataList.size() (hasFooter ? 1 : 0); }onCreateViewHolder里用viewType判断是否有Footer类型的布局private static final int TYPE_FOOTER Integer.MIN_VALUE 1; Override public int getItemViewType(int position) { if (hasFooter position dataList.size()) { return TYPE_FOOTER; } return super.getItemViewType(position); }加载更多的触发方式有两种选择。一种是给RecyclerView添加OnScrollListener在滚动到倒数第几个Item的时候回调外部接口去拉下一页数据。另一种是用现成的工具类。这里我推荐第一种自己手写不依赖第三方库逻辑也特别简单recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { Override public void onScrolled(NonNull RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); LinearLayoutManager layoutManager (LinearLayoutManager) recyclerView.getLayoutManager(); int lastVisibleItemPosition layoutManager.findLastVisibleItemPosition(); int totalItemCount layoutManager.getItemCount(); if (lastVisibleItemPosition totalItemCount - 2 dy 0) { if (listener ! null) { listener.onLoadMore(); } } } });threshold设成2意思是提前两个Item就触发加载这样可以有效规避快速滑到底部时网络请求还没返回用户已经看到Footer“加载中”的尴尬。4.2 空布局与Header的封装空布局的处理方案很多人会用“在Activity里根据数据源长度显示/隐藏一个空态View”的做法。这并不能说错但从复用角度讲每个页面都要写一遍这个逻辑很烦。更优雅的做法还是把空View放进RecyclerView的Adapter里当数据为空时Adapter返回一个空态布局的Item。实现起来也简单数据源长度为0并且开启了空布局开关时getItemCount返回1这个1对应的是一个空布局。这种做法的好处是状态切换不需要操作多个控件只需调用adapter.setEmptyViewVisible(true)即可整个界面布局保持一致。Header的封装思路也类似本质上都是为Adapter维护一个额外的viewType队列。不过Header相对特殊因为业务页面里Header的形态差异很大有轮播图、搜索栏、分类入口甚至还有嵌套的RecyclerView。我的建议是不要过度抽象Header更不要试图搞成“万能Header”否则你会在如何给Header传数据、如何刷新Header里的子控件这些破事上折腾很久。一句话总结做到“支持一个Header布局一个Footer加载状态”就够了需要复杂Header的业务页面可以直接通过LayoutManager.addView等方式在RecyclerView外部组合。4.3 ListView的Footer加载与EmptyViewListView这边因为历史原因本身自带set_empty_view和addFooterView这算是一个原生优势。但ListView的加载更多封装起来反而比RecyclerView更麻烦一点因为ListView的Footer一旦add进去就难移除而且onItemClick的position会算上Footer的位置不做转换就会点击错位。我建议ListView的加载更多不要用ListView原生Footer机制而是在getView里用getItemViewType判断最后一行的类型手动渲染一个加载尾布局。原理跟RecyclerView的Footer思路一致只是写法上结合BaseAdapter的getViewTypeCount和getItemViewType实现。这样能避免原生Footer引发的各种position坑。5. 封装之外性能和坑位盘点5.1 性能对比ListView与RecyclerView该怎么选既然文章标题是“RecyclerView/ListView封装及使用”那这两者的选型问题就必须说清楚。虽然说现在是RecyclerView的天下但ListView并没有完全过气尤其是一些轻量列表场景特别是数据量很小、页面不复杂的时候ListView依然可用甚至代码量更少。从封装角度看RecyclerView的扩展性完胜ListView。RecyclerView的LayoutManager机制天然支持LinearLayout、GridLayout和StaggeredGridLayout三种布局你封装一次Adapter多场景复用起来毫无压力。ListView要做横向列表就得靠HorizontalScrollViewLinearLayout硬凑不仅卡代码还难看。再从性能角度说RecyclerView全面胜出。它自带的ViewHolder复用机制是强制的而ListView的ViewHolder效率取决于开发者的自觉。加上RecyclerView提供的setHasFixedSize优化、DiffUtil自动差量更新、预加载机制等数据量大时优势很明显。我实测过一个1000条数据的列表RecyclerView配合DiffUtil的增量刷新性能比起ListView的notifyDataSetChanged全量刷新流畅度差距肉眼可见。所以结论很明确如果是新项目无脑选RecyclerView。如果是维护老项目ListView代码又改不动那就老老实实用ListView的原生方案优化性能别强行混合穿插。5.2 封装时容易踩的坑封装实践这么久有几个坑每次碰到都会让人头大这里挑典型的列一列。第一个坑是notifyDataSetChanged的乱用。很多初学封装的人不管什么状态变化都直接唤notifyDataSetChanged这会引发RecyclerView全局刷新所有可见Item都会重新绑定一遍数据代价很高。比如只改了某个Item里的一个TextView其实完全可以定位到那个ViewHolder去更新。DiffUtil就是为这个问题设计的但很多人封装时忽略了它。建议基类里留一个notifyDataChanged(List newData)的入口内部自己跑DiffUtilpublic void refreshData(ListT newData) { DiffUtil.DiffResult diffResult DiffUtil.calculateDiff(new DiffCallback(dataList, newData)); dataList.clear(); dataList.addAll(newData); diffResult.dispatchUpdatesTo(this); }DiffCallback里需要实现areItemsTheSame和areContentsTheSame两个方法前者判断是不是同一条数据后者判断内容有没有更新。这两个方法写得好不好直接影响刷新动画的准确性和性能。第二个坑是RecyclerView的ItemAnimator在刷新数据时导致Item闪烁。尤其在多选、选中态切换的场景如果数据更新后调notifyItemChanged默认动画会有一个alpha闪动效果。解决的方案很简单封装时提供一个开关允许关闭动画recyclerView.setItemAnimator(null);或者用自定义的DefaultItemAnimator并设置change动画的时长。第三个坑是ListView的convertView复用导致数据错乱。典型现象就是滚动列表时Item的图片或者文字会“跳”。根因是getView里对某个View的可见性做了控制比如设置了visibility为GONE但没有在else分支里恢复。所以封装ListView的bindData时所有条件判断的状态一定要写全尤其要保证默认状态是全的。第四个坑是RecyclerView嵌套RecyclerView的场景。最常见的是纵向RecyclerView里有一个横向滑动的子列表。如果不做处理子列表滑动时会和父列表争抢滑动事件滑动体验就很糟糕。常规做法是在外层RecyclerView的onInterceptTouchEvent里判断或者用一个自定义LayoutManager限制子列表只能横向滑动。封装层面能做的是如果子列表数据量不大直接禁用子列表的滑动用嵌套滚动配合父列表滚动LinearLayoutManager childLayoutManager new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false); childRecyclerView.setLayoutManager(childLayoutManager); childRecyclerView.setNestedScrollingEnabled(false);setNestedScrollingEnabled(false)这个方法在嵌套滚动场景下能省掉大量滑动冲突的破事。不过到了Android 12以后嵌套滚动的新机制又带来一些变化这点需要大家在各自项目里实测一下。第五个坑是BaseAdapter的getItemViewType返回值混淆。ListView里如果你不重写getItemViewType和getViewTypeCount那列表里只有一个viewType没问题。但只要出现了不同类型一定要保证viewType从0开始连续递增。RecyclerView则相反viewType越界会直接崩溃。封装时要警惕Integer开头的Type值比如用Integer.MAX_VALUE给Footer当viewType很容易在极端数据下冲突。5.3 封装的心得与建议做了这么久封装我的体会是封装不是越复杂越好而是要卡在“够用”和“好用”之间。什么叫够用就是覆盖80%的列表场景包括常规渲染、加载更多、点击事件、空态展示、多类型。什么叫好用就是团队里的新人也能在半小时内学会而不是打开基类源码还要研究半天最后高血压发作。在设计基类时我给你的建议是遵循两条原则。第一条原则是“泛型只处理数据源”。一个Adapter只负责一种类型的数据数据的类型通过泛型参数限定掉。不要在Adapter内部做类型判断去处理多种数据源否则你很快会被一堆if-else拖垮。第二条原则是“Callback要小而精”。点击回调、加载更多回调、子控件回调分开定义。不要搞一个万能Callback接口里面十几个方法子类都得空实现一堆这种设计会让基类变得非常难用。另外还有一个很实用的经验不要把业务数据模型直接当Adapter的泛型。比如你从后端拿到了一个Person对象但这个列表页要展示的信息可能包括“是否选中”、“是否加载完成”这种UI状态。把这些状态塞进Person里会让模型变得很脏。正确做法是定义一个新的ListItem类型把Person数据包一层。public class PersonListItem { public Person person; public boolean isSelected; }这样Adapter的泛型就是PersonListItem数据绑定和状态维护都干净得多。这个技巧看上去不起眼但在列表交互频繁的项目里用着真的会上瘾。6. 常见问题与排查技巧实录最后整理几个我在项目里实际遇到过的、排查起来比较耗时间的问题做成速查格式希望对你有帮助。第一个常见问题是“滚动列表时Item内容闪烁”。排查方向一是看Adapter的bindData里是否有对Item做全局的setVisibility或setAlpha操作二是看RecyclerView的ItemAnimator是否设置了change动画。可以先关掉ItemAnimator如果问题消失就是动画的锅。第二个常见问题是“Listview点击错位点击第一行弹的是另一行的内容”。排查方向检查有没有使用ListView原生的addHeaderView或者addFooterView。如果用了点击事件的position必须减去Header的数量Adapter里的getItemPosition也要做转换。第三个常见问题是“RecyclerView滑动变卡”。排查方向不要一上来就优化布局层级先看是不是在没有必要的地方使用了wrap_content作为高度或者item中是否大量使用了layout_weight。再检查onBindViewHolder中是不是做了耗时操作比如网络取图、计算属性。所有耗时操作都要异步化位置信息通过holder.getBindingAdapterPosition()获取。第四个常见问题是“EmptyView显示不出来”。这个最烦因为通常不是代码写错了而是数据结构的问题。检查Adapter里getItemCount是不是正确地考虑了emptyView的占位。如果使用了DiffUtil刷新数据也要看DiffUtil回调之后有没有正确设置emptyView的可见状态。第五个常见问题是“加载更多被重复触发”。我碰到过的一个经典场景是在onScrolled里触发加载更多后没有做时间锁/状态锁快速滑动时onScrolled会连续触发好几次。解决思路很简单用加载状态做个保护private boolean isLoadingMore false; private void triggerLoadMore() { if (isLoadingMore) { return; } isLoadingMore true; // 发起网络请求请求成功后置为false }在封装层面这需要基类提供一个setLoadingMore和isLoadingMore的开关让外部加载完业务数据后再调用一次setLoadState(STATE_DEFAULT)解锁。第六个常见问题是“多类型列表的itemViewType混乱导致崩溃”。这种崩溃通常是getItemViewType的实现里对不同数据的判断条件有重叠或者是viewType用了一个不稳定的数值比如用position当viewType。解决方法是把viewType定义成常量用switch或者Map做映射保证每个子类实现里返回的都是稳定且唯一的类型值。写在你动手之前个人认为做RecyclerView和ListView的封装不需要一上来就照着全网最全的开源框架抄比如那个几十个类、七大特性的万能适配器库那种方案维护成本极高大部分项目扛不住。你要做的是先明确自己的业务规模再根据实际情况抽取公共逻辑。我在实际项目中用的这套封装最终稳定下来的核心类也就三个左右一个CommonViewHolder、一个CommonRvAdapter、一个对应的ListView版CommonAdapter。代码加起来大概几百行却足以覆盖项目中90%以上的列表需求。如果某个页面出现了这三个类搞不定的场景那这个页面大概率也不适合用通用Adapter直接手写一个专用Adapter反而更快。遇到这种情况你完全不必纠结封装的边界就在这里公共代码抽出来复用特殊页面保持独立。以我的经验这种“七分通用、三分特化”的做法是列表封装最舒服的甜点区。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑