Databinding之RecyclerViewAdapter使用与封装

Databinding之RecyclerViewAdapter使用与封装

RecyclerViewAdapter大家都不陌生,那么在使用Databinding时,RecyclerViewAdapter该如何编写呢?

本文用一个邮箱类型列表作为案例,来讲解在使用Databinding时如何编写RecyclerViewAdapter,并且如何有效的封装RecyclerViewAdapter。

简单使用

首先编写MailType类,text和icon分别表示邮箱类型的名称以及图标。

/**
 * Created by gongw on 2018/7/11.
 */

public class MailType {
    private String text;
    private String icon;

    public MailType(String text, String icon)
    {
        this.text = text;
        this.icon = icon;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public String getIcon() {
        return icon;
    }

    public void setIcon(String icon) {
        this.icon = icon;
    }
}

编写MailTypeModell类,通过getDatas()提供邮箱类型数据。

/**
 * Created by gongw on 2018/7/11.
 */

public class MailTypeModel {

    private static class InstanceHolder {
        static final MailTypeModel INSTANCE = new MailTypeModel();
    }

    private MailTypeModel(){}

    public static MailTypeModel getInstance(){
        return InstanceHolder.INSTANCE;
    }

    public List getDatas(){
        List datas = new ArrayList<>();
        String[] types = BaseApplication.getContext().getResources().getStringArray(R.array.mail_type);

        for(String type : types){
            String[] mailType = type.split("=");
            datas.add(new MailType(mailType[0], mailType[1]));
        }

        return datas;
    }
}

这里使用一个简单的StringArray作为数据源,用“=“分割邮箱类型的名称和图标资源名称。


<resources>
    <array name="mail_type">
        <item>QQ邮箱=icqqmailitem>
        <item>139邮箱=ic139mailitem>
        <item>189邮箱=ic189mailitem>
        <item>阿里邮箱=ic_alimailitem>
    array>
resources>

编写邮箱类型的item布局。

这里使用了Databinding的一个自定义属性的功能,为ImageView设置了一个drawable的属性,功能是通过图片名称获取资源id,并为imageview设置图片。

@BindingAdapter({"drawable"})
    public static void setImage(ImageView imageView, String icon){
        int r_id = imageView.getResources().getIdentifier(icon, "drawable", imageView.getContext().getPackageName());
        imageView.setImageResource(r_id);
    }

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable
            name="mailType"
            type="com.gongw.login.model.MailType"/>
    data>
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="60dp"
    android:orientation="horizontal"
    android:paddingStart="26dp"
    android:paddingEnd="26dp">

    <ImageView
        android:layout_width="36dp"
        android:layout_height="36dp"
        android:layout_gravity="center_vertical"
        app:drawable="@{mailType.icon}"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_marginStart="26dp"
        android:layout_marginLeft="26dp"
        android:gravity="center_vertical"
        android:singleLine="true"
        android:text="@{mailType.text}" />

LinearLayout>
layout>

编写MailTypeAdapter,可以看到使用Databinding可以有效的减少Adapter中代码的行数,在onBindViewHolder方法中,只需要一行binding.setMailType即可为item布局中的视图设置数据。

/**
 * Created by gongw on 2018/7/14.
 */

public class MailTypeAdapter extends RecyclerView.Adapter<MailTypeAdapter.MailTypeViewHolder> {
    private List itemDatas;

    public MailTypeAdapter(List itemDatas){
        this.itemDatas = itemDatas;
    }

    @Override
    public MailTypeAdapter.MailTypeViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        ItemMailTypeBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.item_mail_type, parent, false);
        return new MailTypeViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(MailTypeAdapter.MailTypeViewHolder holder, int position) {
        holder.binding.setMailType(itemDatas.get(position));
        holder.binding.executePendingBindings();
    }

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

    class MailTypeViewHolder extends RecyclerView.ViewHolder{

        ItemMailTypeBinding binding;

        public MailTypeViewHolder(ItemMailTypeBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
        }

    }
}

将MailType数据通过MailTypeAdapter绑定到RecyclerView。

