zoukankan      html  css  js  c++  java
  • 极简的Android RecyclerView Adapter(使用DataBinding)

    阅读本篇文章需要读者对Android Databinding和RecyclerView有一定的了解。

    简介

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

    Android RecyclerView的Adapter起的作用就是连接数据和View

    一个最简单的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(),让每个子项得以显示正确的数据。

    可以看到,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/

  • 相关阅读:
    Java之美[从菜鸟到高手演变]之设计模式
    常见JAVA框架
    每周一荐:学习ACE一定要看的书
    YUV格式&像素
    关于makefile
    socket通信
    [理论篇]一.JavaScript中的死连接`javascript:void(0)`和空连接`javascript:;`
    [应用篇]第三篇 JSP 标准标签库(JSTL)总结
    [应用篇]第一篇 EL表达式入门
    KVM基本实现原理
  • 原文地址:https://www.cnblogs.com/DoNetCoder/p/7243878.html
Copyright © 2011-2022 走看看