对RecyclerView中使用Adapter中的一点思考(RecyclerView 多布局)

为何对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只是一个小点。其实还是有很多业务代码 值得我们持续修改优化的,再牛逼的架构也要在生产中实践,所以何不从业务代码中磨练你的代码能力呢?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值