簡介
隨着Android的發展,最初的MVC框架遠遠無法滿足廣大_Android猿_ 的需求,進而出現了MVP、MVVM等開發框架,Databing就是Google官方出品的支持MVVM開發的一個依賴庫。或許說依賴庫不準確,應該說是一整套開發工具和依賴庫的集合
今天我們不講開發框架的問題,我們只是來看一下 databing 的使用
簡單使用
- 在相關模塊下的
buidl.gradle
中設置對databing的支持
android {
......
//MVVM dataBinding 支持
dataBinding {
enabled = true
}
......
}
- 在
gradle.properties
中添加下面一句代碼開啓數據綁定編輯
android.databinding.enableV2=true
以上兩步完成後我們就可以開心的使用databing的方式進行編碼了。 - 代碼編寫
- 定義 ViewModel類定義 ViewModel類
public class User extends BaseObservable {
private String name;
private String sex;
private int age;
public User(String name, String sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
這裏的user類和我們以前定義的實體類並沒有任何的區別。
- xml中綁定數據
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 數據元素節點,定義導入我們要綁定的數據元素 -->
<data>
<variable
name="user"
type="com.caozy.demo1.bean.User" />
</data>
<!-- 視圖元素節點 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="@{user.name}" />
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="@{user.sex}" />
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="@{String.valueOf(user.age)}" />
<Button
android:id="@+id/btn_1"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="Click Me !" />
<Button
android:id="@+id/btn_2"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="btn2"
/>
</LinearLayout>
</layout>
在這裏xml文件中的根標籤不再是一個容器,而是變成了<layout></layout>
並且在頭部添加了<data></data>
標籤,和java文件中的import有類似的作用。我們一般創建的xml文件的根標籤都是容器,這裏有一個快捷鍵可以使用,將光標放在根標籤處使用快捷鍵 Mac (⌥ + Enter)
wimdows(Alt + Enter)
你會看到如下提示
選擇 Convert to data binding layout
可以很快捷的將普通的xml佈局文件轉換成 dataBinding 需要的xml形式
data binding layout 有兩部分 數據元素(即<data></data>
節點)和視圖元素
數據元素的導入方法
<variable>
標籤直接導入。
<data>
<variable
name="user"
type="com.caozy.demo1.bean.User" />
<variable
name="user2"
type="com.caozy.demo1.samename.User"/>
</data>
代碼中 name 是我們控件綁定數據時的實例引用,類似我們對java實例的應用。
type就是我們要導入的數據類型
2. 結合 <import>
標籤導入
<data>
<import type="com.caozy.demo1.bean.User" />
<!-- 取了別名的 import -->
<import type="com.caozy.demo1.samename.User"
alias="user1"
/>
<variable
name="user"
type="User" />
<variable
name="user1"
type="user1"/>
</data>
如上代碼中,如果在不同包中有同名的類,你可以在導入這個類時取一個別名,<variable>
中type用這個別名就可以了。
數據元素的綁定
佈局中使用@{}
語法在屬性中綁定數據。
android:text="@{user.name}"
這裏就是將text屬性綁定爲user的name屬性
- activity中代碼
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
User user = new User("張三", "男", 18);
binding.setUser(user);
}
這裏可能會有很多人奇怪ActivityMainBinding
類的由來。這個類當你將xml轉換成data binding layout 的時候就自動生成了。默認情況下生成的Binding類名是根據佈局文件名稱生成的,大寫字母開頭,移除下劃線並大寫後面的字母最後加上“Binding”後綴。Binding類位於包com.caozy.demo1.databinding.ActivityMainBinding
,當然這是在build文件夾下。也可以指定Binding類的名字,看代碼
<data class="Main"></data>
或
<data class=".Main"></data>
或
<data class="com.caozhy.Main"></data>
這裏有幾種簡單的生成規則
- 佈局中設置ID的view會在Binding類中生成 public final 變量。生成規則爲View的ID名首字母小寫,移除下劃線並大寫後面的字母。例如 tv_name會生成tvName。
- 對於layout中使用的這些綁定 當我們沒有調用
binding.setUser(user);
的時候是有默認值的。引用類型爲null,int爲0,boolean爲false等
雙向綁定
上面只是簡單的顯示一些數據,一但數據發生變化就監測不到了。別急,下面我們要講的雙向綁定就是解決這個問題的。給個栗子:
從上面的例子我們看出兩點
- 頁面上的輸入可以改變數據的值
- 數據的改變可以實時的顯示在頁面上
要實現雙向綁定在以上使用的基礎上要做以下幾點改動
- 實體類中 這裏有兩種方法
- 實體類中的成員變量或者 get方法使用
@Bindable
註解 (如下代碼中name屬性) - 或者 使用
ObservableField
定義成員變量 (如下代碼中 content 屬性)
- 實體類中的成員變量或者 get方法使用
public class User extends BaseObservable {
//使用 @Bindable 註解實現雙向綁定
@Bindable
private String name;
private String sex;
//使用 ObservableField 定義 成員變量實現雙向綁定
private ObservableField<String> content = new ObservableField<>();
public ObservableField<String> getContent() {
return content;
}
public void setContent(ObservableField<String> content) {
this.content = content;
}
//@Bindable //也可以在get方法上使用 註解
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
//更新頁面數據
notifyPropertyChanged(BR.name);
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
注意: 使用 ObservableField時有一點需要注意,定義的屬性一定要調用new ObservableField<>()
初始化
這裏對於 基本數據類型 和 list map 等都有對於的Observable類可供使用
- xml中
將@{}
改成@={}
即可
這樣就可以實現上面的例子的效果
有幾點想說:
- 如果我們只使用了
@Bindable
註解而沒有在set方法中調用notifyPropertyChanged(BR.name);
那麼上例中editText內容的改變會引起user實例name值的改變,但是不會更新頁面中textView的值。 - 我們不僅可以在user實體類的set方法中調用
notifyPropertyChanged(BR.name)
也可以在任何我們需要的地方用user類的對象調用這一方法
再給個栗子:
這裏就是將user類中的setName
方法中的notifyPropertyChanged(BR.name)
代碼去掉,Activity中添加button的點擊代碼
binding.btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//更新頁面中user的name屬性
user.notifyPropertyChanged(BR.name);
}
});
- 當我們沒有用雙向綁定的時,如果user的數據發生了變化,我們也可以再次調用
binding.setUser(user);
方法更新頁面數據,每次調用binding.setUser(user);
都會進行頁面數據的更新
ListView、RecycleView中的使用
. ListView
ListView 中數據的簡單展示
activity的代碼跟你平時是一樣的。xml的代碼還是那個樣子,只是 item的xml改成dataBinding layout 就可以了。
我們主要看一下Adapter
的代碼
public class ListAdapter extends BaseAdapter {
private Context context;
private List<User> users;
public ListAdapter(Context context, List<User> users) {
this.context = context;
this.users = users;
}
@Override
public int getCount() {
return users.size();
}
@Override
public User getItem(int position) {
return users.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ItemListBinding binding;
if (convertView == null) {
//這裏的最後一個參數必須爲 false
binding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.item_list, parent, false);
convertView = binding.getRoot();
} else {
binding = DataBindingUtil.getBinding(convertView);
}
binding.setUser(getItem(position));
// binding.setVariable(BR.user, getItem(position));
return convertView;
}
}
Adapter的代碼暫時就這些了。
有的朋友會說:”你這個爲什麼沒有用 ViewHolder呀?”。其實,dataBinding 已經對這方面做了處理。 不信你跟蹤DataBindingUtil.getBinding(convertView);
這行代碼進去。跟蹤兩步,你最終會發現這麼一句代碼return (ViewDataBinding) v.getTag(R.id.dataBinding);
是不是這就很熟悉了。
數據源改變後數據更新方式
經過上面第一步,你一經展示出了list的數據。有些人可能會發現有問題,不要着急,因爲確實有問題,即使沒有問題經過下面的操作也會發現問題。
修改數據源,並且不去調用我們通常調用的ListAdapter.notifyDataSetChanged();
此時點擊listview中的某一項,會發現程序閃退。對於這個問題我們並不陌生。報錯信息爲java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. Make sure your adapter calls notifyDataSetChanged() when its content changes. [in ListView(2131165264, class android.widget.ListView) with Adapter(class com.caozy.listview1demo.ListAdapter)]
這裏有兩種方式解決這個問題。
- 和之前一樣當數據源改變時主動調用
ListAdapter.notifyDataSetChanged()
來刷新界面。 - 將數據源中的List替換成
ObservableList
。這個接口繼承 List接口。所以用法和List一樣,只是多了一個數據變化的監聽OnListChangedCallback
;只是一個接口所以DataBinding也提供了可供實例化的ObservableArrayList
,這個類繼承ArrayList。我們倆看下adapter的代碼,
public class ListAdapter extends BaseAdapter {
private Context context;
private ObservableList<User> users;
public ListAdapter(Context context, ObservableList<User> users) {
this.context = context;
this.users = users;
//設置數據變化監聽
users.addOnListChangedCallback(new ListChangeListener());
}
@Override
public int getCount() {
return users.size();
}
@Override
public User getItem(int position) {
return users.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ItemListBinding binding;
if (convertView == null) {
//這裏的最後一個參數必須爲 false
binding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.item_list, parent, false);
convertView = binding.getRoot();
} else {
binding = DataBindingUtil.getBinding(convertView);
}
binding.setUser(getItem(position));
// binding.setVariable(BR.user, getItem(position));
return convertView;
}
/**
* 數據變化監聽
*/
private class ListChangeListener extends ObservableList.OnListChangedCallback {
@Override
public void onChanged(ObservableList sender) {
ListAdapter.this.notifyDataSetChanged();
}
@Override
public void onItemRangeChanged(ObservableList sender, int positionStart, int itemCount) {
ListAdapter.this.notifyDataSetChanged();
}
@Override
public void onItemRangeInserted(ObservableList sender, int positionStart, int itemCount) {
ListAdapter.this.notifyDataSetChanged();
}
@Override
public void onItemRangeMoved(ObservableList sender, int fromPosition, int toPosition, int itemCount) {
ListAdapter.this.notifyDataSetChanged();
}
@Override
public void onItemRangeRemoved(ObservableList sender, int positionStart, int itemCount) {
ListAdapter.this.notifyDataSetChanged();
}
}
}
我們看到代碼並沒有多大的變化,只是將list替換成ObservableList並對其添加數據監聽。這樣就無需在數據變化時調用ListAdapter.notifyDataSetChanged()
。
. RecycleView
其他都和平時的使用是一樣的。我們就來看一下adapter中代碼
public class RecycleViewAdapter extends RecyclerView.Adapter<RecycleViewAdapter.CzyViewHolder> {
ObservableList<User> mList;
public RecycleViewAdapter(ObservableList<User> mList) {
this.mList = mList;
mList.addOnListChangedCallback(new CzyOnListChangeCallback());
}
@NonNull
@Override
public CzyViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
//這裏的第四個參數必須爲 false 這在Google
ItemRecycleViewBinding binding = DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()), R.layout.item_recycle_view, viewGroup, false);
return new CzyViewHolder(binding.getRoot());
}
@Override
public void onBindViewHolder(@NonNull CzyViewHolder czyViewHolder, int i) {
ItemRecycleViewBinding binding = DataBindingUtil.getBinding(czyViewHolder.itemView);
binding.setUser(mList.get(i));
}
@Override
public int getItemCount() {
return mList.size();
}
class CzyViewHolder extends RecyclerView.ViewHolder {
public CzyViewHolder(@NonNull View itemView) {
super(itemView);
}
}
//數據變化監聽
class CzyOnListChangeCallback extends ObservableList.OnListChangedCallback {
@Override
public void onChanged(ObservableList sender) {}
@Override
public void onItemRangeChanged(ObservableList sender, int positionStart, int itemCount) {
RecycleViewAdapter.this.notifyItemRangeChanged(positionStart, itemCount);
}
@Override
public void onItemRangeInserted(ObservableList sender, int positionStart, int itemCount) {
RecycleViewAdapter.this.notifyItemRangeInserted(positionStart, itemCount);
}
@Override
public void onItemRangeMoved(ObservableList sender, int fromPosition, int toPosition, int itemCount) {
RecycleViewAdapter.this.notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onItemRangeRemoved(ObservableList sender, int positionStart, int itemCount) {
RecycleViewAdapter.this.notifyItemRangeRemoved(positionStart, itemCount);
}
}
}
這篇文章我們主要講了一下dataBinding對控件和list視圖等進行數據綁定。我們發現僅僅簡單的綁定了文字,對一些複雜的數據處理我們將在下一章進行講解。
如:網絡圖片的加載等。