设计模式实践-适配器模式,给LinearLayout插上Adap
前言
最近app首页又改版了,不得不说现在项目越来越臃肿,什么模块都想整个入口在首页,导致首页的复杂度增加。为了适应多种类型的视图,一般我们会使用RecyclerView
,但是在首页的条目的类型越来越多,甚至存在1种type就只有一个条目,这样导致RecyclerView
的复用根本派不上用场,毕竟复用需要多个同type的条目才能产生复用。
甚至需要在RecyclerView
的item中,再嵌入一个横向滑动的RecyclerView
,或者是一个ViewPager
,再或者一个九宫格的RecyclerView
。每次滑动条目进出屏幕,都进行onBindView,再重新刷新item里面RecyclerView
的adapter和数据等,造成卡顿,在低端机尤为明显。
于是决定把嵌套层级修改一下,最外层不再是一个RecyclerView
,而是使用NestScrollView
包裹几个不同模块RecyclerView
,RecyclerView
的item如果是垂直排列的,使用LinearLayout
来垂直排列,而不是再嵌套一层RecyclerView
,好处是LinearLayout
离开屏幕不需要重新绘制,省去了不必要的开销。
思考
如果使用LinearLayout
来排列,如果需要展示多个不同类型的item,代码写起来非常麻烦,可能需要for循环不断new View
或者LayoutInflate.from(context).inflate(...)
,代码非常不优雅。而且拓展性非常差,如果突然UI设计改版,需要插入一个新的条目在中间位置,那代码就太难看了。
解决办法
RecyclerView
可以显示那么多种类型的条目,使用的是Adapter适配器模式
,把数据转换为ViewHolder,ViewHolder中存放着每个条目的Item,这样RecyclerView
只需要面对需要的View即可,终于View长什么样,用数据数据渲染,都和RecyclerView
没有关系,RecyclerView
只需要把它add进自身就可以了。
那么LinearLayout
可不可以也使用上Adapter适配器模式
呢,经过翻看RecyclerView
的源码,发现适配器模式实现起来并不难,几百行代码就可以搞定了。
- 自定义一个
ListLayout
,继承于LinearLayout
,在构造方法中,设置为垂直排列 - 设置适配器Adapter,清除自身所有子View
- 调用
adapter.onCreateViewHolder()
,获取每个条目的ViewHolder,创建条目View - 调用
adapter.onBindViewHolder()
,以条目数据,渲染条目内容 - 把条目View添加到自身
优点
使用方法和RecyclerView
基本是一模一样,所以可以轻松把一些RecyclerView
的封装库进行移植,让LinearLayout
使用起来更加方便。
推荐移植MultiType,这个库专注于RecyclerView
的多类型条目绑定,支持一对一、一对多等,代码量少,非常优雅。
移植MultiType,修改的代码量非常少,基本是2个文件即可,所以让它支持上ListView
、GridView
都非常轻松,甚至是本篇文章的ListLayout
。
我移植的是3.5.0
版本的MultiType,并非最新版。
项目地址
源码
/**
* 垂直列表布局,支持类似RecyclerView的Adapter、ViewHolder的写法,但没有它的条目复用和滚动能力
* 为了和其他滚动控件协调滚动的灵活性,所以需要滚动效果时,需要包一层NestScrollView来实现
*/
public class ListLayout extends LinearLayout {
public static final long NO_ID = -1;
/**
* 适配器
*/
private Adapter mAdapter;
/**
* 列表数据观察者,当数据notifyChange时,重新填充子View
*/
private final ListLayoutDataObserver mObserver = new ListLayoutDataObserver();
public ListLayout(Context context) {
this(context, null);
}
public ListLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ListLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
//子View垂直排列
setOrientation(LinearLayout.VERTICAL);
}
/**
* 设置适配器
*/
public void setAdapter(Adapter adapter) {
//旧的Adapter取消监听
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mObserver);
}
//新的Adapter注册监听
mAdapter = adapter;
if (adapter != null) {
adapter.registerAdapterDataObserver(mObserver);
}
//重新填充子视图
populate();
}
/**
* 获取适配器
*/
public Adapter getAdapter() {
return mAdapter;
}
/**
* 按数据填充视图
*/
private void populate() {
//清除掉之前的子View
removeAllViews();
//开始新建子View
int itemCount = mAdapter.getItemCount();
for (int position = 0; position < itemCount; position++) {
long itemId = mAdapter.getItemId(position);
int itemViewType = mAdapter.getItemViewType(position);
//创建ViewHolder
ViewHolder viewHolder = mAdapter.onCreateViewHolder(this, itemViewType);
//设置Item的布局参数
ViewGroup.LayoutParams itemLp = viewHolder.itemView.getLayoutParams();
if (itemLp == null) {
itemLp = new ListLayout.LayoutParams(
ListLayout.LayoutParams.MATCH_PARENT,
ListLayout.LayoutParams.WRAP_CONTENT
);
}
//设置相关属性
viewHolder.setAdapter(mAdapter);
viewHolder.setItemViewType(itemViewType);
viewHolder.setAdapterPosition(position);
viewHolder.setItemId(itemId);
//渲染ViewHolder,内部会渲染布局
mAdapter.onBindViewHolder(viewHolder, position);
//添加子View
addView(viewHolder.itemView, itemLp);
}
}
/**
* 列表适配器
*/
public abstract static class Adapter<VH extends ViewHolder> {
private final AdapterDataObservable mObservable = new AdapterDataObservable();
public abstract VH onCreateViewHolder(ViewGroup parent, int itemType);
public abstract void onBindViewHolder(VH holder, int position);
public int getItemViewType(int position) {
return 0;
}
public long getItemId(int position) {
return NO_ID;
}
public abstract int getItemCount();
/**
* 是否有观察者
*/
public final boolean hasObservers() {
return mObservable.hasObservers();
}
/**
* 注册列表数据观察者
*/
public void registerAdapterDataObserver(AdapterDataObserver observer) {
mObservable.registerObserver(observer);
}
/**
* 取消注册数据观察者
*/
public void unregisterAdapterDataObserver(AdapterDataObserver observer) {
mObservable.unregisterObserver(observer);
}
/**
* 通知数据更新
*/
public final void notifyDataSetChanged() {
if (hasObservers()) {
mObservable.notifyChanged();
}
}
}
public abstract static class ViewHolder {
long mItemId = NO_ID;
/**
* 适配器
*/
private Adapter adapter;
/**
* 条目View
*/
public final View itemView;
/**
* 条目类型
*/
private int mItemViewType;
/**
* 位置
*/
private int adapterPosition;
/**
* 条目Id
*/
private long itemId;
public ViewHolder(View itemView) {
this.itemView = itemView;
}
public void setItemViewType(int itemViewType) {
mItemViewType = itemViewType;
}
public int getItemViewType() {
return mItemViewType;
}
public void setAdapter(Adapter adapter) {
this.adapter = adapter;
}
public Adapter getAdapter() {
return adapter;
}
public void setAdapterPosition(int adapterPosition) {
this.adapterPosition = adapterPosition;
}
public int getAdapterPosition() {
return adapterPosition;
}
public void setItemId(long itemId) {
this.mItemId = itemId;
}
public long getItemId() {
return mItemId;
}
}
public abstract static class AdapterDataObserver {
public void onChanged() {
}
}
/**
* 列表数据观察者,当数据notifyChange时,重新填充子View
*/
private class ListLayoutDataObserver extends AdapterDataObserver {
ListLayoutDataObserver() {
}
@Override
public void onChanged() {
super.onChanged();
//数据更新,重新填充子View
populate();
}
}
/**
* 适配器被观察者
*/
private static class AdapterDataObservable extends Observable<AdapterDataObserver> {
/**
* 是否有观察者
*/
public boolean hasObservers() {
return !mObservers.isEmpty();
}
/**
* 通知观察者数据改变
*/
public void notifyChanged() {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
/**
* 被观察者
*/
private static class Observable<T> {
/**
* 观察者列表
*/
protected final List<T> mObservers = new ArrayList<T>();
/**
* 注册观察者
*/
public void registerObserver(T observer) {
if (observer == null) {
throw new IllegalArgumentException("observer不能为空");
}
synchronized (mObservers) {
if (!mObservers.contains(observer)) {
mObservers.add(observer);
}
}
}
/**
* 取消注册观察者
*/
public void unregisterObserver(T observer) {
if (observer == null) {
throw new IllegalArgumentException("observer不能为空");
}
synchronized (mObservers) {
mObservers.remove(observer);
}
}
/**
* 取消订阅所有观察者
*/
public void unregisterAll() {
synchronized (mObservers) {
mObservers.clear();
}
}
}
}