public class MailTypeFragment extends BaseFragment {

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
       FragmentMailTypeBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_mail_type, container, false);

        RecyclerView recyclerView = binding.recyclerView;
        recyclerView.setLayoutManager(new LinearLayoutManager(recyclerView.getContext(), LinearLayoutManager.VERTICAL, false));

        MailTypeAdapter adapter = new MailTypeAdapter(MailTypeModel.getInstance().getDatas());
        recyclerView.setAdapter(adapter);
        return binding.getRoot();
    }

}

运行效果
这里写图片描述

通用Adapter封装

上面我们已经成功用Databinding实现了RecyclerviewAdapter的使用,但上面的写法存在几个问题:

  1. 上面的写法把Item的布局和item的类型写死在了Adapter中,如果我们想再实现一个使用其他的item布局展示其他数据的列表,上面的Adapter就无法使用。
  2. 无法使用多布局,项目中经常存在一个列表中使用多种布局的场景,遇到这种场景,上面的Adapter也无法使用。
  3. 没有留出设置各类事件回调的方法,比如设置item的点击事件等等。

鉴于上面的问题,这里我们尝试编写一个适用于任何场景的Adapter类。

首先,来解决问题1。

为了能适用于不同的数据,我们不能将数据类型写死在Adapter中,需要用泛型替代具体的数据类型,其次,item的布局也不能写死,需要从构造方法中注入。

在封装过程中会遇到这样一个问题,由于Databinding是与布局绑定的,布局的不固定会导致binding在设置数据时无法用具体的binding.setMailType这样的写法,只能通过binding.setVariable的方式,而这个方法需要提供一个BR id,所以BR id也需要从构造方法中注入。

下面是具体的实现

/**
 * Created by gongw on 2018/7/13.
 */

public class SimpleAdapter<T> extends RecyclerView.Adapter<SimpleAdapter.BaseViewHolder> {

    private List itemDatas;
    private int layoutId;
    private int brId;

    public SimpleAdapter(List itemDatas, int layoutId, int brId){
        this.itemDatas = itemDatas;
        this.layoutId = layoutId;
        this.brId = brId;
    }

    @Override
    public SimpleAdapter.BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), layoutId, parent, false);
        return new BaseViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(SimpleAdapter.BaseViewHolder holder, int position) {
        holder.binding.setVariable(brId, itemDatas.get(position));
        holder.binding.executePendingBindings();
    }

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

    class BaseViewHolder extends RecyclerView.ViewHolder {
        ViewDataBinding binding;

        BaseViewHolder(ViewDataBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
        }
    }

}

通过上面的修改,SimpleAdapter已经可以适用于不同的数据类型和item布局的情况了,成功解决问题1;

现在再来看问题2,如何使Adapter适配多布局的场景。

这里假定一个这样的场景,当邮箱类型的是QQ邮箱时,需要用第二种item布局,将邮箱的图标和文字位置互换。

我们都知道,要适配多种布局需要实现RecyclerViewAdapter的getItemViewType方法,通过不同的viewType来对应不同的item布局。这里我们可以取个巧,因为viewType和item布局都是int类型,所以可以在getItemViewType方法中直接返回item布局id,而具体返回哪个item布局id则交由外部实现。

这里定义一个getItemLayout的方法,默认返回构造方法传入的item布局。外部可以通过重写这个方法来修改返回item布局的逻辑,以此应对多布局的场景。

public class SimpleAdapter<T> extends RecyclerView.Adapter<SimpleAdapter.BaseViewHolder> {

    private List itemDatas;
    private int defaultLayout;
    private int brId;

    public SimpleAdapter(List itemDatas, int defaultLayout, int brId){
        this.itemDatas = itemDatas;
        this.defaultLayout = defaultLayout;
        this.brId = brId;
    }

    public int getItemLayout(T itemData){
        return defaultLayout;
    }

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

    @Override
    public SimpleAdapter.BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), viewType, parent, false);
        return new BaseViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(SimpleAdapter.BaseViewHolder holder, int position) {
        holder.binding.setVariable(brId, itemDatas.get(position));
        holder.binding.executePendingBindings();
    }

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

    class BaseViewHolder extends RecyclerView.ViewHolder {
        ViewDataBinding binding;

        BaseViewHolder(ViewDataBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
        }
    }

}

