一、DataBinding介紹
2015年穀歌I/O大會上介紹了一個框架DataBinding,DataBinding是一個數據綁定框架,以前我們在Activity裏寫很多的findViewById,很多人不想寫這個於是用了一些註解框架,可是註解框架無論性能多好,效率總是要低於findViewById的,現在如果我們使用DataBinding,就可以拋棄findViewById。DataBinding主要解決了兩個問題:
反射,註解(butterKnife)、DataBinding
- 需要多次使用findViewById,損害了應用性能且令人厭煩
- 更新UI數據需切換至UI線程,將數據分解映射到各個view比較麻煩
就讓我們具體來看看怎麼使用它吧。
二、DataBinding的基本使用
(1)DataBinding的導入
在應用的build.gradle文件中添加如下代碼:
android {
...
//導入dataBinding支持
dataBinding{
enabled true
}
...
}
注:如果出現找不到databinding的library的話是因爲跟gradle的版本不兼容,改成2.0.0
classpath 'com.android.tools.build:gradle:2.0.0'
(2)DataBinding基本使用包括以下內容:
● 單純的擺脫findviewbyid
● 綁定基本數據類型及String
● 綁定Model數據
● 綁定事件
● 通過靜態方法轉換數據類型
● 通過運算符操作數據
● 自定義Binding的類名
● 綁定相同Model的操作
● model變量改變自動更新數據
● 綁定List/Map等集合數據
● Observable自動更新
● Databinding與include標籤的結合
● DataBinding與RecyclerView的結合
● DataBinding和Fragment的使用
● DataBinding在Activity或Fragment中加載其他佈局
● DataBinding實現自定義DataBindingAdapter
(3)功能使用
3.1單純的擺脫findviewbyid
佈局文件
<!--佈局以layout作爲根佈局-->
<layout>
<!--我們需要展示的佈局-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="www.zhang.com.databinding.MainActivity">
<Button
android:id="@+id/main_tv1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="單純的擺脫findviewbyid" />
</LinearLayout>
</layout>
佈局通過DataBindingUtils.setContentView()加載到代碼中,而且會生成對應一個Binding對象,對象名是佈局文件文稱加上Binding後綴
MainActivity中代碼
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;//這個類是自動生成的
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//通過DataBInding加載佈局都會對應的生成Binding對象,如ActivityMainBinding,對象名在佈局文件名稱後加上了一個後綴Binding
binding = DataBindingUtil.setContentView(MainActivity.this, R.layout.activity_main);
//通過binfding.id名稱---就相當於獲取了改控件對象了
binding.mainTv1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
binding.mainTv1.setText("沒有findviewbyid了");
}
});
}
}
通過Binding對象.id名稱,就相當於拿到了指定的佈局中的id的控件了,使用起來和findviewbyid獲取的控件是一樣的
3.2綁定基本數據和String
佈局文件
<?xml version="1.0" encoding="utf-8"?><!--佈局以layout作爲根佈局-->
<layout>
<data>
<!--綁定基本數據類型及String-->
<!--name: 和java代碼中的對象是類似的,名字自定義-->
<!--type: 和java代碼中的類型是一致的-->
<variable
name="content"
type="String" />
<variable
name="enabled"
type="boolean" />
</data>
<!--我們需要展示的佈局-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!--綁定基本數據類型及String的使用是通過 @{數據類型的對象} 通過對應數據類型的控制顯示-->
<Button
android:id="@+id/main_tv2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="@{enabled}"
android:text="@{content}" />
</LinearLayout>
</layout>
在佈局中是通過@{}來綁定數據的,{}中是佈局中該控件屬性對應的數據類型數據,同時還可以支持運算符運算和靜態方法調用和轉換,這個後面會介紹
MainActivity文件中
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//通過DataBInding加載佈局都會對應的生成一個對象,如ActivityMainBinding,對象名在佈局文件名稱後加上了一個後綴Binding
binding = DataBindingUtil.setContentView(MainActivity.this, R.layout.activity_main);
//2.綁定基本數據類型及String
binding.setContent("對String類型數據的綁定");//setContent就是給佈局文件text屬性綁定的content設置值
binding.setEnabled(false);//設置enabled值爲false
//給控件設置點擊事件,發現其實點擊無效,因爲在佈局文件中給cilckable屬性綁定了enabled,在代碼中設置enabled值爲false,所以點擊事件無效
binding.mainTv2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "我被點擊了", Toast.LENGTH_SHORT).show();
}
});
}
}
3.3綁定model數據
Model數據類型
public class User {
private String text;
public User(String text) {
this.text = text;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
後面會對model數據做更詳細的講解,這裏先讓大家知道一下用法
佈局文件
<?xml version="1.0" encoding="utf-8"?><!--佈局以layout作爲根佈局-->
<layout>
<data>
<!--綁定Model數據2中形式,一中是導入該類型的,一種指定類型的全稱,和java一樣-->
<!-- 方式一 -->
<variable
name="user"
type="www.zhang.com.databinding.User" />
<!-- 方式二 -->
<!--<import type="www.zhang.com.databinding.User" />-->
<!--<variable-->
<!--name="user"-->
<!--type="User" />-->
</data>
<!--我們需要展示的佈局-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/main_btn3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{user.text}" /><!--這裏user.text相當於user.getText()-->
</LinearLayout>
</layout>
MainActivity中代碼:
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//通過DataBInding加載佈局都會對應的生成一個對象,如ActivityMainBinding,對象名在佈局文件名稱後加上了一個後綴Binding
binding = DataBindingUtil.setContentView(MainActivity.this, R.layout.activity_main);
//3.綁定model對象數據
User user = new User("綁定Model數據類型");
binding.setUser(user);//或者 binding.setVariable(BR.user,user);
}
}
●
binding設置數據有2中方式:
1.binding.setUser(user)
2.binding.setVariable(BR.user,user)–採用BR指定
3.4事件的綁定
佈局文件
<?xml version="1.0" encoding="utf-8"?><!--佈局以layout作爲根佈局-->
<layout>
<data>
<variable
name="event"
type="www.zhang.com.databinding.model.EventListener" />
<variable
name="title1"
type="String" />
<variable
name="title2"
type="String" />
<variable
name="title3"
type="String" />
<variable
name="title4"
type="String" />
</data>
<!--我們需要展示的佈局-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{event.click1}"
android:text="@{title1}" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{event::click2}"
android:text="@{title2}" />
<!--android:onClick="@{event::click2}" 編譯報錯沒關係,可以運行的-->
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{()->event.cilck3(title4)}"
android:text="@{title3}" />
</LinearLayout>
</layout>
事件有多種寫法
1.android:onClick="@{event.click1}"
2.android:onClick="@{event::click2}"
3.android:onClick="@{()->event.cilck3(title4)}"
[注]:()->event.cilck3(title4)是lambda表達式寫法,
也可以寫成:(view)->event.cilck3(title4),前面(view)表示onClick方法的傳遞的參數,
如果event.click3()方法中不需要用到view參數,可以將view省略。
當然event.click1也可以寫成(view)->event.click1(view),將onClick(View view)的view參數傳遞給event.click1(view)方法。
大概就這意思,以下是僞代碼
onclick(View view){
event.click1(view)
}
EventListener接口
public interface EventListener{
public void click1(View v);
public void click2(View v);
public void cilck3(String s);
}
MainActivity
public class MainActivity extends AppCompatActivity{
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//通過DataBInding加載佈局都會對應的生成一個對象,如ActivityMainBinding,對象名在佈局文件名稱後加上了一個後綴Binding
binding = DataBindingUtil.setContentView(MainActivity.this, R.layout.activity_main);
binding.setTitle1("事件綁定1");
binding.setTitle2("事件綁定2");
binding.setTitle3("事件綁定3");
binding.setTitle4("change ok");
binding.setEvent(new EventListener() {
@Override
public void click1(View v) {
binding.setTitle1("事件1方法調用");
}
@Override
public void click2(View v) {
binding.setTitle2("事件2方法調用");
}
@Override
public void cilck3(String s) {
binding.setTitle3(s);
}
});
}
}
3.5通過靜態方法轉化數據類型
佈局文件
<?xml version="1.0" encoding="utf-8"?><!--佈局以layout作爲根佈局-->
<layout>
<data>
<variable
name="user"
type="www.zhang.com.databinding.User" />
<!--調用靜態方法,需要先導入需要的包 當然java中的lang包可以不用導包-->
<import type="www.zhang.com.databinding.Utils" />
</data>
<!--我們需要展示的佈局-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="www.zhang.com.databinding.MainActivity">
<Button
android:id="@+id/main_btn5"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{Utils.getName(user)}" /><!--就和java中寫代碼一樣,只要符合數據類型就好-->
</LinearLayout>
</layout>
靜態方法的類(當然也可以使用Java自帶API)
public class Utils {
public static String getName(Object o) {
return o.getClass().getName();
}
}
3.6通過運算符操作數據
佈局文件
<?xml version="1.0" encoding="utf-8"?><!--佈局以layout作爲根佈局-->
<layout>
<data>
<variable
name="user2"
type="www.zhang.com.databinding.model.User" />
</data>
<!--我們需要展示的佈局-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="www.zhang.com.databinding.MainActivity">
<Button
android:id="@+id/main_btn5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="@{user2.state ? @dimen/largepadding : (int)@dimen/smallpadding}"
android:text="@{user2.content ?? @string/app_name}" />
<!-- android:text="@{user2.content ?? @string/app_name}"
等價於
android:text="@{user2.content!=null? user2.content : @string/app_name}"-->
<Button
android:id="@+id/main_btn6"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{`Hello World`+ @string/app_name}" /><!-- ``字符包裹的表示字符串,可用作拼接字符串 -->
</LinearLayout>
</layout>
public class User {
private String content;
private boolean state;
public User(String content, boolean state) {
this.content = content;
this.state = state;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public boolean isState() {
return state;
}
public void setState(boolean state) {
this.state = state;
}
}
MainActivity中
package www.zhang.com.databinding;
import android.databinding.DataBindingUtil;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import www.zhang.com.databinding.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//通過DataBInding加載佈局都會對應的生成一個對象,如ActivityMainBinding,對象名在佈局文件名稱後加上了一個後綴Binding
binding = DataBindingUtil.setContentView(MainActivity.this, R.layout.activity_main);
//運算符操作
www.zhang.com.databinding.model.User user2 = new www.zhang.com.databinding.model.User("通過運算符操作數據", true);
// user2 = new www.zhang.com.databinding.model.User(null, false); 改變數據作爲測試DataBinding是否有效
binding.setUser2(user2);
}
}
DataBinding支持的表達式有:
數學表達式: + - / * %
字符串拼接 +
邏輯表達式 && ||
位操作符 & | ^
一元操作符 + - ! ~
位移操作符 >> >>> <<
比較操作符 == > < >= <=
instanceof
分組操作符 ()
字面量 - character, String, numeric, null
強轉、方法調用
字段訪問
數組訪問 []
三元操作符 ?:
聚合判斷(Null Coalescing Operator)語法 ‘??’
3.7自定義Binding的類名
data標籤有個class屬性,它可以用來對Binding對象重新命名(一般是以佈局文件名加上Binding後綴作爲該Binding類名)
文件目錄如下
自定義類名有3中方式
1. 通過指定全類名的方式
<data class="www.zhang.com.databinding.activity.Item">
...
</data>
import www.zhang.com.databinding.activity.Item;
Item binding = DataBindingUtil.setContentView(FiveActivity.this, R.layout.activity_five);
- 直接生成在項目的包目錄下
<data class=".Item">
...
</data>
import www.zhang.com.databinding.Item;
Item binding = DataBindingUtil.setContentView(FiveActivity.this, R.layout.activity_five);
- 如果FiveActivity直接在包下與方式二相同,如果FiveActivity在包的子目錄下,則Binding生成的目錄如下
<data class="Item">
...
</data>
import www.zhang.com.databinding.databinding.Item;
Item binding =DataBindingUtil.setContentView(FiveActivity.this, R.layout.activity_five);
3.8綁定相同model的操作
綁定相同model我的理解有2種,一種是同類的2個對象,另一種是不同類的2個類,但類名相同,具體看代碼:
● 第一種,同一個類的2個對象,只需對象名不同就可以
佈局文件
<?xml version="1.0" encoding="utf-8"?><!--佈局以layout作爲根佈局-->
<layout>
<data>
<import type="www.zhang.com.databinding.User" />
<variable
name="user3"
type="User" />
<variable
name="user4"
type="User" />
</data>
<!--我們需要展示的佈局-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="www.zhang.com.databinding.MainActivity">
<Button
android:id="@+id/main_btn7"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{user3.text}" />
<Button
android:id="@+id/main_btn8"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{user4.text}" />
</LinearLayout>
</layout>
User類
public class User {
private String text;
public User(String text) {
this.text = text;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
MainActivity中
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//通過DataBInding加載佈局都會對應的生成一個對象,如ActivityMainBinding,對象名在佈局文件名稱後加上了一個後綴Binding
binding = DataBindingUtil.setContentView(MainActivity.this, R.layout.activity_main);
User user3 = new User("相同類,不同對象1");
binding.setUser3(user3);
User user4 = new User("相同類,不同對象2");
binding.setUser4(user4);
}
}
● 第二種不同的2個類,對象名相同
佈局文件
<?xml version="1.0" encoding="utf-8"?><!--佈局以layout作爲根佈局-->
<layout>
<data>
<import type="www.zhang.com.databinding.User" />
<variable
name="user4"
type="User" />
<!--因爲type="User"都爲User類,會導致不知道到那個包,所以可以通過alias屬性重命名type的類型,但實際上alias被指定的那個類型(如:www.zhang.com.databinding.model.User)-->
<import type="www.zhang.com.databinding.model.User" alias="Model"/>
<variable
name="user5"
type="Model" />
</data>
<!--我們需要展示的佈局-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="www.zhang.com.databinding.MainActivity">
<Button
android:id="@+id/main_btn8"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{user4.text}" />
<Button
android:id="@+id/main_btn9"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{user5.content}" />
</LinearLayout>
</layout>
因爲2個地方都有type=”User”都爲User類,會導致不知道導入哪個包,所以可以通過alias屬性重命名type的類型,但實際上是alias被指定的那個類型(如:www.zhang.com.databinding.model.User)
2個不同的User類
package www.zhang.com.databinding;
public class User {
private String text;
public User(String text) {
this.text = text;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
package www.zhang.com.databinding.model;
public class User {
private String content;
private boolean state;
public User(String content, boolean state) {
this.content = content;
this.state = state;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public boolean isState() {
return state;
}
public void setState(boolean state) {
this.state = state;
}
}
MainActivity
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//通過DataBInding加載佈局都會對應的生成一個對象,如ActivityMainBinding,對象名在佈局文件名稱後加上了一個後綴Binding
binding = DataBindingUtil.setContentView(MainActivity.this, R.layout.activity_main);
User user4 = new User("相同類,不同對象2");
binding.setUser4(user4);
www.zhang.com.databinding.model.User user5 = new www.zhang.com.databinding.model.User("類名相同,但不是相同的2個類",false);
binding.setUser5(user5);
}
}
3.9model變量改變自動更新數據
前面講了DataBinding對基本數據類型及Model的使用以及運算符等操作,但這些基本是靜態的去設置數據,現在我們來看看 如何在數據改變時,不手動設置,讓其自動改變。
佈局文件
<?xml version="1.0" encoding="utf-8"?><!--佈局以layout作爲根佈局-->
<layout>
<data>
<import type="www.zhang.com.databinding.model.Person" />
<variable
name="person"
type="Person" />
</data>
<!--我們需要展示的佈局-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="@{`firstName:`+person.firstName}" />
<TextView
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="@{`lastName:`+person.lastName}" />
<TextView
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="@{`age:`+person.age}" />
<Button
android:id="@+id/second_btn1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="改變數據" />
</LinearLayout>
</layout>
Person類
package www.zhang.com.databinding.model;
import android.databinding.BaseObservable;
import android.databinding.Bindable;
import www.zhang.com.databinding.BR;
public class Person extends BaseObservable {
private String firstName;
private String lastName;
private int age;
public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
@Bindable
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
@Bindable
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
@Bindable
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
notifyPropertyChanged(BR.age);
}
}
Model類繼承BaseObservable,BaseObservable實現Android.databinding.Observable接口,Observable接口可以允許附加一個監聽器到model對象以便監聽對象上的所有屬性的變化。
Observable接口有一個機制來添加和刪除監聽器,但通知與否由開發人員管理。爲了使開發更容易,BaseObservable實現了監聽器註冊機制。DataBinding實現類依然負責通知當屬性改變時。這是通過指定一個Bindable註解給getter以及setter內通知來完成的。
notifyPropertyChanged(BR.參數名)通知更新這一個參數,需要與@Bindable註解配合使用。notifyChange()通知更新所有參數,可以不用和@Bindable註解配合使用
SecondActivity
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivitySecondBinding binding = DataBindingUtil.setContentView(SecondActivity.this, R.layout.activity_second);
final Person person = new Person("zhang","san",38);
binding.setPerson(person);
binding.secondBtn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
person.setFirstName("li");
person.setLastName("si");
person.setAge(40);
}
});
}
}
3.10綁定List/Map等集合數據
佈局文件
<?xml version="1.0" encoding="utf-8"?><!--佈局以layout作爲根佈局-->
<layout>
<data>
<import type="java.util.ArrayList" />
<import type="java.lang.String" />
<variable
name="list"
type="ArrayList<String>" />
<!--泛型的支持會在編譯時期報紅線,但是是可以直接運行的
但是需要通過轉義字符才行,如:<數據類型> 或者<數據類型>-->
<import type="java.util.Map" />
<variable
name="map"
type="Map<String,String>" />
<variable
name="arrays"
type="String[]" />
</data>
<!--我們需要展示的佈局-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="@{list[0]}" />
<!--List集合既可以和數組一樣通過索引獲取值list[index]方式,也可以調用API-->
<TextView
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="@{list.get(1)}" />
<TextView
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="@{map[`name`]}" />
<!--Map集合既可以通過map[key]的方式,也可以通過調用API-->
<TextView
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="@{map.get(`age`)}" />
<TextView
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="@{arrays[0]}" />
<TextView
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="@{arrays[1]}" />
</LinearLayout>
</layout>
泛型的支持會在編譯時期報紅線,是可以直接運行的,但是需要通過轉義字符才行,如:\<數據類型> 或者\<數據類型>
ThirdActivity
public class ThirdActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityThirdBinding binding = DataBindingUtil.setContentView(ThirdActivity.this, R.layout.activity_third);
ArrayList<String> list = new ArrayList<>();
list.add("first");
list.add("second");
binding.setList(list);
Map<String, String> map = new HashMap<>();
map.put("name", "zhangsan");
map.put("age", "40");
binding.setMap(map);
String[] arrays = {"lisi", "laowang"};
binding.setArrays(arrays);
}
}
3.11 Observable數據改變自動更新
Observable是一個接口,它的子類有BaseObservable,ObservableField,ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable,ObservableArrayList,ObservableArrayMap
<?xml version="1.0" encoding="utf-8"?><!--佈局以layout作爲根佈局-->
<layout>
<data>
<import type="www.zhang.com.databinding.model.Animal"/>
<variable
name="animal"
type="Animal"/>
<variable
name="list"
type="android.databinding.ObservableArrayList<String>"/>
<variable
name="map"
type="android.databinding.ObservableArrayMap<String,String>"/>
</data>
<!--我們需要展示的佈局-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="@{animal.field}" />
<TextView
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="@{String.valueOf(animal.age)}" />
<TextView
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="@{list[0]}" />
<!--Map集合既可以通過map[key]的方式,也可以通過調用API-->
<TextView
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="@{list[1]}" />
<TextView
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="@{map[`name`]}" />
<TextView
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="@{map[`age`]}" />
<Button
android:id="@+id/four_btn"
android:layout_width="match_parent"
android:text="改變數據"
android:layout_height="wrap_content" />
</LinearLayout>
</layout>
Animal類
public class Animal {
public final ObservableField field = new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
FourActivity
package www.zhang.com.databinding;
public class FourActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityFourBinding binding = DataBindingUtil.setContentView(FourActivity.this, R.layout.activity_four);
final Animal animal = new Animal();
animal.field.set("cat");
animal.age.set(2);
binding.setAnimal(animal);
final ObservableArrayList<String> list = new ObservableArrayList<>();
list.add("dog");
list.add("mouse");
binding.setList(list);
final ObservableArrayMap<String, String> map = new ObservableArrayMap<>();
map.put("name","Tom");
map.put("age","4");
binding.setMap(map);
binding.fourBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
animal.field.set("dog");
animal.age.set(4);
list.set(0,"cat");
list.set(1,"dog");
map.put("name","Sam");
map.put("age","5");
}
});
}
當Animal屬性數據改變,list/map集合數據改變,會自動更新數據,我們不需要自己手動設置,省去了一個操作,讓我們更專注於業務邏輯。
3.12 Databinding與include標籤的結合
佈局文件
activity_five.xml
<?xml version="1.0" encoding="utf-8"?><!--佈局以layout作爲根佈局-->
<layout>
<data >
<import type="www.zhang.com.databinding.model.Content"/>
<variable
name="con"
type="Content"/>
</data>
<!--我們需要展示的佈局-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="@+id/toolbar"
layout="@layout/toolbar"
android:layout_height="56dp"
android:layout_width="match_parent"
bind:content="@{con}" />
<!--通過命名空間將寫有toolbar的xml文件中定義的content對象作爲屬性綁定con對象,這2個對象是同一個類-->
<TextView
android:text="@string/app_name"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</layout>
toolbar.xml
<?xml version="1.0" encoding="utf-8"?>
<layout >
<data>
<import type="www.zhang.com.databinding.model.Content"/>
<variable
name="content"
type="Content"/>
</data>
<android.support.v7.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/toolbar"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="56dp"
android:layout_width="match_parent"
app:title="@{content.title}"
app:subtitle="@{content.subTitle}"
android:background="@color/colorPrimary"
app:titleTextColor="@android:color/white"
app:subtitleTextColor="@android:color/white" />
</layout>
在activity_five.xml中的include屬性中定義了一個id,同時又在toolbar.xml中的Toolbar標籤中又定義了一個id,其作用是通過binding.toolbar.toolbar等同於Toolbar控件,可以方便做一些操作等
FiveActivity中
public class FiveActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
ActivityFiveBinding binding =DataBindingUtil.setContentView(FiveActivity.this, R.layout.activity_five);
Content con = new Content("Title","SubTitle");
binding.setCon(con);
// binding.toolbar.setContent(con); //這個測試沒有效果,不會顯示toolbar的title/subTitle
// binding.toolbar.toolbar.setTitle("");
// binding.toolbar.toolbar.setSubtitle("");
//下面的代碼也可以通過DataBinding綁定數據
binding.toolbar.toolbar.setNavigationIcon(R.mipmap.ic_launcher);
setSupportActionBar(binding.toolbar.toolbar);
binding.toolbar.toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
}
Content
public class Content extends BaseObservable {
private String title;
private String subTitle;
public Content(String title, String subTitle) {
this.title = title;
this.subTitle = subTitle;
}
@Bindable public String getSubTitle() {
return subTitle;
}
public void setSubTitle(String subTitle) {
this.subTitle = subTitle;
notifyPropertyChanged(BR.subTitle);
}
@Bindable public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
notifyPropertyChanged(BR.title);
}
}
3.13 DataBinding與RecyclerView的結合
佈局文件
activity_five.xml
<?xml version="1.0" encoding="utf-8"?><!--佈局以layout作爲根佈局-->
<layout>
<data >
<import type="www.zhang.com.databinding.model.Content"/>
<variable
name="con"
type="Content"/>
</data>
<!--我們需要展示的佈局-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="@+id/toolbar"
layout="@layout/toolbar"
android:layout_height="56dp"
android:layout_width="match_parent"
bind:content="@{con}" />
<!--通過命名空間將寫有toolbar的xml文件中定義的content對象作爲屬性綁定con對象,這2個對象是同一個類-->
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</layout>
recycler_item.xml
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="str"
type="String"/>
</data>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:orientation="vertical">
<TextView
android:text="@{str}"
android:gravity="center_vertical"
android:textColor="@android:color/black"
android:textSize="16sp"
android:layout_width="match_parent"
android:layout_height="48dp" />
</LinearLayout>
</layout>
FiveActivity
public class FiveActivity extends AppCompatActivity {
private ActivityFiveBinding binding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
binding = DataBindingUtil.setContentView(FiveActivity.this, R.layout.activity_five);
initToolbar();
initRecyclerView();
}
private void initRecyclerView() {
LinearLayoutManager manager = new LinearLayoutManager(FiveActivity.this);
binding.recycler.setLayoutManager(manager);
binding.recycler.setHasFixedSize(true);
MyAdapter adapter = new MyAdapter(getApplicationContext());
binding.recycler.setAdapter(adapter);
}
private void initToolbar() {
Content con = new Content("Title","SubTitle");
binding.setCon(con);
// binding.toolbar.setContent(con); //這個測試沒有效果,不會顯示toolbar的title/subTitle
// binding.toolbar.toolbar.setTitle("");
// binding.toolbar.toolbar.setSubtitle("");
//下面的代碼也可以通過DataBinding綁定數據
binding.toolbar.toolbar.setNavigationIcon(R.mipmap.ic_launcher);
setSupportActionBar(binding.toolbar.toolbar);
binding.toolbar.toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
}
MyAdapter
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private Context mContext;
String[] datas;
public MyAdapter(Context context) {
mContext = context;
datas = context.getResources().getStringArray(R.array.item_list);
}
@Override
public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerItemBinding binding = DataBindingUtil.inflate(LayoutInflater.from(mContext), R.layout.recycler_item, parent, false);
return new MyViewHolder(binding);
}
@Override
public void onBindViewHolder(MyAdapter.MyViewHolder holder, int position) {
String name = datas[position];
holder.getBinding().setVariable(www.zhang.com.databinding.BR.str,name);
//holder.getBinding().setStr(name); //兩者都可以
//executePendingBindings()方法說明
// When a variable or observable changes, the binding will be scheduled to change before the next frame.
// There are times, however, when binding must be executed immediately.
// To force execution, use the executePendingBindings() method.
holder.getBinding().executePendingBindings();//此方法必須執行在UI線程,當綁定的數據修改時更新視圖(不知道翻譯的準不準)
}
@Override
public int getItemCount() {
return datas.length;
}
public class MyViewHolder extends RecyclerView.ViewHolder {
private RecyclerItemBinding binding;
public MyViewHolder(ViewDataBinding binding) {
super(binding.getRoot());
this.binding = (RecyclerItemBinding) binding;
}
public RecyclerItemBinding getBinding() {
return binding;
}
public void setBinding(RecyclerItemBinding binding) {
this.binding = binding;
}
}
}
3.14 DataBinding在Fragment中使用
DataBinding在Fragment中使用和Activity中使用大體相同不同的是獲取對象要在
DataBindingUtil.inflate(inflater, R.layout.homepage_fragment, container, false);
3.15 DataBinding在Activity或Fragment中加載其他佈局
此處以PopupWindow 爲例,其他如Dialog等類同;
ItemMapInfoBinding infoBinding = ItemMapInfoBinding.inflate(getLayoutInflater());
View popupView = infoBinding.getRoot();
PopupWindow mPopupWindow = new PopupWindow(mContext);
mPopupWindow.setContentView(popupView);
3.16 重新BindingAdapter來實現動畫等非點擊事件
安卓上新的binding framework可以輕鬆實現視圖根據model的改變而改變。你只需要讓model能被觀測,然後framework會做好其餘的事情。比如,你可以通過下面的代碼來實現一個加載提示的顯示和隱藏:
@BindingAdapter("fadeVisible")
public static void setFadeVisible(final View view, boolean visible) {
view.animate().cancel();
if (visible) {
view.setVisibility(View.VISIBLE);
view.setAlpha(0);
view.animate().alpha(1).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
view.setAlpha(1);
}
});
} else {
view.animate().alpha(0).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
view.setAlpha(1);
view.setVisibility(View.GONE);
}
});
}
}
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:fadeVisible="@{model.loading}"/>
但是,這種方式有個問題,每次值發生改變的時候都會產生動畫效果,即使是在第一次綁定的時候。比如,你會在旋轉屏幕的時候也看到淡入淡出。我們需要一種能判斷是否是第一次綁定的辦法。
可以利用view的tag不被保持的特點,如果沒有tag,則這是第一次綁定,我們設置tag而不運行動畫:
@BindingAdapter("fadeVisible")
public static void setFadeVisible(final View view, boolean visible) {
if (view.getTag() == null) {
view.setTag(true);
view.setVisibility(visible ? View.VISIBLE : View.GONE);
} else {
view.animate().cancel();
if (visible) {
view.setVisibility(View.VISIBLE); view.setAlpha(0);
view.animate().alpha(1).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
view.setAlpha(1);
}
});
} else {
view.animate().alpha(0).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
view.setAlpha(1);
view.setVisibility(View.GONE);
}
});
}
}
}
嗯,就是這樣,加載提示便會很好的根據model的變化正確的顯示淡入淡出了。