極簡的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/

相關文章
相關標籤/搜索