Android 对 Adapter 的 ItemType 进行封装简化

前言

之所以要把 ItemType 的封装单独拉出一篇文章,是因为前面两篇分别是针对 ListAdapter 和 RecyclerAdapter,而 ItemType 封装的思路则都是一样的,基本上没有区别可言。另外一方面,我也觉得一篇文章的篇幅还是应该有点限制,不然我自己看着都没耐心。

首先我觉得应该感谢一下鸿扬大神,很大程度上我对 ItemType 的处理参考了他的 baseAdapter 项目,虽然我仍然坚持我对某一个细节的处理,但是他的项目确实给了我不少灵感和参考。后面也会针对这一点进行对比。

正文

在上一期的 RecyclerAdapter 的基础上,做最小修改实现 ItemType 的代码大概会是这样的,也是我最早实现的方法

adapter = new RecyclerAdapter<Item>(this, dataSource) {
            @Override
            public RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                if (viewType == 0) {
                    return new RecyclerViewHolder(mInflater.inflate(layout0, parent, false));
                } else {
                    return new RecyclerViewHolder(mInflater.inflate(layout1, parent, false));
                }

            }

            @Override
            protected void onBindData(RecyclerViewHolder holder, Item data, int position) {
                if (getItemViewType(position) == 0) {
                    //TODO
                } else {
                    //TODO
                }
            }

            @Override
            protected int getItemViewType(int position, Item data) {
                return data.type;
            }
        };

可以看出,在不对 RecyclerAdapter 大改的条件下, 至少需要重写 onCreateViewHolder、onBindData、getItemViewType 三个方法,而且这还是我已经对 RecyclerAdapter 做出了一定程度的修改的情况:

1.构造器重载,可以不传入布局 id
2.添加getItemViewType(int, Item) 方法,避免手动写 dataSource.get(position) 这样的意大利面代码

我还尝试过传入一个 Map

class MultiTypeAdapter extends ListAdapter<Item> {
        public MultiTypeAdapter(Context context, List<Item> data, Map<Integer, Integer> layouts) {
            super(context, data, layouts);
        }

        @Override
        protected void setItem(View convertView, Item data, int position) {
            if (getItemViewType(position) == Item.TYPE_ONE) {
                setTypeOneItem(convertView, data, position);
            } else {
                setTypeTwoItem(convertView, data, position);
            }
        }

        private void setTypeOneItem(View convertView, Item data, int position) {
            //TODO
        }

        private void setTypeTwoItem(View convertView, Item data, int position) {
            //TODO
        }

        @Override
        protected int getItemViewType(int position, Item data) {
            return data.type;
        }
    }
        Map<Integer, Integer> layouts = new HashMap<>();
        layouts.put(Item.TYPE_ONE, R.layout.item_multi_type_one);
        layouts.put(Item.TYPE_TWO, R.layout.item_multi_type_two);

        adapter = new MultiTypeAdapter(this, dataSource, layouts);

从代码可以看出,确实减少了我们去处理什么 type 加载什么布局的工作,但是无可避免的在绑定的时候却不得不再去判断一次 type,然后做不同的事情。说好听一点,这并不符合我们期望的绑定方法只做绑定的事情,也担起了分类的责任;说简单一点,那就是这代码不优雅~

思考

在不同的 Activity 中,长得一样、逻辑也差不多的 ListView、RecyclerView 可以使用同一个 Adapter 就完成 Item 的展示。但是考虑到 ItemType 的话,事情就会复杂些许。例如:我有一个拥有类型1和类型2的 ListView 取名为 A,我有一个拥有类型2和类型3的 ListView 取名为 B,如果按照上面的做法,那么我只能写两个 Adapter 。如果把情况考虑复杂一点,A 对应123,B 对应234,那么 A 和 B 他们有一大半的 Item 类型是一致的,这意味着我们的重复代码会很多。再考虑极端一些呢?……

可是,A 和 B 的不同导致他们几乎不得不使用不同的 Adapter,那我们应该怎么办呢?