重写getItemLayout方法,返回Adapter应该使用的item布局,这里如果是QQ邮箱就使用R.layout.item_mail_type1,否则使用R.layout.item_mail_type作为item布局。

/**
 * Created by gongw on 2018/7/10.
 */

public class MailTypeFragment extends BaseFragment {

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        FragmentMailTypeBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_mail_type, container, false);

        RecyclerView recyclerView = binding.recyclerView;
        recyclerView.setLayoutManager(new LinearLayoutManager(recyclerView.getContext(), LinearLayoutManager.VERTICAL, false));

        SimpleAdapter adapter = new SimpleAdapter(MailTypeModel.getInstance().getDatas(), R.layout.item_mail_type, BR.mailType){
            @Override
            public int getItemLayout(MailType itemData) {
                return itemData.getText().equals("QQ邮箱") ? R.layout.item_mail_type1 : R.layout.item_mail_type;
            }
        };

        recyclerView.setAdapter(adapter);
        return binding.getRoot();
    }

}

编写第二种item布局R.layout.item_mail_type1。


<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable
            name="mailType"
            type="com.gongw.login.model.MailType"/>
    data>
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="@dimen/item_height_normal"
    android:orientation="horizontal"
    android:paddingStart="26dp"
    android:paddingEnd="26dp">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_marginStart="26dp"
        android:layout_marginLeft="26dp"
        android:gravity="center_vertical"
        android:singleLine="true"
        android:text="@{mailType.text}" />

     <ImageView
        android:layout_width="36dp"
        android:layout_height="36dp"
        android:layout_gravity="center_vertical"
        app:drawable="@{mailType.icon}"/>

LinearLayout>
layout>

运行效果,可以看到QQ邮箱使用的布局与其他邮箱是不同的

这里写图片描述

通过上面的修改,SimpleAdapter可以通过重写getItemLayout来应对多布局的场景了,问题2解决;

现在再来看问题3,需要提供设置item事件回调的方法。

事件回调封装的思想主要有两点:首先需要在onBindViewHolder中设置回调,因为只有在这个方法中才可以拿到具体的view和position。其次,item布局中可能有多个不同的view,每个view都应该可以设置自己的事件回调,而且事件回调的类型根据view的类型可能有很多种,如一般view都有的onclick,checkbox的onCheckedChange,srcollview的onScrollChange等等,这些类型无法用一个抽象的参数来表示。

基于以上两点,我决定将事件绑定的整套逻辑交由外部实现,adapter向外部提供事件绑定所需的资源即view、itemData和position。这里提供一个空方法addListener供外部实现,onBindViewHolder中通过调用addListener来设置事件回调。

/**
 * Created by gongw on 2018/7/13.
 */

public class SimpleAdapter<T> extends RecyclerView.Adapter<SimpleAdapter.BaseViewHolder> {

    private List itemDatas;
    private int defaultLayout;
    private int brId;

    public SimpleAdapter(List itemDatas, int defaultLayout, int brId){
        this.itemDatas = itemDatas;
        this.defaultLayout = defaultLayout;
        this.brId = brId;
    }

    public int getItemLayout(T itemData){
        return defaultLayout;
    }

    public void addListener(View root, T itemData, int position){}

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

    @Override
    public SimpleAdapter.BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), viewType, parent, false);
        return new BaseViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(SimpleAdapter.BaseViewHolder holder, int position) {
        holder.binding.setVariable(brId, itemDatas.get(position));
        addListener(holder.binding.getRoot(), itemDatas.get(position), position);
        holder.binding.executePendingBindings();
    }

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

    class BaseViewHolder extends RecyclerView.ViewHolder {
        ViewDataBinding binding;

        BaseViewHolder(ViewDataBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
        }
    }

}

重写addListener方法,自行设置item中具体view的具体事件。

/**
 * Created by gongw on 2018/7/10.
 */

