先看兩個問題:
問題1:android開發中,需要使用到findviewById來進行初始化view,如果頁面,可能會看到幾十行的findviewById方法,而如果需要設置點擊事件,又要幾十行的代碼。
解決:ButterKnife,自動綁定了view,點擊事件等,代替了繁雜的手寫步驟。
問題2:用ButterKnife,就是如果一個頁面view過多的話,也需要一長串的 @bindView 代碼,導致一個頁面輕輕鬆鬆300行+的代碼量,看着也有點不舒服。而這些也都是重複性的工作,那麼有什麼辦法解決嗎?
解決:DataBinding!
MVVM
MVVM和MVP的區別,是把presenter層換成了viewmodel層,還有就是view層和viewmodel層是相互綁定的關係,這意味着當你更新viewmodel層的數據的時候,view層會相應的變動ui。視圖和數據的雙向綁定就是 通過Android Data Binding技術。
下面學習開始學習dataBinding。
一、配置databinding
首先在Module app下build.gradle中配置databinding
android {
dataBinding{
enabled = true
}
}
二、使用databinding
1、一個例子--簡單使用
先寫個Model實體類Man:
/**
* 俠客,ViewModel
*/
public class Man extends BaseObservable{
private String name;
private String level;
public Man(String name, String level) {
this.name = name;
this.level = level;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
}
然後佈局文件:
<?xml version="1.0" encoding="utf-8"?>
<!--根標籤-->
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<!--變量的定義-->
<data>
<variable
name="man" <!--變量名-->
type="com.hfy.demo01.module.mvvm.bean.Man" /> <!--變量類型-->
</data>
<!--傳統的視圖-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{man.name}" /> <!--變量的引用-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{man.level}" />
</LinearLayout>
</layout>
說明:
a.佈局根標籤爲:<layout>,由以前的<LinearLayout>改爲<layout>,注意 l 小寫。
b.定義變量的標籤:<data>、<variable>,變量的引用:@{man.level}
最後在MvvmActivity中對 數據和視圖 進行綁定。(要先Make Project)注意下面代碼的註釋1、2、3。
public class MvvmActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_mvvm);
//1、這裏使用DataBindingUtil.setContentView()
//2、ActivityMvvmBinding是在寫好<layout>佈局後,make project,自動生成的Binding輔助類,包含了佈局中所有的綁定關係
ActivityMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
//3、給 佈局中定義的變量man 進行賦值。
Man man = new Man("段譽","愛情高手");
binding.setMan(man);
}
運行結果如下圖。可見:我們沒有findViewById、setText,依然可以把數據展示出來。
2、事件處理,兩種方法如下圖
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<!--對象變量的定義-->
<variable
name="man"
type="com.hfy.demo01.module.mvvm.bean.Man" />
<variable
name="onClickListener"
type="android.view.View.OnClickListener" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{man.name}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{man.level}" /> <!--支持表達式-->
<!--事件處理的兩種方法-->
<!--方法一、先設置控件id,然後java代碼中使用Binding設置-->
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="button1" />
<!--方法二、引用變量-->
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{onClickListener}"
android:text="button2" />
</LinearLayout>
</layout>
//事件處理方法一,binding.
binding.btnTest.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
man.setName("段正淳");
}
});
//事件處理方法二:
binding.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MvvmActivity.this, "button2", Toast.LENGTH_SHORT).show();
}
});
3、佈局屬性
import用法,
<data>
<!--導入-->
<import type="com.hfy.demo01.module.mvvm.bean.Man" />
<!--對象變量的定義-->
<variable
name="man"
type="Man" />
</data>
變量定義,
<!--基本數據類型 的變量, java.lang.*會自動導入-->
<variable
name="home"
type="String" />
<variable
name="age"
type="int" />
<variable
name="isMale"
type="boolean" />
<!--集合變量的定義( <> 要用轉義符 )-->
<variable
name="list"
type="ArrayList<String>" />
<!-------->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{home}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{list.get(0)}" />
binding.setHome("安徽");
ArrayList<String> strings = new ArrayList<>();
strings.add("一");
strings.add("二");
binding.setList(strings);
靜態方法調用,
<!--靜態方法調用-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{Utils.getName(man)}" /> <!--要先導入Utils-->
public class Utils {
public static String getName(Man man) {
return man.getName();
}
}
Man man = new Man("段正淳","愛情高高高手");
binding.setMan(man);
支持表達式,(三目預算、+、!、&&、>= 等等)
<variable
name="isMale"
type="boolean" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{isMale? man.name + ":男的": man.name + ":女的"}' /> <!--支持表達式-->
Convert,把數據格式 轉爲需要的格式
<variable
name="time"
type="java.util.Date" />
public class Utils {
/**
* convertDate()這個方法在哪個類不重要,重要的是 @BindingConversion
* @param date
* @return
*/
@BindingConversion
public static String convertDate(Date date) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
String format = simpleDateFormat.format(date);
return format;
}
}
ActivityMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
binding.setTime(new Date());
三、動態更新 / 雙向綁定
此前例子中,Model實體類變化,UI是不會動態更新的。 接下來實現 動態更新:繼承BaseObservable
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="com.hfy.demo01.module.mvvm.bean.Man" />
<variable
name="man"
type="Man" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{man.name}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{man.level}" />
</LinearLayout>
</layout>
/**
* 俠客, 繼承BaseObservable
*/
public class Man extends BaseObservable {
private String name;
private String level;
public Man(String name, String level) {
this.name = name;
this.level = level;
}
/**
* @return
* @Bindable BR中生成一個對應的字段,BR編譯時生成,類似R文件
*/
@Bindable
public String getName() {
return name;
}
/**
* notifyPropertyChanged(BR.name),通知系統BR.name已發送變化,並更新UI
*
* @param name
*/
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
@Bindable
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
notifyPropertyChanged(BR.level);
}
}
ActivityMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
Man man = new Man("段譽","愛情高手");
binding.setMan(man);
// //事件處理方法一,binding.
binding.btnTest.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//Model實體類發生變化 -- UI會動態變化
man.setName("段正淳");
}
});
接下來就是雙向綁定:就是Model和View 通過VIewModel 進行雙向動態更新。Model實體類變化,VIew會自動更新;View發生變化,Model實體類也能動態改變。
<!--使用EditText通過 "@={}" 改變man.name的值-->
<!--UI EditText變化,實體類man也動態變化-->
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={man.name}"/>
五、結合RecyclerView
avtivity佈局:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!--這裏設置了id,就可以通過binding直接找到RecyclerView-->
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
</LinearLayout>
</layout>
item佈局:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="man"
type="com.hfy.demo01.module.mvvm.bean.Man" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{man.name}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{man.level}" />
</LinearLayout>
</layout>
activity:
public class RecyclerActivity extends AppCompatActivity {
private ActivityRecyclerBinding binding;
/**
* launch MvpActivity
*/
public static void launch(Activity activity) {
Intent intent = new Intent(activity, RecyclerActivity.class);
activity.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_recycler);
// 1、使用 DataBindingUtil.setContentView() 代替 setContentView(R.layout.activity_recycler);
binding = DataBindingUtil.setContentView(this, R.layout.activity_recycler);
//直接通過binding找到了recyclerView
LinearLayoutManager layout = new LinearLayoutManager(this);
layout.setOrientation(LinearLayoutManager.VERTICAL);
binding.recycler.setLayoutManager(layout);
binding.recycler.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
ManAdapter manAdapter = new ManAdapter(getList());
binding.recycler.setAdapter(manAdapter);
}
private List<Man> getList() {
List<Man> list = new ArrayList<>();
list.add(new Man("喬峯", "幫主"));
list.add(new Man("虛竹", "小和尚"));
return list;
}
private class ManAdapter extends RecyclerView.Adapter<ManViewHolder> {
private final List<Man> mList;
public ManAdapter(List<Man> list) {
mList = list;
}
@NonNull
@Override
public ManViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
//2、這裏是把item_layout對應的Binding 注入iewHolder(以前是View)
ItemLayoutRecyclerBinding manViewBinding = DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()), R.layout.item_layout_recycler, viewGroup, false);
return new ManViewHolder(manViewBinding);
}
@Override
public void onBindViewHolder(@NonNull ManViewHolder manViewHolder, int i) {
//3、綁定只用使用Binding,很簡潔。
manViewHolder.manViewBinding.setMan(mList.get(i));
}
@Override
public int getItemCount() {
return mList.size();
}
}
private class ManViewHolder extends RecyclerView.ViewHolder {
private final ItemLayoutRecyclerBinding manViewBinding;
//4、這裏沒有findViewById()去找各個view。就holder了Binding。
public ManViewHolder(ItemLayoutRecyclerBinding manViewBinding) {
super(manViewBinding.getRoot());
this.manViewBinding = manViewBinding;
}
}
}
結果:
RecyclerView功能強大,使用DataBinding來處理RecyclerView Item 再合適不過,做到了數據和itemView的完美分離,告別了反覆、冗餘的自定義Adapter,不需要關心太多無意義的事。
六、使用注意點
1、儘可能少的variable和import。 過多的variable除了會讓你多做幾次無謂的綁定外,數據也將變得難以管理。
bad
<data>
<import type="com.ditclear.app.util.DateUtil"/>
<import type="android.view.View"/>
<import type="com.ditclear.app.util.StringUtil"/>
<import type="com.ditclear.app.network.model.StudentState"/>
<import type="java.math.BigDecimal"/>
<import type="com.ditclear.app.presentation.student.StudentActivity"/>
<variable
name="isShow"
type="Boolean"/>
<variable
name="time"
type="java.util.Date"/>
<variable
name="date"
type="java.util.Date"/>
<variable
name="signTime"
type="String"/>
<variable
name="item"
type="com.ditclear.app.network.model.student.StudentItem"/>
<variable
name="presenter"
type="com.ditclear.app.presentation.student.StudentActivity.Presenter"/>
</data>
better
<data>
<variable
name="vm"
type="com.ditclear.app.view.student.viewmodel.StudentViewModel"/>
</data>
數據的處理和view的顯示都用ViewModel來處理,比如:
android:text="@{vm.signTime}"
android:visibility="@{vm.isShowVisbility}"
好處是減輕了layout.xml文件的複雜程度,xml文件只用來單純的展示數據, 而複雜的邏輯判斷都放在了ViewModel中,並且爲頁面佈局數據提供了唯一的入口,方便查看和管理數據源。
2、避免使用複雜的表達式,下面這種的邏輯判斷最好放在ViewModel中,由ViewModel提供對應的變量。
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{isMale? man.name + ":男的": man.name + ":女的"}' /> <!--支持表達式-->
而且,剛入門DataBinding的人都遇到過找不到binding文件的錯,可能是表達式寫錯、variable的類路徑不對或者沒import相應的類(比如View)等等,都與複雜的表達式有關係,而DataBinding報錯也不太智能,有時不能準確定位,所以建議不要在xml裏進行復雜的數據綁定,這些都儘量放到ViewModel裏進行,xml只綁定簡單的基礎數據類型。
七、Android Studio插件 Databinding Support
https://plugins.jetbrains.com/plugin/9271-databinding-support
參考:
《Android進階之光》
推薦閱讀 詳細系列文章: