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

Posted kakacxicm

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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<String> mData;
    public TestAdapter(List<String> 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 <T> model
     * @param <H> 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<T> mData;

    public UniversalAdapterBeta(List<T> data , OnBindDataInterface<T, H> bindInterface)
        mData = data;
        mOnBindDataInterface = bindInterface;
    

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

    private OnBindDataInterface<T, H> 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<String, UniversalViewHolderBeta> adapter = new UniversalAdapterBeta<>(mData, new UniversalAdapterBeta.OnBindDataInterface<String, UniversalViewHolderBeta>() 
            @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<View> 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 <T>
     * @return
     */
    public <T extends View> T getSubView(int viewId)
        View view = mViews.get(viewId);
        if(view == null)
            view = mContentView.findViewById(viewId);
            mViews.put(viewId, view);
        
        return (T)view;
    

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

    /**
     * 绑定数据的接口
     * @param <T> model
     */
    interface OnBindDataInterface<T>
        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<T> mData;

    public UniversalAdapter(List<T> data , OnBindDataInterface<T> bindInterface)
        mData = data;
        mOnBindDataInterface = bindInterface;
    

    public UniversalAdapter(List<T> data , OnMultiTypeBindDataInterface<T> bindInterface)
        mData = data;
        mOnMultiTypeBindDataInterface = bindInterface;
        mOnBindDataInterface = bindInterface;
    

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

    /**
     * 多类型支持
     * @param <T>
     */
    interface OnMultiTypeBindDataInterface<T> extends OnBindDataInterface<T>
        int getItemViewType(int postion);
    

    private OnBindDataInterface<T> mOnBindDataInterface;
    private OnMultiTypeBindDataInterface<T> 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<String> adapter = new UniversalAdapter<>(mData, new UniversalAdapter.OnMultiTypeBindDataInterface<String>() 
            @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进阶之通用RecyclerView适配器打造方法的主要内容,如果未能解决你的问题,请参考以下文章

android进阶篇02RecyclerView回收复用机制源码解析

我的Android进阶之旅强烈推荐 一种优雅的方式实现RecyclerView条目多类型

Android高级UI进阶RecyclerView 刷新列表数据的 notifyDataSetChanged() 为什么是昂贵的?

进阶篇-用户界面:3.RecyclerView

通用RecyclerView Adapter之VastBindAdapter

通用RecyclerView Adapter之VastBindAdapter