为何对adapter多布局的写法有思考?
对于电商app来说,一个列表页面,会有多种布局,基本上都是在5个布局以上,且业务逻辑各自不同,非常复杂,导致的结果 就是adapter代码过于长了,十分不优雅。例如:
可以看到我实际项目中的adapter代码都到了2000多行,这还是较少的一个页面,多的甚至3000多行的都有。
这么做的危害,以及必须进行重构的原因:
-
代码不易维护,这种几千行的代码,要你去修改一个bug,相信任何一个人都会抖抖索索,怕搞出事情,改错一个地方就全错
-
不支持代码复用。比如我们有一个tab下面要用的a布局 和 另外一tab下的b 布局在高保真上表现完全一致,此时按照老的 代码只能拷贝一份过去,很麻烦,以后要修改可能要修改两处。新的adapter我们希望 可以不费吹灰之力就可以利用老的布局 代码,让我们的新页面可以支持老的布局展示
-
ui层和接口层没有做分离,adapter里面的bean和服务器接口返回的bean 是同一个class。这样的危害就在于,服务器一旦 对接口有改动,ui层adapter代码就得大改。且一旦碰到 两个不同界面 不同接口 展示的是同一种样式的时候,拷贝代码 都无法解决了,不但拷贝代码,而且拷贝完毕以后还要修改代码为新的bean。实在是太麻烦。并且如果服务端同学比较忙 接口一开始不能及时提供的话,你的adapter代码里也会比较吃力,因为bean还没确定下来。
新的adaper希望达成什么目标?
- adapter代码能有好的分层,不同的样式最好对应不同的class,方便修改。
- 对于不同样式的class代码来说 要能迅速的适配到其他不同接口不同的页面中。也就是说复用性要好。
怎么解决我们想要解决的问题?
记住计算机界有一个名言是:任何计算机界中的问题都可以通过引入一个中间层来解决
我们可以看看老的adapter代码是怎么解决多布局问题的
首先判断到底是哪一种viewtype
然后通过这个值来加载不同的layout布局文件
最后再bindviewholder里面设置我们的数据(对应不同的viewholder)
实际上,这里面我们用到的主要就是两个类,一个是RecyclerView.Adapter,一个是RecyclerView.ViewHolder
那么下面我们就通过引入一个中间件的方式,来解决我们前文中提到的问题。
SmartRecylerAdapter
我们首先对viewholder做一次扩充:
package com.example.a16040657.adaptertest;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* Created by 16040657 on 2018/4/2.
*/
public abstract class SmartRecylerItem<T> extends RecyclerView.ViewHolder {
private T data;
/**
* 这个构造函数其实并不会直接调用
* 是为了给另外一个真正使用的构造函数使用
*
* @param itemView
*/
public SmartRecylerItem(View itemView) {
super(itemView);
}
/**
* 真正使用的构造函数是这个
*
* @param itemLayoutId
* @param parent
*/
public SmartRecylerItem(int itemLayoutId, ViewGroup parent) {
this(LayoutInflater.from(parent.getContext()).inflate(itemLayoutId, parent, false));
}
/**
* 这个方法不多说了
* @param id
* @return
*/
public View findViewById(int id) {
return itemView.findViewById(id);
}
/**
* adapter代码里 会直接调用这个bind方法
*
* @param position
* @param data
*/
public void bindData(int position, T data) {
this.data = data;
onSetData(position, data);
}
/**
* 设置数据 这个方法交给子类来实现
*
* @param position 位置
* @param data 数据
*/
protected abstract void onSetData(int position, T data);
/**
* 这个方法就是在adapter里面的oncreateviewholder里面调用,交给子类实现。
* 只执行一次,通常我们可以在这里面设置一下view的尺寸,以及复杂嵌套布局的一些属性等等
*
* @param context
*/
protected abstract void configView(Context context);
/**
* 这个方法我们用来重写findviewbyid 之类的方法
*/
protected abstract void findViews();
}
复制代码
然后我们对adapter做一次扩充:
package com.example.a16040657.adaptertest;
import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
/**
* Created by 16040657 on 2018/4/2.
*/
public class SmartRecylerAdapter extends RecyclerView.Adapter {
public SmartRecylerAdapter(List<Object> mDataList) {
this.mDataList = mDataList;
}
//存放所有数据的list
private List<Object> mDataList;
/**
* 对于多种布局来说,itemType的值都是从0开始,布局越多 这个值越大 默认为0
*/
private int itemTypeIndex = 0;
/**
* 这个值就是用来保存中间件的,你要适配那种布局 就增加一个中间件到这个list中。
*/
private ArrayList<SmartRecylerItemMiddleware> smartRecylerItemMiddlewareArrayList;
/**
* 实际上是hashmap,key 是 itemType的值,value 就是我们的中间件。
* 用SparseArray是为了性能考虑,key 为int值的话 SparseArray 比hashmap效率要高
*/
private SparseArray<Object> smartMiddleHashMap;
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//如果不使用中间件的话,这里熟悉的代码应该是根据不同的viewtype的值
//来返回不同的layout布局
//这里我们就直接根据viewtype的值 来取出我们对应的中间件
Object item = smartMiddleHashMap.get(viewType);
// Item
if (item instanceof SmartRecylerItemMiddleware) {
SmartRecylerItemMiddleware itemFactory = (SmartRecylerItemMiddleware) item;
return itemFactory.dispatchCreatesSmartItem(parent);
}
throw new IllegalStateException("这边异常随便你自己怎么写吧。");
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof SmartRecylerItem) {
((SmartRecylerItem) holder).bindData(position, getItem(position));
}
}
public void addMiddleWare(SmartRecylerItemMiddleware smartRecylerItemMiddleware) {
smartRecylerItemMiddleware.setItemType(itemTypeIndex++);
if (smartMiddleHashMap == null) {
smartMiddleHashMap = new SparseArray<Object>();
}
smartMiddleHashMap.put(smartRecylerItemMiddleware.getItemType(), smartRecylerItemMiddleware);
if (smartRecylerItemMiddlewareArrayList == null) {
smartRecylerItemMiddlewareArrayList = new ArrayList<SmartRecylerItemMiddleware>(5);
}
smartRecylerItemMiddlewareArrayList.add(smartRecylerItemMiddleware);
}
@Override
public int getItemCount() {
return mDataList.size();
}
@Override
public int getItemViewType(int position) {
//遍历一下中间件 看看是否有对应的中间件可以使用,如果没有就抛出异常
SmartRecylerItemMiddleware smartRecylerItemMiddleware;
for (int w = 0, size = smartRecylerItemMiddlewareArrayList.size(); w < size; w++) {
smartRecylerItemMiddleware = smartRecylerItemMiddlewareArrayList.get(w);
if (smartRecylerItemMiddleware.isTarget(mDataList.get(position))) {
return smartRecylerItemMiddleware.getItemType();
}
}
//你当然可以在异常信息里写出更详细的信息。。。。
throw new IllegalStateException("Didn't find suitable Middleware.");
}
/**
* 返回list中的bean
*
* @param position
* @return
*/
public Object getItem(int position) {
// 数据
return mDataList.get(position);
}
}
复制代码
最后看下我们的中间件代码:
package com.example.a16040657.adaptertest;
import android.view.ViewGroup;
/**
* 中间件,这个类的主要作用就是桥接adapter和viewholder使用
*/
public abstract class SmartRecylerItemMiddleware<T extends SmartRecylerItem> {
/**
* 这个值就是在多布局的情况下,返回的itemType的值,我们在中间件里储存一下这个值
* 方便使用
*/
private int itemType;
/**
* 匹配数据
*
* @param data 待匹配的数据,通常是使用instanceof关键字匹配类型
* @return 如果返回true,Adapter将会使用此ItemFactory来处理当前这条数据
*/
public abstract boolean isTarget(Object data);
/**
* 创建Item
*/
public abstract T createSmartItem(ViewGroup parent);
public int getItemType() {
return itemType;
}
public void setItemType(int itemType) {
this.itemType = itemType;
}
protected T dispatchCreatesSmartItem(ViewGroup parent) {
T item = createSmartItem(parent);
item.findViews();
item.configView(parent.getContext());
return item;
}
}
复制代码
注释已经写的很详细了,其实就是在adapter和viewholder里面架起了一座桥梁,对使用者来说不用再关心oncrateviewholder
onbind 以及getItemviewtype这种方法了,只用专心写业务逻辑就好。
看看使用效果:
我们假设列表页就只支持两种布局效果(毕竟是demo不搞的太复杂了) ,那就是要写2个中间件
package com.example.a16040657.adaptertest;
import android.content.Context;
import android.view.ViewGroup;
import android.widget.TextView;
/**
* Created by 16040657 on 2018/3/23.
*/
public class StudentItemMiddleWare extends SmartRecylerItemMiddleware<StudentItemMiddleWare.StudentItem> {
@Override
public boolean isTarget(Object data) {
return data instanceof Student;
}
@Override
public StudentItem createSmartItem(ViewGroup parent) {
return new StudentItem(R.layout.student_item_layout, parent); }
public class StudentItem extends SmartRecylerItem<Student> {
TextView nameTv;
TextView name2Tv;
public StudentItem(int itemLayoutId, ViewGroup parent) {
super(itemLayoutId, parent);
}
@Override
protected void onSetData(int position, Student student) {
nameTv.setText(student.name);
name2Tv.setText(student.age);
}
@Override
protected void configView(Context context) {
}
@Override
protected void findViews() {
nameTv = (TextView) findViewById(R.id.tv);
name2Tv = (TextView) findViewById(R.id.tv2);
}
}
}
复制代码
package com.example.a16040657.adaptertest;
import android.content.Context;
import android.view.ViewGroup;
import android.widget.TextView;
/**
* Created by 16040657 on 2018/3/23.
*/
public class UserItemFactoryMiddleWare extends SmartRecylerItemMiddleware<UserItemFactoryMiddleWare.UserItem> {
@Override
public boolean isTarget(Object data) {
return data instanceof User;
}
@Override
public UserItem createSmartItem(ViewGroup parent) {
return new UserItem(R.layout.user_item_layout, parent);
}
public class UserItem extends SmartRecylerItem<User> {
TextView nameTv;
TextView name2Tv;
public UserItem(int itemLayoutId, ViewGroup parent) {
super(itemLayoutId, parent);
}
@Override
protected void onSetData(int position, User user) {
nameTv.setText(user.name);
name2Tv.setText(user.age);
}
@Override
protected void configView(Context context) {
}
@Override
protected void findViews() {
nameTv = (TextView) findViewById(R.id.tv);
name2Tv = (TextView) findViewById(R.id.tv2);
}
}
}
复制代码
recyclerView = (RecyclerView) findViewById(R.id.rv);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
List<Object> dataList = new ArrayList<>();
dataList.add(new User("wuyueu", "wuyueu2"));
dataList.add(new Student("wuyueus", "wuyues2"));
dataList.add(new User("wuyueu", "wuyueu2"));
dataList.add(new Student("wuyueus", "wuyues2"));
dataList.add(new User("wuyueu", "wuyueu2"));
dataList.add(new User("wuyueu", "wuyueu2"));
dataList.add(new Student("wuyueus", "wuyues2"));
dataList.add(new User("wuyueu", "wuyueu2"));
dataList.add(new Student("wuyueus", "wuyues2"));
dataList.add(new User("wuyueu", "wuyueu2"));
dataList.add(new Student("wuyueus", "wuyues2"));
dataList.add(new User("wuyueu", "wuyueu2"));
dataList.add(new Student("wuyueus", "wuyues2"));
dataList.add(new User("wuyueu", "wuyueu2"));
dataList.add(new User("wuyueu", "wuyueu2"));
dataList.add(new Student("wuyueus", "wuyues2"));
dataList.add(new User("wuyueu", "wuyueu2"));
dataList.add(new Student("wuyueus", "wuyues2"));
dataList.add(new User("wuyueu", "wuyueu2"));
dataList.add(new Student("wuyueus", "wuyues2"));
dataList.add(new User("wuyueu", "wuyueu2"));
dataList.add(new Student("wuyueus", "wuyues2"));
dataList.add(new User("wuyueu", "wuyueu2"));
dataList.add(new User("wuyueu", "wuyueu2"));
dataList.add(new Student("wuyueus", "wuyues2"));
dataList.add(new User("wuyueu", "wuyueu2"));
dataList.add(new Student("wuyueus", "wuyues2"));
SmartRecylerAdapter smartRecylerAdapter=new SmartRecylerAdapter(dataList);
//这边代码很好理解吧,以后其他页面如果也要用这边的布局代码 直接add 这个我们的中间件就可以了
//其他的都不需要关心,反正我们的adapter bean是object。其他页面只要自己把服务器返回的bean
//转成我们这里的bean即可。且不同的布局对应着不同的中间件,即使是其他组的人来修改你的代码
//也不怕埋坑啦。逻辑十分清晰。
smartRecylerAdapter.addMiddleWare(new StudentItemMiddleWare());
smartRecylerAdapter.addMiddleWare(new UserItemFactoryMiddleWare());
recyclerView.setAdapter(smartRecylerAdapter);
复制代码
最后看一下效果(明显看出来是加载了不同的布局哈):
感想
很多同学都觉得写业务代码提高不了技术能力,其实不是的,当你觉得你的业务代码各种别扭,各种需要复制粘贴, 各种改一点需求都要很多工作量的时候,就是时候重构你的代码了,adapter只是一个小点。其实还是有很多业务代码 值得我们持续修改优化的,再牛逼的架构也要在生产中实践,所以何不从业务代码中磨练你的代码能力呢?