public class MailTypeFragment extends BaseFragment {

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        FragmentMailTypeBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_mail_type, container, false);

        RecyclerView recyclerView = binding.recyclerView;
        recyclerView.setLayoutManager(new LinearLayoutManager(recyclerView.getContext(), LinearLayoutManager.VERTICAL, false));
        recyclerView.addItemDecoration(new RecyclerViewDivider(recyclerView.getContext(), LinearLayoutManager.VERTICAL));

        SimpleAdapter adapter = new SimpleAdapter(MailTypeModel.getInstance().getDatas(), R.layout.item_mail_type, BR.mailType){
            @Override
            public void addListener(View root, MailType itemData, final int position) {
                root.findViewById(R.id.textView).setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Toast.makeText(getActivity(), "textView clicked!", Toast.LENGTH_SHORT).show();
                    }
                });

                root.findViewById(R.id.imageView).setOnLongClickListener(new View.OnLongClickListener() {
                    @Override
                    public boolean onLongClick(View v) {
                        Toast.makeText(getActivity(), "imageView long clicked!", Toast.LENGTH_SHORT).show();
                        return true;
                    }
                });
            }
        };

        recyclerView.setAdapter(adapter);
        return binding.getRoot();
    }

}

通过上面的修改,SimpleAdapter可以通过重写addListener方法来设置多种类型的事件回调了,问题3解决;

除了上面提到的问题,一个成熟的RecyclerViewAdapter还应该提供一些常用的方法供项目中方便使用,如item数据变化、范围变化、范围增加、范围删除时,Adapter自动刷新列表的方法。

    public void onItemDatasChanged(List newItemDatas){
        this.itemDatas = newItemDatas;
        notifyDataSetChanged();
    }

    protected void onItemRangeChanged(List newItemDatas, int positionStart, int itemCount)
    {
        this.itemDatas = newItemDatas;
        notifyItemRangeChanged(positionStart,itemCount);
    }

    protected void onItemRangeInserted(List newItemDatas, int positionStart, int itemCount)
    {
        this.itemDatas = newItemDatas;
        notifyItemRangeInserted(positionStart,itemCount);
    }

    protected void onItemRangeRemoved(List newItemDatas, int positionStart, int itemCount)
    {
        this.itemDatas = newItemDatas;
        notifyItemRangeRemoved(positionStart,itemCount);
    }

最终得出的产物如下,所有代码加起来只有80多行,却可以应对项目中的绝大多数场景重复使用,也可以说得上短小精悍了。

package com.gongw.common.adapter;

import android.databinding.DataBindingUtil;
import android.databinding.ViewDataBinding;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.List;

/**
 * Created by gongw on 2018/7/13.
 */

public class SimpleAdapter<T> extends RecyclerView.Adapter<SimpleAdapter.BaseViewHolder> {

    private List itemDatas;
    private int defaultLayout;
    private int brId;

    public SimpleAdapter(List itemDatas, int defaultLayout, int brId){
        this.itemDatas = itemDatas;
        this.defaultLayout = defaultLayout;
        this.brId = brId;
    }

    public int getItemLayout(T itemData){
        return defaultLayout;
    }

    public void addListener(View root, T itemData, int position){}

    public void onItemDatasChanged(List newItemDatas){
        this.itemDatas = newItemDatas;
        notifyDataSetChanged();
    }

    protected void onItemRangeChanged(List newItemDatas, int positionStart, int itemCount)
    {
        this.itemDatas = newItemDatas;
        notifyItemRangeChanged(positionStart,itemCount);
    }

    protected void onItemRangeInserted(List newItemDatas, int positionStart, int itemCount)
    {
        this.itemDatas = newItemDatas;
        notifyItemRangeInserted(positionStart,itemCount);
    }

    protected void onItemRangeRemoved(List newItemDatas, int positionStart, int itemCount)
    {
        this.itemDatas = newItemDatas;
        notifyItemRangeRemoved(positionStart,itemCount);
    }

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

    @Override
    public SimpleAdapter.BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), viewType, parent, false);
        return new BaseViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(SimpleAdapter.BaseViewHolder holder, int position) {
        holder.binding.setVariable(brId, itemDatas.get(position));
        addListener(holder.binding.getRoot(), itemDatas.get(position), position);
        holder.binding.executePendingBindings();
    }

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

    class BaseViewHolder extends RecyclerView.ViewHolder {
        ViewDataBinding binding;

        BaseViewHolder(ViewDataBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
        }
    }

}

你可能感兴趣的:(Databinding)