我个人有一个观点:对 Adapter 的复用实质上是为了对 Item 的复用,这里的 Item 包括布局、数据绑定、事件监听等。既然 Adapter 一定是不同的,为了实现 Item 的复用,我们是不是应该考虑把布局、数据绑定、事件监听等处理从 Adapter 中剥离出来?

于是 Delegate 类的价值就产生出来了,我们先看看鸿扬大神的代码:

/**
 * Created by zhy on 16/6/22.
 */
public interface ItemViewDelegate<T>
{

    int getItemViewLayoutId();

    boolean isForViewType(T item, int position);

    void convert(ViewHolder holder, T t, int position);

}

三个方法依次为:

1.向 Adapter 提供布局文件的 id
2.判断传入的 item 是不是自己应该处理的类型
3.绑定 holder 和数据

忽略掉 Adapter 的具体封装,使用更是简单无比

MultiItemTypeAdapter adapter = new MultiItemTypeAdapter(this,mDatas);
adapter.addItemViewDelegate(new MsgSendItemDelagate());
adapter.addItemViewDelegate(new MsgComingItemDelagate());

每种Item类型对应一个ItemViewDelegete,例如

public class MsgComingItemDelagate implements ItemViewDelegate<ChatMessage>
{

    @Override
    public int getItemViewLayoutId()
    {
        return R.layout.main_chat_from_msg;
    }

    @Override
    public boolean isForViewType(ChatMessage item, int position)
    {
        return item.isComMeg();
    }

    @Override
    public void convert(ViewHolder holder, ChatMessage chatMessage, int position)
    {
        holder.setText(R.id.chat_from_content, chatMessage.getContent());
        holder.setText(R.id.chat_from_name, chatMessage.getName());
        holder.setImageResource(R.id.chat_from_icon, chatMessage.getIcon());
    }
}

鸿扬大神的思路,大致上是在 adapter 调用 getItemType 的时候,遍历所有 delegate,调用 delegate 的 isForViewType 来判断是否是自己的类型,如果是的话就停止遍历,返回这个 delegate。

public int getItemViewType(T item, int position)
    {
        int delegatesCount = delegates.size();
        for (int i = delegatesCount - 1; i >= 0; i--)
        {
            ItemViewDelegate<T> delegate = delegates.valueAt(i);
            if (delegate.isForViewType( item, position))
            {
                return delegates.keyAt(i);
            }
        }
        throw new IllegalArgumentException(
                "No ItemViewDelegate added that matches position=" + position + " in data source");
    }

有了符合条件的 delegate,就可以将渲染 item 的任务交给它了。于是就有了上面三行代码实现一个多类型的 Adapter 如此精简的代码。
代码详情请至鸿扬大神的 github 查看:https://github.com/hongyangAndroid/baseAdapter

真·正文

请原谅我鸡蛋里面挑骨头。

虽然鸿扬大神提供的方案可以说是精简到了极致,但是在复用方面我个人是持怀疑态度的。鸿扬大神的 Delegate 是自己决定我是不是属于这个类型。回到上面 A、B 两个 ListView 的例子中,我们能保证 A、B 中使用类型2和3的条件是一样的吗?不能,所以我们只能去修改 isForViewType 方法来兼容两种条件;而当这两种条件之间无法兼容的时候,我们只能让 A、B 中的同一种类型使用不同的 Delegate 类,即便他们长得一样,交互也一样。

即:当业务发生变化的时候,我可能会需要去修改 Delegate 类,或者增加仅有 isForViewType 实现不同的类。在比较苛刻的条件下,这并没有真正的做到 Item 复用。

而我所期望的 Delegate,什么时候用它,什么条件下用它,这不应该由它自己去决定,因为 Delegate 并不懂业务,我也不希望它和业务耦合在一起,我只是希望它能够根据传入的数据对象执行绑定工作而已。

所以我的 Delegate 是这样的:

public interface AdapterDelegate<T> {
    int getLayoutId();
    void bind(RecyclerViewHolder holder, T data, int position);
}

