XCoreRecyclerAdapter: a better adapter for RecyclerView - Alibaba Cloud Developer Forums: Cloud Discussion Forums

Dave
Assistant Engineer
Assistant Engineer
  • UID627
  • Fans3
  • Follows0
  • Posts55
Reads:2093Replies:0

[Share]XCoreRecyclerAdapter: a better adapter for RecyclerView

Created#
More Posted time:Nov 1, 2016 16:19 PM
Background
When using RecyclerView to write a list, we always need to write code similar to the following:
...
    mTestRv = (RecyclerView) view.findViewById(R.id.test_home_rv);
    mTestRecyclerAdapter = new TestRecyclerAdapter(context);
    mLinearLayoutManager = new LinearLayoutManager(context);
    mTestRv.setLayoutManager(mLinearLayoutManager);
    mTestRv.setAdapter(mTestRecyclerAdapter);
    ...


So the adapter is necessary. For example, in order to support multiple types, we have to override the getItemViewType, onCreateViewHolder and onBindViewHolder methods among others. For example:
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(){
            ...
        }
        ...
    }

}


From the writing above, we need to modify the code for TestRecyclerAdapter and add the corresponding code in the onCreateViewHolder method every time when we need to add a type. People later found this approach too annoying and put the switch statement of the onCreateViewHolder method to the factory class, which reduced the frequency to modify the adapter to some extent. Can we write a relatively generic adapter?
Encapsulate the generic plug-in adapter
We hope to achieve the following:
• 1) Make the adapter generic so that you do not need to create an adapter every time
• 2) Adopt plug-in Item(Cell) components that are decoupled and reusable
The code will be:
...
    //Create a generic adapter
    mXCoreRecyclerAdapter = new XCoreRecyclerAdapter(context);
    //The adapter registers the item components
    mXCoreRecyclerAdapter.registerItemUIComponent(new TodoItemComponent())
                .registerItemUIComponent(new TestItemComponent());
    //Set the data source and complete the demo
    mXCoreRecyclerAdapter.setDataSet(List<IDataComponent> dataSet)
    ...


The diagram of the principle of the generic plug-in XCoreRecyclerAdapter is as follows:


Every time you need to add a new type, you only need to perform the following steps:
• 1. Create a new TodoItemUIComponent inherited from XCoreItemUIComponent
Implement the corresponding methods;
• 2. Register the newly added TodoItemUIComponent
Call the registerItemUIComponent method of XCoreRecyclerAdapter for the registration.
• 3. Implement IDataComponent interface for the data source
The getViewType should match that of the TodoItemUIComponent;
In this way, you don't need to modify the adapter code to add a new type of item. In addition, the item components are reusable.
So how should it be designed?
Data source
First, abstract the data source: in the adapter list, you need to select different ViewHolders for different types of data in the data source. So the data source must have a getType method.
/**
     * Interfaces that must be implemented by the data source
     */
    public interface IDataComponent {
        String getViewType();
    }


ItemUIComponent
ItemUIComponent manages a certain type. It must implement the following methods to become a plug-in:
• onCreateView method
This method is used to generate the root View of the Item. When the adapter calls the onCreateViewHolder method, this method will be called. Note: Do not write the initialization of the sub View in onCreateView. Instead, you should write it in the onViewCreated method.
• onViewCreated method
After the ItemComponent is created, it will call this method immediately. You should write the initialization of View here.
• getViewType method
This method serves for association with the data source. When the getViewType method of the data source and the getViewType method of the component are consistent, the matching is complete.
• bindView method
This method will call back the onBindViewHolder method in the adapter.
public class TodoItemComponent extends XCoreItemUIComponent {

    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container) {
        //TODO Specify the layout of the item component
        return inflater.inflate(R.layout.todo_item_layout,container,false);
    }

    @Override
    public void onViewCreated(View view) {
        //TODO Write the initialization of the View here
        view.findViewById();
        ...
    }

    @Override
    public String getViewType() {
        //Associate with the getViewType method of the data source
        return TodoItemComponent.class.getSimpleName();
    }

    @Override
    public void bindView(IXCoreComponent coreComponent,
                         XCoreRecyclerAdapter coreRecyclerAdapter,
                         XCoreRecyclerAdapter.IDataComponent data,
                         int pos) {
        //TODO Write the binding logic here
    }
}


