Android进阶之通用RecyclerView适配器打造方法

一、引言

从事android开发已经快三年了,相信大家和我一样,写了无数列表View的适配器,不知大家是否厌倦了这些重复的流程和代码?反正我是早厌倦了。本篇旨在从一段司空见惯的RecyclerView适配器代码开始,一步一步抽取代码的重复部分,打造和ListView通用适配器类似的RecyclerView通用适配器,一来为大家提供RecyclerView通用适配器,提高开发效率,二来分享一种代码抽取的思路。记得之前带我的师傅曾说过:框架不是一下子空想出来的,是”演化”而来的,就像大家熟知的Imageloarder,1.0版本绝对不是现在这个样子。。。废话就扯到这里,下面先看看一段让人熟悉到吐的适配器代码:

package com.star.xadapter;

import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.List;

/**
 * Created by kakaxicm on 16/7/7.
 */
public class TestAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
    private List mData;
    public TestAdapter(List data){
        mData = data;
    }
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new TestViewHolder(View.inflate(parent.getContext(),R.layout.item_test, null));
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        TestViewHolder tHolder = (TestViewHolder) holder;
        tHolder.tv.setText(mData.get(position));
    }

    @Override
    public int getItemCount() {
        return mData == null ? 0 : mData.size();
    }

    private class TestViewHolder extends RecyclerView.ViewHolder{
        TextView tv;
        public TestViewHolder(View itemView) {
            super(itemView);
            tv = (TextView) itemView.findViewById(R.id.tv);
        }


    }
}

当每个页面的RecyclerView的Model和View不同时,都得另写一个适配器,流程都是标准化的:
1.覆写getItemCount、onCreateViewHolder、onBindViewHolder方法,多类型支持还要覆写getItemViewType方法;
2.根据每个位置返回的itemview类型,构造不同的ViewHolder,ViewHolder和布局文件通过onBindViewHolder建立绑定关系。
既然流程是标准化的,那么这些公共的流程就可以抽取出来,具体的实现细节暴露给调用者实现。下面就来一步一步来“演化”代码,打造通用的适配器。

二、通用适配器初步演化

从第一节的分析,我们可以把公共的部分抽取出来,具体的细节通过接口暴露给调用者实现。那么公共部分有哪些呢?哪些细节是和业务相关的呢?
1.上面提到的四个方法规定了公共的流程,可以抽取出来;
2.数据层是一个列表,支持的Model类型由用户决定,所以可以做泛型支持;
3.View层即ViewHolder,也可以通过接口由用户构造。当然适配器在多type情况下支持不同的ViewHolder,也需要做泛型支持;
4.从以上3点的分析,那么对外暴露的接口就应该是这样:暴露两个个方法,一个方法绑定数据入参是model和viewholder,另外一个方法构造viewholder。接口代码如下:

   /**
     * 绑定数据的接口
     * @param  model
     * @param  viewholder
     */
    interface OnBindDataInterface<T, H extends RecyclerView.ViewHolder>{
        void onBindData(T model, H holder, int viewType);
        H getViewHolder(ViewGroup parent, int viewType);
    }

结合标准流程的方法,初步演化的适配器代码如下:

package com.star.xadapter;

import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;

import java.util.List;

/**
 * Created by kakaxicm on 16/7/6.
 */
public class UniversalAdapterBeta<T, H extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<H>{
    private List mData;

    public UniversalAdapterBeta(List data , OnBindDataInterface bindInterface){
        mData = data;
        mOnBindDataInterface = bindInterface;
    }

    /**
     * 绑定数据的接口
     * @param  model
     * @param  viewholder
     */
    interface OnBindDataInterfaceextends RecyclerView.ViewHolder>{
        void onBindData(T model, H holder, int viewType);
        H getViewHolder(ViewGroup parent, int viewType);
    }

    private OnBindDataInterface mOnBindDataInterface;

    @Override
    public H onCreateViewHolder(ViewGroup parent, int viewType) {
        //构建ViewHolder 入参 parant viewType layoutid
        return mOnBindDataInterface.getViewHolder(parent, viewType);
    }

    @Override
    public void onBindViewHolder(H holder, int position) {
        mOnBindDataInterface.onBindData(mData.get(position), holder, getItemViewType(position));
    }

    @Override
    public int getItemCount() {
        return mData == null ? 0 : mData.size();
    }

}

用例:

 UniversalAdapterBeta adapter = new UniversalAdapterBeta<>(mData, new UniversalAdapterBeta.OnBindDataInterface() {
            @Override
            public void onBindData(String model, UniversalViewHolderBeta holder, int type) {
                holder.mTv.setText(model);
            }

            @Override
            public UniversalViewHolderBeta getViewHolder(ViewGroup parent, int viewType) {
                return new UniversalViewHolderBeta(parent, R.layout.item_test);
            }
        });

可以看到,用户只需要构造viewholder和绑定数据就行,而不必关心流程是怎么走的。初步演化的思想是把View层的viewholder(H) 和 model(T)分开,把model和viewholder都暴露给用户来构造。

三、通用ViewHolder的封装

初步演化出来的适配器需要调用者构造model和holder,细想一下,viewholder的构造最关键的只有一个参数:布局文件id,而这个布局文件中有哪些view元素,都交给viewholder封装即可。至于多种类型的item对应多种viewholder,也是对应不同的layoutid和它的子view。这样就可以抽取出不同ViewHolder所共有的元素与方法,实现内部构造ViewHolder,进而把接口中ViewHolder构造的方法隐藏起来。那么这个通用的ViewHolder有哪些共有属性和方法呢?
1.布局文件id
2.子View元素集合
3.子View元素的访问方法
UniversalViewHolder具体代码:

