极简的Android RecyclerView Adapter(使用DataBinding)

阅读本篇文章须要读者对Android Databinding和RecyclerView有必定的了解。android

简介

咱们知道,DataBinding的核心理念是数据驱动。数据驱动驱动的目标就是View,使用DataBinding,咱们经过添加、修改、删除数据源,View就会自动予以相关变化。git

Android RecyclerView的Adapter起的做用就是链接数据和Viewgithub

一个最简单的RecyclerView Adapter多是下面这个样子的:数据库

public class UserAdapter extends RecyclerView.Adapter
{
    @Override
    public int getItemCount()
    {
        return 0;
    }
    
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
    {
        return null;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
    {

    }
}

经过getItemsCount(), RecyclerView知道了全部子项的数量。服务器

经过onCreateViewHolder(), RecyclerView知道了每个子项长什么样子。网络

经过onBindViewHolder(),让每一个子项得以显示正确的数据。ide

能够看到,Adapter起的做用和DataBinding是很是相似的,使用DataBinding,能够使Adapter的编写显得更加简单。函数

DataBinding简单使用

接下来看一个简单的例子。这个例子建立了一个简单的列表,效果以下:布局

image

咱们看看,使用DataBinding该如何实现它。优化

Model类:

public class User
{
    private String name;
    private int age;

    public User(String name, int age)
    {
        this.name = name;
        this.age = age;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public int getAge()
    {
        return age;
    }

    public void setAge(int age)
    {
        this.age = age;
    }
}

View xml

<?xml version="1.0" encoding="utf-8"?>
<layout>
    <data>
        <import type="cn.zmy.databindingadapter.model.User"/>
        <variable name="model"
                  type="User"/>
    </data>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  android:layout_width="match_parent"
                  android:layout_height="60dp"
                  android:layout_marginBottom="10dp"
                  android:background="@android:color/darker_gray"
                  android:gravity="center_vertical"
                  android:orientation="vertical">
        <TextView android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:text="@{model.name}"/>
        <TextView android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:text="@{String.valueOf(model.age)}"/>
    </LinearLayout>
</layout>

Adapter

public class UserAdapter extends RecyclerView.Adapter
{
    private Context context;
    private List<User> items;

    public UserAdapter(Context context)
    {
        this.context = context;
        this.items = new ArrayList<User>()
        {{
            add(new User("张三", 18));
            add(new User("李四", 28));
            add(new User("王五", 38));
        }};
    }

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

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
    {
        ItemUserBinding binding = DataBindingUtil.inflate(LayoutInflater.from(this.context), R.layout.item_user, parent, false);
        return new UserViewHolder(binding.getRoot());
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
    {
        ItemUserBinding binding = DataBindingUtil.getBinding(holder.itemView);
        binding.setModel(this.items.get(position));
        binding.executePendingBindings();
    }

    static class UserViewHolder extends RecyclerView.ViewHolder
    {
        public UserViewHolder(View itemView)
        {
            super(itemView);
        }
    }
}

Activity

public class MainActivity extends AppCompatActivity
{

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(new UserAdapter(this));
    }
}

能够看到,使用了DataBinding以后,咱们在onBindViewHolder中,无需再写一些相似于holder.view.setXXX()的代码,由于这些在Xml中就已经完成了。

优化

上面的Adapter还能不能更简单呢?

优化ViewHolder

咱们发现,Adapter中的UserViewHolder几乎没有作任何事。事实上,咱们声明它彻底是因为AdapteronCreateViewHolder须要这么一个返回值。

咱们能够把ViewHolder提出来,这样全部Adapter均可以使用而无需在每一个Adapter中都声明一个ViewHolder。

取名就叫BaseBindingViewHolder

public class BaseBindingViewHolder extends RecyclerView.ViewHolder
{
    public BaseBindingViewHolder(View itemView)
    {
        super(itemView);
    }
}

优化getItemCount

getItemCount返回了子项的数量。

因为几乎每一个Adapter都会存在一个List用于保存全部子项的数据,咱们彻底能够建立一个Adapter基类,而后在基类中实现getItemCount

优化onCreateViewHolder&onBindViewHolder

onCreateViewHolder代码以下:

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
    ItemUserBinding binding = DataBindingUtil.inflate(LayoutInflater.from(this.context), R.layout.item_user, parent, false);
    return new BaseBindingViewHolder(binding.getRoot());
}

能够看到,这个方法里面惟一的“变数”就是“R.layout.item_user”这个layout。

onBindViewHolder代码以下:

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
    ItemUserBinding binding = DataBindingUtil.getBinding(holder.itemView);
    binding.setModel(this.items.get(position));
    binding.executePendingBindings();
}

