XCoreRecyclerAdapter:更好用的Adapter For RecyclerView

XCoreRecyclerAdapter:一种通用的Adapter For RecyclerView

背景

每当我们使用RecyclerView写一个列表的时候,都需要写类似的如下代码:


    ...
    mTestRv = (RecyclerView) view.findViewById(R.id.test_home_rv);
    mTestRecyclerAdapter = new TestRecyclerAdapter(context);
    mLinearLayoutManager = new LinearLayoutManager(context);
    mTestRv.setLayoutManager(mLinearLayoutManager);
    mTestRv.setAdapter(mTestRecyclerAdapter);
    ...

那么,我们一般必不可少的是写一个Adapter,比如,为了支持多种类型,我们需要重写getItemViewType、onCreateViewHolder、onBindViewHolder等方法。比如:


public class TestRecyclerAdapter extends RecyclerView.Adapter<BaseViewHolder> {

    ...

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = null;
        switch (viewType) {
            case Type.TYPE_HEADER:
                view = LayoutInflater.from(mContext).inflate(R.layout.card_header,
                        parent, false);
                return new HeaderViewHolder(view);
            case Type.TYPE_HIS_HEAD:
                view = LayoutInflater.from(mContext).inflate(R.layout.his_header,
                        parent, false);
                return new HeaderHisViewHolder(view);
            ...
            default:
                break;
        }
        return new HeaderViewHolder(new RelativeLayout(mContext));
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        switch (getItemViewType(position)) {
            case Type.TYPE_HEADER:
                HeaderViewHolder headerViewHolder = (HeaderViewHolder) holder;
                headerViewHolder.bindView(mDataSet.get(position));
                break;
            case Type.TYPE_HIS_HEAD:
                HeaderHisViewHolder headerHisViewHolder = (HeaderHisViewHolder) holder;
                headerHisViewHolder.bindView(mDataSet.get(position), mOnHeaderItemClickListener);
                break;
            ...
            default:
                break;
        }
    }

    @Override
    public int getItemViewType(int position) {
        Data data = mDateSet.get(position);
        String type = data.getType();
        if("xxx".equals(type)){
            return Type.TYPE_HEADER;
        }else if("xxxxx".equals(type)){
            return Type.TYPE_HIS_HEAD;
        }else if(){
            ...
        }
        ...
    }

}

按照上述写法,每当我们需要增加一种类型的时候,都需要修改TestRecyclerAdapter的代码,在onCreateViewHolder等方法中添加对应的代码。后来大家觉得这个挺烦的,然后就把onCreateViewHolder中的switch语句放到工厂类当中,这一定层度上缓解了修改Adapter的频率。那么,我们能不能写一个相对通用的Adapter呢?

封装通用的插件式Adapter

我们希望做到如下目标:

  • 1).Adapter通用化,无需每次新建Adapter
  • 2).Item(Cell)的组件是插件式的,解耦,并可复用

用代码描述为:


    ...
    //创建通用的Adapter
    mXCoreRecyclerAdapter = new XCoreRecyclerAdapter(context);
    //Adapter注册item组件
    mXCoreRecyclerAdapter.registerItemUIComponent(new TodoItemComponent())
                .registerItemUIComponent(new TestItemComponent());
    //设置数据源,完成展示
    mXCoreRecyclerAdapter.setDataSet(List<IDataComponent> dataSet)
    ...
    

插件式的通用XCoreRecyclerAdapter的原理图如下:

XCoreRecyclerAdapter:更好用的Adapter For RecyclerView

每当我们需要添加一个新的类型时,只需要一下几步:

  • 1.新建一个TodoItemUIComponent组件,继承自XCoreItemUIComponent

    实现对应的方法;
  • 2.注册新增的组件TodoItemUIComponent

    调用XCoreRecyclerAdapter的registerItemUIComponent方法即可注册。
  • 3.数据源实现IDataComponent接口

    getViewType和组件TodoItemUIComponent的getViewType匹配即可;

这样,每次新增一种新的类型的Item时,无需修改Adapter的代码。并且,Item的组件是可复用的。

那么具体怎么设计的呢?

数据源

首先对数据源进行抽象:在Adapter列表中,需要根据数据源Data的不同类型,选择不同的ViewHolder。所以,数据源必须有一个getType的方法。


    /**
     * 数据源必须实现的接口
     */
    public interface IDataComponent {
        String getViewType();
    }

Item组件 ItemUIComponent