package com.star.xadapter;
import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;

/**
 * Created by kakaxicm on 16/7/6.
 *
 */
public class UniversalViewHolder extends RecyclerView.ViewHolder{
    private SparseArray mViews;//子View元素集合
    private View mContentView;//itemView,用于查找子View
    public static UniversalViewHolder getViewHolder(ViewGroup parent, int layoutId)
    {
        return new UniversalViewHolder(View.inflate(parent.getContext(), layoutId, null));
    }

    public UniversalViewHolder(View itemView) {
        super(itemView);
        mViews = new SparseArray<>();
        mContentView = itemView;
    }

    /**
     * 子View元素的访问方法
     * @param viewId 
     * @param 
     * @return
     */
    public  T getSubView(int viewId){
        View view = mViews.get(viewId);
        if(view == null){
            view = mContentView.findViewById(viewId);
            mViews.put(viewId, view);
        }
        return (T)view;
    }

}

对用户暴露的接口就变成了这样:

    /**
     * 绑定数据的接口
     * @param  model
     */
    interface OnBindDataInterface{
        void onBindData(T model, UniversalViewHolder hodler, int type);
        int getItemLayoutId(int viewType);
    }

方法getItemLayoutId就是用户构造不同的UniversalViewHolder的。于是通用的适配器就出来了:

package com.star.xadapter;
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;

import java.util.List;

/**
 * Created by kakaxicm on 16/7/6.
 */
public class UniversalAdapter<T> extends RecyclerView.Adapter<UniversalViewHolder>{
    private List mData;

    public UniversalAdapter(List data , OnBindDataInterface bindInterface){
        mData = data;
        mOnBindDataInterface = bindInterface;
    }

    public UniversalAdapter(List data , OnMultiTypeBindDataInterface bindInterface){
        mData = data;
        mOnMultiTypeBindDataInterface = bindInterface;
        mOnBindDataInterface = bindInterface;
    }

    /**
     * 绑定数据的接口
     * @param  model
     */
    interface OnBindDataInterface{
        void onBindData(T model, UniversalViewHolder hodler, int type);
        int getItemLayoutId(int viewType);
    }

    /**
     * 多类型支持
     * @param 
     */
    interface OnMultiTypeBindDataInterface extends OnBindDataInterface{
        int getItemViewType(int postion);
    }

    private OnBindDataInterface mOnBindDataInterface;
    private OnMultiTypeBindDataInterface mOnMultiTypeBindDataInterface;

    @Override
    public int getItemViewType(int position) {
        if(mOnMultiTypeBindDataInterface != null){
            return mOnMultiTypeBindDataInterface.getItemViewType(position);
        }
        return 0;
    }

    @Override
    public UniversalViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //构建ViewHolder 入参 parant viewType layoutid
        int layoutId = mOnBindDataInterface.getItemLayoutId(viewType);
        UniversalViewHolder holder = UniversalViewHolder.getViewHolder(parent, layoutId);
        return holder;
    }

    @Override
    public void onBindViewHolder(UniversalViewHolder holder, int position) {
        mOnBindDataInterface.onBindData(mData.get(position), holder, getItemViewType(position));
    }

    @Override
    public int getItemCount() {
        return mData == null? 0 : mData.size();
    }

}

说明:
1.这个演化将ViewHolder的构造交给通用的ViewHolder处理,ViewHolder持有子View的id和对象的映射关系。与第一个版本相比,用户不用构造新的ViewHolder类,只需要设置layout即可;
2.为了支持多类型item,这里引入OnBindDataInterface的子接口OnMultiTypeBindDataInterface,getItemViewType方法返回item类型,用户根据返回类型在onBindData方法里做不同处理。用例如下:

UniversalAdapter adapter = new UniversalAdapter<>(mData, new UniversalAdapter.OnMultiTypeBindDataInterface() {
            @Override
            public int getItemViewType(int postion) {
                if(postion % 2 == 0){
                    return 0;
                }
                if(postion % 3 == 0){
                    return 2;
                }
                return 1;
            }

            @Override
            public void onBindData(String model, UniversalViewHolder holder, int type) {
                switch (type){
                    case 0:
                        TextView tv = holder.getSubView(R.id.tv);
                        tv.setText(model);
                        break;
                    case 1:
                        break;
                    case 2:
                        Button btn = holder.getSubView(R.id.btn);
                        btn.setText(model);
                        break;
                    default:
                        break;
                }

            }

            @Override
            public int getItemLayoutId(int viewType) {
                switch (viewType){
                    case 0:
                        return R.layout.item_test;
                    case 1:
                        return R.layout.item_test2;
                    case 2:
                        return R.layout.item_test3;
                }
                return 0;
            }
        });

补充:UniversalViewHolder持有子View的引用,可以添加一些常用辅助方法,如setImageSrc(int viewId, int drawid)等方法,方便用户调用。
总结:逐步抽取代码构建通用框架的设计思想就是典型的模板方法设计模式,它由公用的模板规定流程,通过子类或者接口实现具体每个流程的细节。希望大家从这篇博客中得到的不仅仅是一个通用的适配器,更重要的是”重复的事情简单做”的框架演化思想。

你可能感兴趣的:(Android进阶)