能够看到,这个方法先获取到View的Binding,而后给Binding的Data赋值。Binding从哪里来?都是经过DataBindingUtil.getBinding(holder.itemView)获取到的。

本着不写重复代码,能封装就封装的原则,咱们来建立Adapter基类。代码以下:

public abstract class BaseBindingAdapter<M, B extends ViewDataBinding> extends RecyclerView.Adapter
{
    protected Context context;
    protected List<M> items;

    public BaseBindingAdapter(Context context)
    {
        this.context = context;
        this.items = new ArrayList<>();
    }

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

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
    {
        B binding = DataBindingUtil.inflate(LayoutInflater.from(this.context), this.getLayoutResId(viewType), parent, false);
        return new BaseBindingViewHolder(binding.getRoot());
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
    {
        B binding = DataBindingUtil.getBinding(holder.itemView);
        this.onBindItem(binding, this.items.get(position));
    }

    protected abstract @LayoutRes int getLayoutResId(int viewType);

    protected abstract void onBindItem(B binding, M item);
}

而后使UserAdapter继承自上面封装的BaseBindingAdapter,代码以下:

public class UserAdapter extends BaseBindingAdapter<User, ItemUserBinding>
{
    public UserAdapter(Context context)
    {
        super(context);
        items.add(new User("张三", 18));
        items.add(new User("李四", 28));
        items.add(new User("王五", 38));
    }

    @Override
    protected int getLayoutResId(int viewType)
    {
        return R.layout.item_user;
    }

    @Override
    protected void onBindItem(ItemUserBinding binding, User user)
    {
        binding.setModel(user);
        binding.executePendingBindings();
    }
}

能够看到,优化后的Adapter除去初始化User数据源的那部分代码,实际上的核心代码就寥寥数行。

经过getLayoutResId咱们告诉了RecyclerView子项长什么样子。

经过onBindItem咱们给具体的每一个子项绑定了合适的数据。

至于具体的绑定过程,是放在布局的xml文件中的。

优化数据源

咱们的数据源是在构造函数中这样添加的:

items.add(new User("张三", 18));
items.add(new User("李四", 28));
items.add(new User("王五", 38));

在实际开发过程当中,咱们极少这么作。由于一般在构造Adapter的时候,咱们并未获得任何有效的数据。数据源多是经过网络请求从服务器得来,也多是经过查询本地数据库表得来。咱们在构造Adapter以后,可能还须要较长的时间去获取有效的数据源,这就要求必须在Adapter构造完成以后,外部调用者还能够修改的数据源。

咱们能够这样作:

adapter.items.add(XXX);
adapter.notifyItemInserted();

这样咱们新增数据源以后,adapter也知道咱们修改了数据源,进而View也就能随之变化。

不过有了DataBinding,咱们能够更为巧妙的实现上述操做。

ObservableArrayList

ObservableArrayList是Android DataBinding库中的一个类。

public class ObservableArrayList<T> extends ArrayList<T> implements ObservableList<T>
{
    ...
}

ObservableArrayList实现了ObservableList接口。经过ObservableList,咱们能够为ObservableArrayList添加一个或多个Listener。当ObservableArrayList中的数据发生变化时(添加了一个或多个元素、删除了其中某个或某些元素时),这些Listener或收到数据源发生改变的通知。

其实ObservableArrayList的实现并不复杂,只须要重写addaddAllremove等等等等这些可能形成集合发生变化的方法就能够实现上述效果。

虽然实现不复杂,可是ObservableArrayList却能够解决咱们上面遇到的修改数据源的问题。

咱们只须要在集合发生改变时,调用adapter.notifyXXX()等方法就能够实现当数据源发生变化时,View也能够自动发生变化,而外部却无需调用adapter.notifyXXX()了。

代码实现

咱们再次修改BaseBindingAdapter的代码,使之支持数据源发生变化时,自动更新View。

public abstract class BaseBindingAdapter<M, B extends ViewDataBinding> extends RecyclerView.Adapter
{
    protected Context context;
    protected ObservableArrayList<M> items;
    protected ListChangedCallback itemsChangeCallback;