ItemUIComponent组件负责某一类type的管理。为了做到插件式,它必须实现的方法有:

  • onCreateView方法

    该方法是用于生成Item的根View的。当Adapter调用onCreateViewHolder时,该方法会被调用。注意,子View的初始化,不要写在onCreateView中。需要写在onViewCreated方法中。
  • onViewCreated方法

    创建完ItemComponent之后,就会立即回调该方法,View的初始化请写在这里。
  • getViewType方法

    该方法是为了和数据源进行关联的,数据源的getViewType方法和组件的getViewType方法的值一致时,即完成匹配。
  • bindView方法

    该方法会在Adapter回调onBindViewHolder

    public class TodoItemComponent extends XCoreItemUIComponent {

    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container) {
        //TODO 在这里指定item组件的布局
        return inflater.inflate(R.layout.todo_item_layout,container,false);
    }

    @Override
    public void onViewCreated(View view) {
        //TODO 在这里写View初始化
        view.findViewById();
        ...
    }

    @Override
    public String getViewType() {
        //与数据源的getViewType关联
        return TodoItemComponent.class.getSimpleName();
    }

    @Override
    public void bindView(IXCoreComponent coreComponent,
                         XCoreRecyclerAdapter coreRecyclerAdapter,
                         XCoreRecyclerAdapter.IDataComponent data,
                         int pos) {
        //TODO 在这里写绑定逻辑
    }
}

数据源和Item组件都已经定义好,那么,核心的XCoreRecyclerAdapter源码应该怎么做呢?

XCoreRecyclerAdapter源码


package com.github.nuptboyzhb.xcore.adapter;


import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.github.nuptboyzhb.xcore.components.IXCoreComponent;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @version mochuan.zhb on 2016/8/9.
 * @Author Zheng Haibo
 * @Blog github.com/nuptboyzhb
 * @Company Alibaba Group
 * @Description 通用的Adapter for RecyclerView
 */
public class XCoreRecyclerAdapter extends RecyclerView.Adapter<XCoreRecyclerAdapter.CommonViewHolder> {

    private IXCoreComponent mIXCoreComponent;//外层UI组件
    private List<IDataComponent> mDataSet = new ArrayList<IDataComponent>();//数据源
    private SparseArray<XCoreItemUIComponent> mConfigurationSparseArray = new SparseArray<XCoreItemUIComponent>();//集合:type对应的Item组件
    private Map<String, Integer> mViewTypeMap = new HashMap<String, Integer>();//type的string和int映射

    public XCoreRecyclerAdapter() {

    }

    public XCoreRecyclerAdapter(IXCoreComponent mIXCoreComponent) {
        this.mIXCoreComponent = mIXCoreComponent;
    }

    @Override
    public CommonViewHolder onCreateViewHolder(ViewGroup viewGroup, int type) {
        //根据数据类型获取对应的item组件
        XCoreItemUIComponent XCoreItemUIComponent = mConfigurationSparseArray.get(type);
        if (XCoreItemUIComponent == null) {//如果未获取到,展示空item
            return getDefaultViewHolder(viewGroup.getContext());
        }
        try {
            //使用item组件创建一个新的View
            View view = XCoreItemUIComponent.onCreateView(LayoutInflater.from(viewGroup.getContext()), viewGroup);
            //使用View构建内部的ViewHolder
            CommonViewHolder commonViewHolder = new CommonViewHolder(view);
            //创建一个新的Item组件
            XCoreItemUIComponent realItem = XCoreItemUIComponent.getClass().newInstance();
            //将创建的View设置到真是的Item组件中
            realItem.setItemView(view);
            //使用内部ViewHolder
            commonViewHolder.setXCoreItemUIComponent(realItem);
            return commonViewHolder;
        } catch (Throwable t) {
            t.printStackTrace();
        }
        return getDefaultViewHolder(viewGroup.getContext());
    }

    @Override
    public void onBindViewHolder(CommonViewHolder baseViewHolder, int pos) {
        baseViewHolder.bindView(mIXCoreComponent, this, mDataSet.get(pos), pos);
    }

    @Override
    public int getItemViewType(int position) {
        IDataComponent item = mDataSet.get(position);
        Integer integer = mViewTypeMap.get(item.getViewType());
        if (integer == null) {
            return -1;
        }
        return integer;
    }

    @Override
    public int getItemCount() {
        if (mDataSet != null) {
            return mDataSet.size();
        }
        return 0;
    }

    /**
     * 获取Adapter的数据源
     *
     * @return
     */
    public List<IDataComponent> getDataSet() {
        return mDataSet;
    }

    /**
     * 设置Adapter的数据源
     *
     * @param dataSet
     */
    public void setDataSet(List<IDataComponent> dataSet) {
        this.mDataSet = dataSet;
        notifyDataSetChanged();
    }

    /**
     * get the error view holder
     *
     * @param context
     * @return
     */
    protected CommonViewHolder getDefaultViewHolder(Context context) {
        return new CommonViewHolder(new View(context));
    }

    /**
     * get the unique int type
     *
     * @param name
     * @return
     */
    private int getUniqueIntType(String name) {
        if (TextUtils.isEmpty(name)) {
            return -1;
        }
        int type = name.hashCode();
        while (true) {
            XCoreItemUIComponent old = mConfigurationSparseArray.get(type);
            if (old != null) {
                String oldName = old.getViewType();
                if (!name.equals(oldName)) {
                    type = type + 1;
                } else {
                    return type;
                }
            } else {
                return type;
            }
        }
    }

