使用Android DataBinding簡化Adapter的開發 ...
閱讀本篇文章需要讀者對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簡單使用
接下來看一個簡單的例子。這個例子創建了一個簡單的列表,效果如下:
我們看看,使用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
幾乎沒有做任何事。事實上,我們聲明它完全是由於Adapter
的onCreateViewHolder
需要這麼一個返回值。
我們可以把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
的實現並不複雜,只需要重寫add
、addAll
、remove
等等等等這些可能造成集合發生變化的方法就可以實現上述效果。
雖然實現不複雜,但是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/