    public BaseBindingAdapter(Context context)
    {
        this.context = context;
        this.items = new ObservableArrayList<>();
        this.itemsChangeCallback = new ListChangedCallback();
    }

    public ObservableArrayList<M> getItems()
    {
        return items;
    }

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

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
    {
        B binding = DataBindingUtil.inflate(LayoutInflater.from(this.context), this.getLayoutResId(viewType), parent, false);
        return new BaseBindingViewHolder(binding.getRoot());
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
    {
        B binding = DataBindingUtil.getBinding(holder.itemView);
        this.onBindItem(binding, this.items.get(position));
    }

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView)
    {
        super.onAttachedToRecyclerView(recyclerView);
        this.items.addOnListChangedCallback(itemsChangeCallback);
    }

    @Override
    public void onDetachedFromRecyclerView(RecyclerView recyclerView)
    {
        super.onDetachedFromRecyclerView(recyclerView);
        this.items.removeOnListChangedCallback(itemsChangeCallback);
    }

    //region 处理数据集变化
    protected void onChanged(ObservableArrayList<M> newItems)
    {
        resetItems(newItems);
        notifyDataSetChanged();
    }

    protected void onItemRangeChanged(ObservableArrayList<M> newItems, int positionStart, int itemCount)
    {
        resetItems(newItems);
        notifyItemRangeChanged(positionStart,itemCount);
    }

    protected void onItemRangeInserted(ObservableArrayList<M> newItems, int positionStart, int itemCount)
    {
        resetItems(newItems);
        notifyItemRangeInserted(positionStart,itemCount);
    }

    protected void onItemRangeMoved(ObservableArrayList<M> newItems)
    {
        resetItems(newItems);
        notifyDataSetChanged();
    }

    protected void onItemRangeRemoved(ObservableArrayList<M> newItems, int positionStart, int itemCount)
    {
        resetItems(newItems);
        notifyItemRangeRemoved(positionStart,itemCount);
    }

    protected void resetItems(ObservableArrayList<M> newItems)
    {
        this.items = newItems;
    }
    //endregion

    protected abstract @LayoutRes int getLayoutResId(int viewType);

    protected abstract void onBindItem(B binding, M item);

    class ListChangedCallback extends ObservableArrayList.OnListChangedCallback<ObservableArrayList<M>>
    {
        @Override
        public void onChanged(ObservableArrayList<M> newItems)
        {
            BaseBindingAdapter.this.onChanged(newItems);
        }

        @Override
        public void onItemRangeChanged(ObservableArrayList<M> newItems, int i, int i1)
        {
            BaseBindingAdapter.this.onItemRangeChanged(newItems,i,i1);
        }

        @Override
        public void onItemRangeInserted(ObservableArrayList<M> newItems, int i, int i1)
        {
            BaseBindingAdapter.this.onItemRangeInserted(newItems,i,i1);
        }

        @Override
        public void onItemRangeMoved(ObservableArrayList<M> newItems, int i, int i1, int i2)
        {
            BaseBindingAdapter.this.onItemRangeMoved(newItems);
        }

        @Override
        public void onItemRangeRemoved(ObservableArrayList<M> sender, int positionStart, int itemCount)
        {
            BaseBindingAdapter.this.onItemRangeRemoved(sender,positionStart,itemCount);
        }
    }
}

而后咱们修改Activity的代码:

public class MainActivity extends AppCompatActivity
{

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        UserAdapter adapter = new UserAdapter(this);
        recyclerView.setAdapter(adapter);

        adapter.getItems().add(new User("张三", 18));
        adapter.getItems().add(new User("李四", 28));
        adapter.getItems().add(new User("王五", 38));
    }
}

能够看到,外部仅仅将数据添加到了数据源中,而没有作任何其余操做。不过咱们的View仍是更新了,效果和上面是同样的。这也符合DataBinding的核心原则:数据驱动。使用DataBinding,咱们关心的只有数据源,只要数据源发生改变,View就应随之发生改变。

Demo

文章中的代码已整理上传至Github。 连接:https://github.com/a3349384/DataBindingAdapter 博客:https://www.zhoumingyao.cn/

相关文章
相关标签/搜索