因为 delegate 不负责类型的判断,所以使用时稍微复杂一些:

adapter = new MultiTypeRecyclerAdapter<MultiTypeItem>(this, dataSource) {
            @Override
            protected int getItemViewType(MultiTypeItem data) {
                return data.type;
            }
        };
        adapter.addDelegate(MultiTypeItem.TYPE_ONE, new TypeOneDelegate())
                .addDelegate(MultiTypeItem.TYPE_TWO, new TypeTwoDelegate());

首先,adapter 添加 delegate 的时候是以键值对的形式添加的,可以指定 delegate 去处理哪一种类型;
其次,adapter 需要重写一个 getItemViewType 方法,告诉 adapter 判断类型的依据。

这样子, adapter 就知道了什么时候去使用哪个 delegate。而当业务发生变化,但是 UI 没改的情况下,我不需要改动任何一个 delegate,而是改 adapter 定义的代码。

附上 MultiTypeRecyclerAdapter 的代码:

public class MultiTypeRecyclerAdapter<T> extends RecyclerView.Adapter<RecyclerViewHolder> {

    protected Context mContext;
    protected List<T> mData;
    protected LayoutInflater mInflater;
    protected SparseArray<AdapterDelegate<T>> delegates = new SparseArray<>();

    protected int layoutRes;

    public MultiTypeRecyclerAdapter(Context context) {
        this.mData = new ArrayList<>();
        this.mContext = context;
        this.mInflater = LayoutInflater.from(mContext);
    }

    public MultiTypeRecyclerAdapter(Context context, List<T> data) {
        this.mData = data;
        this.mContext = context;
        this.mInflater = LayoutInflater.from(mContext);
    }

    public MultiTypeRecyclerAdapter(Context context, List<T> data, int layoutRes) {
        this.mData = data;
        this.mContext = context;
        this.mInflater = LayoutInflater.from(mContext);
        this.layoutRes = layoutRes;
    }

    public MultiTypeRecyclerAdapter(Context context, List<T> data, AdapterDelegate<T> delegate) {
        this.mData = data;
        this.mContext = context;
        this.mInflater = LayoutInflater.from(mContext);
        delegates.put(0, delegate);
    }

    public MultiTypeRecyclerAdapter<T> addDelegate(int type, AdapterDelegate<T> delegate) {
        delegates.put(type, delegate);
        return this;
    }

    public MultiTypeRecyclerAdapter<T> addDelegate(AdapterDelegate<T> delegate) {
        return addDelegate(0, delegate);
    }

    public void refresh(List<T> data) {
        try {
            this.mData = data;
            notifyDataSetChanged();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (delegates.size() == 0) {
            return new RecyclerViewHolder(mInflater.inflate(layoutRes,
                    parent,
                    false));
        } else {
            return new RecyclerViewHolder(mInflater.inflate(delegates.get(viewType).getLayoutId(),
                    parent,
                    false));
        }
    }

    @Override
    public void onBindViewHolder(RecyclerViewHolder holder, int position) {
        if (delegates.size() == 0) {
            bind(holder, mData.get(position), position);
        } else {
            AdapterDelegate<T> delegate = delegates.get(getItemViewType(position));
            delegate.bind(holder, mData.get(position), position);
        }
    }

    @Override
    public int getItemCount() {
        return mData.size();
    }

    @Override
    public int getItemViewType(int position) {
        return getItemViewType(mData.get(position));
    }

    /**
     * 由子类处理,默认返回 0
     * @param data
     * @return
     */
    protected int getItemViewType(T data) {
        return 0;
    }

    /**
     * 单类型时子类需实现的方法
     * 处理绑定 view
     * @param holder
     * @param data
     * @param position
     */
    protected void bind(RecyclerViewHolder holder, T data, int position) {

    }
}

最后,再次感谢鸿扬大神,他的 baseAdapter 项目更为成熟,包含的功能也更多,让我受益匪浅。

代码详见:https://github.com/neverwoodsS/zy-open

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值