The data source and the Item component have been defined, so what should we do for the source code of the core XCoreRecyclerAdapter?
XCoreRecyclerAdapter source code
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;

public class XCoreRecyclerAdapter extends RecyclerView.Adapter<XCoreRecyclerAdapter.CommonViewHolder> {

    private IXCoreComponent mIXCoreComponent;//Outer UI component
    private List<IDataComponent> mDataSet = new ArrayList<IDataComponent>();//Datasource
    private SparseArray<XCoreItemUIComponent> mConfigurationSparseArray = new SparseArray<XCoreItemUIComponent>();//Set: The Item component of the corresponding type
    private Map<String, Integer> mViewTypeMap = new HashMap<String, Integer>();//The string and int mapping of the type

    public XCoreRecyclerAdapter() {

    }

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

    @Override
    public CommonViewHolder onCreateViewHolder(ViewGroup viewGroup, int type) {
        //Get the corresponding item component based on the data type
        XCoreItemUIComponent XCoreItemUIComponent = mConfigurationSparseArray.get(type);
        if (XCoreItemUIComponent == null) {//If the item component is null, display an empty item
            return getDefaultViewHolder(viewGroup.getContext());
        }
        try {
            //Create a new View using the item component
            View view = XCoreItemUIComponent.onCreateView(LayoutInflater.from(viewGroup.getContext()), viewGroup);
            //Create an internal ViewHolder using the View
            CommonViewHolder commonViewHolder = new CommonViewHolder(view);
            //Create a new Item component
            XCoreItemUIComponent realItem = XCoreItemUIComponent.getClass().newInstance();
            //Set the created View to a real Item component
            realItem.setItemView(view);
            //Use the internal 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;
    }

    /**
     * Get the data source of the adapter
     *
     * @return
     */
    public List<IDataComponent> getDataSet() {
        return mDataSet;
    }

    /**
     * Set the data source of the 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;
            }
        }
    }

    /**
     * Register the Item component
     *
     * @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;
    }

    /**
     * Cancel the configuration
     *
     * @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;
    }

    /**
     * Interfaces that must be implemented by the data source
     */
    public interface IDataComponent {
        String getViewType();
    }

    /**
     * Use CommonViewHolder as an agent of the 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 source code
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;

public abstract class XCoreItemUIComponent implements IXCoreComponent {

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

    /**
     * Initialize the View here
     *
     * @param view
     */
    public abstract void onViewCreated(View view);

    /**
     * The ViewType supported by the item component, corresponding to the getViewType of IDataComponent
     *
     * @return
     */
    public abstract String getViewType();

    /**
     * Bind the data - frequent callbacks
     *
     * @param coreComponent       External UI components
     * @param coreRecyclerAdapter Adapter
     * @param data                Corresponding data sources
     * @param pos                 Location of the list of the item
     */
    public abstract void bindView(IXCoreComponent coreComponent,
                                  XCoreRecyclerAdapter coreRecyclerAdapter,
                                  XCoreRecyclerAdapter.IDataComponent data,
                                  int pos);

    /**
     * Called at the item component destruction
     */
    public abstract void onViewDetachedFromWindow();

    //Constructors with no parameters are required
    public View itemView;

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

}


Concluding remarks
This blog article introduces the generic XCoreRecyclerAdapter engine which greatly improves the reuse rate of adapters, so that developers can focus on necessary business code to improve development efficiency. In addition, supporting functions of XCoreRecyclerAdapter engine also include the UI components, the data control framework XCoreRedux and data binding.
Guest