    /**
     * 注册Item组件
     *
     * @param XCoreItemUIComponent
     * @return
     */
    public XCoreRecyclerAdapter registerItemUIComponent(XCoreItemUIComponent XCoreItemUIComponent) {
        if (XCoreItemUIComponent == null || TextUtils.isEmpty(XCoreItemUIComponent.getViewType())) {
            return this;
        }
        int viewTypeInt = getUniqueIntType(XCoreItemUIComponent.getViewType());
        mViewTypeMap.put(XCoreItemUIComponent.getViewType(), viewTypeInt);
        mConfigurationSparseArray.put(viewTypeInt, XCoreItemUIComponent);
        return this;
    }

    /**
     * 注销配置
     *
     * @param XCoreItemUIComponent
     * @return
     */
    public XCoreRecyclerAdapter unregisterItemUIComponent(XCoreItemUIComponent XCoreItemUIComponent) {
        if (XCoreItemUIComponent == null || TextUtils.isEmpty(XCoreItemUIComponent.getViewType())) {
            return this;
        }
        int index = mConfigurationSparseArray.indexOfValue(XCoreItemUIComponent);
        if (index == -1) {
            return this;
        }
        mConfigurationSparseArray.remove(index);
        return this;
    }

    /**
     * 数据源必须实现的接口
     */
    public interface IDataComponent {
        String getViewType();
    }

    /**
     * 使用CommonViewHolder代理XCoreItemUIComponent组件
     */
    public static class CommonViewHolder extends RecyclerView.ViewHolder {

        private XCoreItemUIComponent XCoreItemUIComponent;

        public void setXCoreItemUIComponent(XCoreItemUIComponent XCoreItemUIComponent) {
            this.XCoreItemUIComponent = XCoreItemUIComponent;
        }

        public XCoreItemUIComponent getXCoreItemUIComponent() {
            return XCoreItemUIComponent;
        }

        public CommonViewHolder(View itemView) {
            super(itemView);
        }

        public void bindView(IXCoreComponent mIXCoreComponent,
                             XCoreRecyclerAdapter XCoreRecyclerAdapter,
                             XCoreRecyclerAdapter.IDataComponent data,
                             int pos) {
            if (XCoreItemUIComponent == null) {
                return;
            }
            XCoreItemUIComponent.bindView(mIXCoreComponent, XCoreRecyclerAdapter, data
                    , pos);
        }

        public void onViewDetachedFromWindow() {
            if (XCoreItemUIComponent != null) {
                XCoreItemUIComponent.onViewDetachedFromWindow();
            }
        }
    }

    @Override
    public void onViewDetachedFromWindow(CommonViewHolder holder) {
        super.onViewDetachedFromWindow(holder);
        holder.onViewDetachedFromWindow();
    }
}

XCoreItemUIComponent源码


package com.github.nuptboyzhb.xcore.adapter;

import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.github.nuptboyzhb.xcore.components.IXCoreComponent;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @version mochuan.zhb on 2016/8/12.
 * @Author Zheng Haibo
 * @Blog github.com/nuptboyzhb
 * @Company Alibaba Group
 * @Description item component 抽象类
 */
public abstract class XCoreItemUIComponent implements IXCoreComponent {

    /**
     * 创建View
     *
     * @param inflater
     * @param container
     * @return
     */
    public abstract View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container);

    /**
     * 在此做View的初始化操作
     *
     * @param view
     */
    public abstract void onViewCreated(View view);

    /**
     * item组件支持的ViewType,与IDataComponent的getViewType对应
     *
     * @return
     */
    public abstract String getViewType();

    /**
     * 绑定数据-频繁回调
     *
     * @param coreComponent       外出UI组件
     * @param coreRecyclerAdapter Adapter
     * @param data                对应的数据源
     * @param pos                 item所在列表的位置
     */
    public abstract void bindView(IXCoreComponent coreComponent,
                                  XCoreRecyclerAdapter coreRecyclerAdapter,
                                  XCoreRecyclerAdapter.IDataComponent data,
                                  int pos);

    /**
     * item组件销毁时调用
     */
    public abstract void onViewDetachedFromWindow();

    //必须有无参数的构造函数
    public View itemView;

    public void setItemView(View itemView) {
        this.itemView = itemView;
        onViewCreated(itemView);
    }
    
}

后续

本篇博客主要是介绍通用的XCoreRecyclerAdapter引擎,极大提高了Adapter的复用率,让开发集中把精力集中在必要的业务代码上,提高开发效率。另外,与XCoreRecyclerAdapter引擎相配套的还有UI组件化、数据控制框架XCoreRedux、数据绑定等。请参考下一遍博客:《Android Redux实践与UI组件化:XCoreRedux框架》(ing...)

上一篇:linux centos bash -lt 4.4 被攻击乱码处理方案升级bash5, 将字符编码设置为中文


下一篇:XCoreRedux框架:Android UI组件化与Redux实践