Android新推出了一個(gè)官方的數(shù)據(jù)綁定框架Data Binding Library,既然是官方推出的新玩意,我們就有必要了解一下Android新帶來的數(shù)據(jù)綁定框架,等到該框架推出正式版的時(shí)候,我們就可以快速地運(yùn)用到項(xiàng)目中去。數(shù)據(jù)綁定框架給我們帶來了很大的方便性,以前我們可能需要在Activity里寫很多的findViewById,煩人的代碼也增加了我們代碼的耦合性,現(xiàn)在我們馬上就可以拋棄那些findViewById。說到這里,有人可能會問:我使用的一些注解框架也可以不用findViewById啊,是的,但是注解的缺點(diǎn)是拖累代碼的效率,Data Binding則不會,Android官方文檔說還會提高解析XML的速度,最主要的是Data Binding并不是單單減少我們的findViewById,更多的好處我們接下來一起探尋。
1.環(huán)境
使用最新的Android Studio,并更新你的Suport Repository到最新的版本,確保Android Studio的Gradle插件不低于1.5.0
classpath 'com.android.tools.build:gradle:1.5.0'
然后修改對應(yīng)模塊(Module)的build.gradle,添加如下腳本代碼:
android {
//添加DataBinding Library
dataBinding {
enabled true
}
}
最后,點(diǎn)擊Sync同步一下Gradle即可完成環(huán)境配置
2.Data Binding示例
首先,我們需要新建一個(gè)Java Bean,一個(gè)簡單的學(xué)生類。
package com.example.iaiai.databinding;
/**
* Created by iaiai on 2016/2/16.
*/
public class Student {
private String name;
private String addr;
public Student() {
}
public Student(String name,String addr) {
this.addr = addr;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
}
其次,編寫布局文件data_binding.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<data>
<variable
name="stu"
type="com.example.iaiai.databinding.Student" />
</data>
<LinearLayout
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{stu.name}"/> //也可以是android:text="@{stu.getName()}"
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{stu.addr}"/> //也可以是android:text="@{stu.getAddr()}"
</LinearLayout>
</layout>
最后,實(shí)現(xiàn)MainActivity,為變量賦值
import com.example.iaiai.databinding.databinding.DataBindingBinding;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DataBindingBinding binding = DataBindingUtil.setContentView(this, R.layout.data_binding);
binding.setStu(new Student("lee", "Shenzhen"));
}
由上面可以看出,MainActivity的代碼非常簡單,就添加了兩行代碼,需要注意的是我們并沒有findViewById然后再去setText。
3.Data Binding詳解
上面的示例僅僅是帶領(lǐng)我們進(jìn)入了Data Binding的世界,接下來我們解釋一下Data Binding的開發(fā)步驟。先看看上面的布局文件。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<data>
<variable
name="stu"
type="com.example.iaiai.databinding.Student" />
</data>
.....
根節(jié)點(diǎn)使用的是layout,在layout中分成兩部分,第一部分是data節(jié)點(diǎn),第二部分才是我們布局的根節(jié)點(diǎn),在data節(jié)點(diǎn)下我們定義了一個(gè)variable,它是一個(gè)變量,變量名稱是stu,類型是com.example.iaiai.databinding.Student,這類似我們在java文件中的定義:
com.example.iaiai.databinding.Student stu;
不過這里要寫Student完整的包名,如果這里我們需要多個(gè)Student呢?我們可以像寫java文件那樣導(dǎo)入類包
<layout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<data>
<!--導(dǎo)入類包-->
<import type="com.example.iaiai.databinding.Student"/>
<variable
name="stu"
type="Student" />
</data>
.....
</layout>
這樣就類似于java中的
import com.example.iaiai.databinding.Student;
Student stu1,stu2,...
既然變量定義好了,那該怎么使用呢?我們?nèi)匀豢瓷厦娴膞ml文件
<layout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
....
<LinearLayout
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{stu.name}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{stu.addr}"/>
</LinearLayout>
</layout>
由上面可以看出,兩個(gè)TextView的android:text,它的值是以@開始,以{}包裹的形式出現(xiàn),而值呢?是stu.name,stu就是上面定義的variable,name就是Student類中的成員變量,其實(shí)這里就會去調(diào)用stu.getName()方法。
最后,我們看看如何給變量賦值呢?如下代碼:
import com.example.iaiai.databinding.databinding.DataBindingBinding;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DataBindingBinding binding = DataBindingUtil.setContentView(this, R.layout.data_binding);
binding.setStu(new Student("lee", "Shenzhen")); //賦值
}
大部分情況,我們會在Activity中去使用它,以前我們都是在OnCreate方法中通過setContextView去設(shè)置布局。但現(xiàn)在不一樣了,現(xiàn)在我們是通過DataBindingUtil類的一個(gè)靜態(tài)方法setContentView設(shè)置布局,同時(shí)該方法會返回一個(gè)對象,這個(gè)對象時(shí)一個(gè)自動生成的類的對象,如DataBindingBinding?那么它的命名規(guī)則是什么呢?將我們布局文件的首字母大寫,并且去掉下劃線,將下劃線后面的字母大寫,加上后綴Binding組成。最后,我們通過這個(gè)對象來給變量賦值。
通過以上分析,我們了解Data Binding的具體開發(fā)步驟,下面讓我們定義不同的幾個(gè)變量看看
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="com.example.iaiai.databinding.Student" />
<variable
name="stu"
type="Student" />
<variable
name="str"
type="String"/>
<variable
name="error"
type="boolean"/>
<variable
name="num"
type="int" />
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{stu.name}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{str}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(num)}"/>
</LinearLayout>
</layout>
由上面代碼可以看出,String類型的變量沒有導(dǎo)入包,這是因?yàn)镈ata Binding和Java一樣,java.lang包里的類,我們是可以不用導(dǎo)入包的,再往下一個(gè)boolean和int類型的變量,都是java基本類型,也不用導(dǎo)入包。
再來看看幾個(gè)TextView,第二個(gè)TextView,我們直接使用@{str}來為android:text設(shè)置文本內(nèi)容;接下來注意第三個(gè)TextView,我們使用android:text="@{String.valueOf(num)}"來設(shè)置一個(gè)int類型的變量,因?yàn)樵诮oandroid:text設(shè)置int類型的值一定要轉(zhuǎn)化為String類型,不然系統(tǒng)會認(rèn)為是資源文件id。此外,我們還學(xué)習(xí)到了一點(diǎn),在Xml中,我們不僅可以使用變量,而且還可以調(diào)用方法
- 變量定義的高級部分
在上面,我們學(xué)會了如何在xml中定義變量,但是我們并沒有定義像List、Map等這樣的集合變量。那么到底能不能定義呢?答案是肯定的,而且定義的方式和我們上面的基本一致,區(qū)別就在于我們還需要為它定義key的變量,例如:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<data>
<!--導(dǎo)入類包-->
<import type="com.example.iaiai.databinding.Student" />
<import type="android.graphics.Bitmap" />
<import type="java.util.ArrayList" />
<import type="java.util.HashMap" />
<variable
name="stu"
type="Student" />
<variable
name="str"
type="String" />
<variable
name="error"
type="boolean" />
<variable
name="num"
type="int" />
<variable
name="list"
type="ArrayList<String>" />
<variable
name="map"
type="HashMap<String, String>" />
<variable
name="array"
type="String[]" />
<variable
name="listKey"
type="int" />
<variable
name="mapKey"
type="String" />
<variable
name="arrayKey"
type="int" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{stu.name}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{str}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(num)}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{list[listKey]}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{map[mapKey]}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{array[arrayKey]}" />
</LinearLayout>
</layout>
然后在java代碼中為變量賦值
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DataBindingBinding binding = DataBindingUtil.setContentView(this, R.layout.data_binding);
binding.setStu(new Student("lee", "Shenzhen"));
binding.setStr("just do it");
binding.setNum(10);
ArrayList<String> list = new ArrayList<String>();
list.add("list1");
list.add("list2");
binding.setList(list);
binding.setListKey(0);
HashMap<String,String> map = new HashMap<String,String>();
map.put("name","liu");
map.put("sex","male");
binding.setMap(map);
binding.setMapKey("sex");
String[] array = new String[2];
array[0] = "array0";
array[1] = "array1";
binding.setArray(array);
binding.setArrayKey(1);
}
5.表達(dá)式
xml中還支持表達(dá)式
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{error ? "error" : "ok"}'/>
如上所示,android:text后是一個(gè)三元表達(dá)式,如果error是true,則text就是error,否則是OK。
除此外還支持null合并操作,??--左邊的對象如果它不是null,選擇左邊的對象;或者如果它是null,選擇右邊的對象
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{str ?? "not null"}' />
還支持以下表達(dá)式:
- 數(shù)學(xué) + - / * %
- 字符串連接 +
- 邏輯 && ||
- 二進(jìn)制 & | ^
- 一元運(yùn)算 + - ! ~
- 移位 >> >>> <<
- 比較 == > < >= <=
- instanceof
- 分組 ()
- null
- Cast
- 方法調(diào)用
- 數(shù)據(jù)訪問 []
- 三元運(yùn)算 ?:
示例:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
但是它不支持一下表達(dá)式:
- this
- super
- new
- 顯式泛型調(diào)用
- 其他知識點(diǎn)
6.1 設(shè)置別名
假如我們import了兩個(gè)相同名稱的類咋辦?我們可以借助于別名來解決,別名借助alias字段來標(biāo)識,例如:
<data>
<import type="xxx.Name" alias="MyName">
<import type="xxx.xx.Name">
</data>
<TextView xxx:@{MyName.getName()}>
<TextView xxx:@{Name.getName()}>
6.2 自定義Binding類名稱
默認(rèn)情況下,Binding類的命名是基于所述layout文件的名稱,用大寫開頭,除去下劃線()以及()后的第一個(gè)字母大寫,然后添加“Binding”后綴。這個(gè)類將被放置在一個(gè)模塊封裝包里的databinding封裝包下。例如,所述layout文件contact_item.xml將生成ContactItemBinding。如果模塊包是com.example.my.app,那么它將被放置在com.example.my.app.databinding。
Binding類可通過調(diào)整data元素中的class屬性來重命名或放置在不同的包中。例如:
<data class="ContactItem">
...
</data>
在模塊封裝包的databinding包中會生成名為ContactItem的Binding類。如果要想讓該類生成在不同的包中,你需要添加前綴.,如下:
<data class=".ContactItem">
...
</data>
在這個(gè)情況下,ContactItem類直接在模塊包中生成?;蛘吣憧梢蕴峁┱麄€(gè)包名:
<data class="com.example.ContactItem">
...
</data>
6.3 字符串
當(dāng)使用單引號包含屬性值時(shí),在表達(dá)式中使用雙引號很容易:
android:text='@{map["firstName"]}'
使用雙引號來包含屬性值也是可以的。字符串前后需要使用"":
android:text="@{map[`firstName`]}"
6.4 Resources
使用正常的表達(dá)式來訪問resources也是可行的:
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
6.5 include
通過使用application namespace以及在屬性中的Variable名字從容器layout中傳遞Variables到一個(gè)被包含的layout:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
</LinearLayout>
</layout>
注意:在name.xml以及contact.xml兩個(gè)layout文件中必需要有user variable
7.事件綁定
大家都知道,在xml中我們可以給button設(shè)置一個(gè)onClick來達(dá)到事件的綁定,現(xiàn)在DataBinding也提供了事件綁定,而且不僅僅是button。首先定義一個(gè)對象處理點(diǎn)擊事件,如下:
/**
* Created by iaiai on 2016/2/16.
*/
public class EventHandler {
public void handleClick(View view) {
Toast.makeText(view.getContext(),"click",Toast.LENGTH_SHORT).show();
}
}
其次看布局:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<data>
<import type="com.example.iaiai.databinding.EventHandler" />
<variable
name="handler"
type="EventHandler" />
</data>
<LinearLayout android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Click"
android:onClick="@{handler.handleClick}"/>
</LinearLayout>
</layout>
最后,實(shí)現(xiàn)事件綁定
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ClickBindingBinding binding = DataBindingUtil.setContentView(this, R.layout.click_binding);
binding.setHandler(new EventHandler());
}
8.Data對象
我們學(xué)會了通過binding為我們的變量設(shè)置數(shù)據(jù),但是不知道你有沒有發(fā)現(xiàn)一個(gè)問題,當(dāng)我們數(shù)據(jù)改變的時(shí)候會怎樣?數(shù)據(jù)是跟隨著改變呢?還是原來的數(shù)據(jù)呢?這里告訴你答案:很不幸,顯示的還是原來的數(shù)據(jù)?那有沒有辦法讓數(shù)據(jù)源發(fā)生變化后顯示的數(shù)據(jù)也隨之發(fā)生變化?先來想想ListView是怎么做的, ListView的數(shù)據(jù)是通過Adapter提供的,當(dāng)數(shù)據(jù)發(fā)生改變時(shí),我們通過notifyDatasetChanged通過UI去改變數(shù)據(jù),這里面的原理其實(shí)就是內(nèi)容觀察者,慶幸的是DataBinding也支持內(nèi)容觀察者,而且使用起來也相當(dāng)方便!
8.1 Observable
我們可以通過Observable的方式去通知UI數(shù)據(jù)已經(jīng)改變了,當(dāng)然了,官方為我們提供了更加簡便的方式BaseObservable,我們的實(shí)體類只需要繼承該類,稍做幾個(gè)操作,就能輕松實(shí)現(xiàn)數(shù)據(jù)變化的通知。如何使用呢? 首先我們的實(shí)體類要繼承BaseObservale類,第二步在Getter上使用注解@Bindable,第三步,在Setter里調(diào)用方法notifyPropertyChanged,第四步,完成。就是這么簡單,下面我們來實(shí)際操作一下。
首先定義一個(gè)實(shí)體類,并繼承BaseObservable
/**
* Created by iaiai on 2016/2/16.
*/
public class Student extends BaseObservable{
private String name;
private String addr;
public Student() {
}
public Student(String name,String addr) {
this.addr = addr;
this.name = name;
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(com.example.iaiai.databinding.BR.name);
}
@Bindable
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
notifyPropertyChanged(com.example.iaiai.databinding.BR.addr);
}
}
觀察getName方法,我們使用了@Bindable注解,觀察setName,我們調(diào)用了notifyPropertyChanged方法,這個(gè)方法還需要一個(gè)參數(shù),這里參數(shù)類似于R.java,保存了我們所有變量的引用地址,這里我們使用了name。
其次,看看布局文件
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<data>
<import type="com.example.iaiai.databinding.Student" />
<variable
name="stu"
type="Student"/>
<variable
name="click"
type="com.example.iaiai.databinding.MainActivity" />
</data>
<LinearLayout android:orientation="vertical">
<TextView
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{stu.name+stu.addr}"
android:onClick="@{click.click}"/>
</LinearLayout>
</layout>
最后,java實(shí)現(xiàn)
public class MainActivity extends AppCompatActivity {
private Student mStu;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ClickBindingBinding binding = DataBindingUtil.setContentView(this, R.layout.click_binding);
mStu = new Student("lau","Shenzhen");
binding.setStu(mStu); //設(shè)置初始顯示數(shù)據(jù)
binding.setClick(this); //設(shè)置點(diǎn)擊事件
}
public void click(View view) {
//點(diǎn)擊時(shí)數(shù)據(jù)發(fā)生改變
mStu.setName("lee");
mStu.setAddr("Beijing");
}
}
8.2 ObservableFields
上面使用BaseObservable已經(jīng)非常容易了,但是google工程師還不滿足,繼續(xù)給我們封裝了一系列的ObservableFields,這里有ObservableField,ObservableBoolean,ObservableByte,ObservableChar,ObservableShort,ObservableInt,ObservableLong,ObservableFloat,ObservableDouble,ObservableParcelable
ObservableFields的使用方法就更加簡單了,例如下面代碼:
public class People {
public ObservableField<String> name = new ObservableField<>();
public ObservableInt age = new ObservableInt();
public ObservableBoolean isMan = new ObservableBoolean();
}
很簡單,只有三個(gè)ObservableField變量,并且沒有g(shù)etter和setter,因?yàn)槲覀儾恍枰猤etter和setter。
在xml中怎么使用呢?
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class=".Custom">
<variable
name="people"
type="org.loader.app4.People" />
</data>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{people.name}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(people.age)}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{people.isMan ? "man" : "women"}'/>
</LinearLayout>
</layout>
也很簡單,直接使用變量,那怎么賦值和取值呢?這些ObservableField都會有一對get和set方法,所以使用起來也很方便了:
mPeople = new People();
binding.setPeople(mPeople);
mPeople.name.set("people");
mPeople.age.set(19);
mPeople.isMan.set(true);
8.3 Observable Collections
既然普通的變量我們有了ObservableFields的分裝,那集合呢?當(dāng)然也有啦,來看著兩個(gè):ObservableArrayMap,ObservableArrayList。使用和普通的Map、List基本相同,直接看代碼:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class=".Custom">
<variable
name="map"
type="android.databinding.ObservableArrayMap<String,String>" />
<variable
name="list"
type="android.databinding.ObservableArrayList<String>" />
</data>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{map[`name`]}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{list[0]}"/>
</LinearLayout>
</layout>
在來看java文件,怎么設(shè)置數(shù)據(jù)
ObservableArrayMap<String, String> map = new ObservableArrayMap<>();
ObservableArrayList<String> list = new ObservableArrayList<>();
map.put("name", "loader or qibin");
list.add("loader!!!");
binding.setMap(map);
binding.setList(list);
9.Inflate
上面的代碼我們都是在activity中通過DataBindingUtil.setContentView來加載的布局的,現(xiàn)在有個(gè)問題了,如果我們是在Fragment中使用呢?Fragment沒有setContentView怎么辦?不要著急,Data Binding也提供了inflate的支持!
使用方法如下,大家肯定會覺得非常眼熟。
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
接下來,我們就嘗試著在Fragment中使用一下Data Binding吧。
首先還是那個(gè)學(xué)生類,Student
public class Student extends BaseObservable {
private String name;
private int age;
public Student() {
}
public Student(int age, String name) {
this.age = age;
this.name = name;
}
@Bindable
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
notifyPropertyChanged(org.loader.app5.BR.age);
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(org.loader.app5.BR.name);
}
}
其次,activity的布局
<RelativeLayout 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"
tools:context=".MainActivity">
<FrameLayout
android:id="@+id/container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
Activity的實(shí)現(xiàn)
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, new MyFragment()).commit();
}
}
重點(diǎn)來了,我們這里data binding的操作都放在了fragment里,那么我們先來看看fragment的布局。
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class=".Custom">
<import type="org.loader.app5.Student" />
<variable
name="stu"
type="Student" />
<variable
name="frag"
type="org.loader.app5.MyFragment" />
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{frag.click}"
android:text="@{stu.name}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(stu.age)}"/>
</LinearLayout>
</layout>
兩個(gè)TextView分別綁定了Student的name和age字段,而且給name添加了一個(gè)點(diǎn)擊事件,點(diǎn)擊后會調(diào)用Fragment的click方法。我們來迫不及待的看一下Fragment怎么寫:
public class MyFragment extends Fragment {
private Student mStu;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
org.loader.app5.Custom binding = DataBindingUtil.inflate(inflater,
R.layout.frag_layout, container, false);
mStu = new Student(20, "loader");
binding.setStu(mStu);
binding.setFrag(this);
return binding.getRoot();
}
public void click(View view) {
mStu.setName("qibin");
mStu.setAge(18);
}
}
在onCreateView中,不同于在Activity中,這里我們使用了DataBindingUtil.inflate方法,接受4個(gè)參數(shù),第一個(gè)參數(shù)是一個(gè)LayoutInflater對象,正好,我們這里可以使用onCreateView的第一個(gè)參數(shù),第二個(gè)參數(shù)是我們的布局文件,第三個(gè)參數(shù)是一個(gè)ViewGroup,第四個(gè)參數(shù)是一個(gè)boolean類型的,和在LayoutInflater.inflate一樣,后兩個(gè)參數(shù)決定了是否想container中添加我們加載進(jìn)來的布局。
下面的代碼和我們之前寫的并無差別,但是有一點(diǎn),onCreateView方法需要返回一個(gè)View對象,我們從哪獲取呢?ViewDataBinding有一個(gè)方法getRoot可以獲取我們加載的布局,是不是很簡單?
來看一下效果:
10.Data Binding VS RecyclerView
有了上面的思路,大家是不是也會在ListView和RecyclerView中使用了?我們僅以一個(gè)RecyclerView來學(xué)習(xí)一下。
首先來看看item的布局,
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="stu"
type="org.loader.app6.Student" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{stu.name}"
android:layout_alignParentLeft="true"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(stu.age)}"
android:layout_alignParentRight="true"/>
</RelativeLayout>
</layout>
可以看到,還是用了那個(gè)Student實(shí)體,這樣得代碼,相信你也已經(jīng)看煩了吧。 那我們來看看activity的。
private RecyclerView mRecyclerView;
private ArrayList<Student> mData = new ArrayList<Student>() {
{
for (int i=0;i<10;i++) add(new Student("loader" + i, 18 + i));
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this,
LinearLayoutManager.VERTICAL, false));
mRecyclerView.setAdapter(new MyAdapter(mData));
}
這里給RecyclerView設(shè)置了一個(gè)Adapter,相信最主要的代碼就在這個(gè)Adapter里。
private class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private ArrayList<Student> mData = new ArrayList<>();
private MyAdapter(ArrayList<Student> data) {
mData.addAll(data);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater
.from(viewGroup.getContext()), R.layout.item, viewGroup, false);
ViewHolder holder = new ViewHolder(binding.getRoot());
holder.setBinding(binding);
return holder;
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
viewHolder.getBinding().setVariable(org.loader.app6.BR.stu, mData.get(i));
viewHolder.getBinding().executePendingBindings();
}
@Override
public int getItemCount() {
return mData.size();
}
class ViewHolder extends RecyclerView.ViewHolder {
private ViewDataBinding binding;
public ViewHolder(View itemView) {
super(itemView);
}
public void setBinding(ViewDataBinding binding) {
this.binding = binding;
}
public ViewDataBinding getBinding() {
return this.binding;
}
}
果然,這個(gè)adapter的寫法和我們之前的寫法不太一樣,首先看看ViewHolder,在這個(gè)holder里,我們保存了一個(gè)ViewDataBinding對象,并給它提供了Getter和Setter方法, 這個(gè)ViewDataBinding是干嘛的?我們稍后去講。繼續(xù)看看onCreateViewHolder,在這里面,我們首先調(diào)用DataBindingUtil.inflate方法返回了一個(gè)ViewDataBinding的對象,這個(gè)ViewDataBinding是個(gè)啥?我們以前沒見過啊,這里告訴大家我們之前返回的那些都是ViewDataBinding的子類!繼續(xù)看代碼,我們new了一個(gè)holder,參數(shù)是肯定是我們的item布局了,繼續(xù)看,接著我們又把binding設(shè)置給了holder,最后返回holder。這時(shí)候,我們的holder里就保存了剛剛返回的ViewDataBinding對象,干嘛用呢?繼續(xù)看onBindViewHolder就知道了。
@Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
viewHolder.getBinding().setVariable(org.loader.app6.BR.stu, mData.get(i));
viewHolder.getBinding().executePendingBindings();
}
只有兩行代碼,但是都是我們沒有見過的,首先第一行,我們以前都是使用類似binding.setStu這樣方法去設(shè)置變量,那這個(gè)setVariable呢? 為什么沒有setStu,這里要記住,ViewDataBinding是我們之前用的那些binding的父類,只有自動生成的那些子類才會有setXXX方法,那現(xiàn)在我們需要在ViewDataBinding中設(shè)置變量咋辦?這個(gè)類為我們提供了setVariable去設(shè)置變量,第一個(gè)參數(shù)是我們的變量名的引用,第二個(gè)是我們要設(shè)置的值。
第二行代碼,executePendingBindings的作用是干嘛的?
官方的回答是:
當(dāng)數(shù)據(jù)改變時(shí),binding會在下一幀去改變數(shù)據(jù),如果我們需要立即改變,就去調(diào)用executePendingBindings方法。 所以這里的作用就是去讓數(shù)據(jù)的改變立即執(zhí)行。
ok,現(xiàn)在看起來,我們的代碼更加簡潔了,而且不需要保存控件的實(shí)例,是不是很爽? 來看看效果:
11.View with ID
在使用Data Binding的過程中,我們發(fā)現(xiàn)并沒有保存View的實(shí)例,但是現(xiàn)在我們有需求需要這個(gè)View的實(shí)例咋辦?難道走老路findViewById?當(dāng)然不是啦,當(dāng)我們需要某個(gè)view的實(shí)例時(shí),我們只要給該view一個(gè)id,然后Data Binding框架就會給我們自動生成該view的實(shí)例,放哪了?當(dāng)然是ViewDataBinding里面。
上代碼:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class=".Custom">
<variable
name="str"
type="android.databinding.ObservableField<String>" />
<variable
name="handler"
type="org.loader.app7.MainActivity" />
</data>
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{str.get}"
android:onClick="@{handler.click}"/>
</layout>
xml中代碼沒有什么好說的,都是之前的代碼。需要注意的是, 我們給TextView設(shè)定了一個(gè)id-textView。
activity代碼如下:
public class MainActivity extends AppCompatActivity {
private org.loader.app7.Custom mBinding;
private ObservableField<String> mString;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this,
R.layout.activity_main);
mString = new ObservableField<String>();
mString.set("loader");
mBinding.setStr(mString);
mBinding.setHandler(this);
}
public void click(View view) {
mString.set("qibin");
mBinding.textView.setTextColor(Color.GREEN); //找到控件
}
}
通過ViewDataBinding類的實(shí)例直接去獲取的。只要我們給了view一個(gè)id,那么框架就會在ViewDataBinding中自動幫我們保存這個(gè)view的實(shí)例,變量名就是我們設(shè)置的id。
12.自定義setter(BindingAdapter)
想想這樣的一種情景,一個(gè)ImageView需要通過網(wǎng)絡(luò)去加載圖片,那我們怎么辦?看似好像使用DataBinding不行,恩,我們上面所學(xué)到東西確實(shí)不能夠解決這個(gè)問題,但是DataBinding框架給我們提供了很好的擴(kuò)展,允許我們自定義setter,那該怎么做呢?這里就要引出另一個(gè)知識點(diǎn)——BindingAdapter,這是一個(gè)注解,參數(shù)是一個(gè)數(shù)組,數(shù)組中存放的是我們自定義的’屬性’。接下來就以一個(gè)例子學(xué)習(xí)一下BindingAdapter的使用。
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data class=".Custom">
<variable
name="imageUrl"
type="String" />
</data>
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:image="@{imageUrl}"/>
</layout>
這里我們增加了一個(gè)命名空間app,并且注意ImageView的app:image屬性,這里和我們自定義view時(shí)自定義的屬性一樣,但是這里并不需要我們?nèi)ブ貙慖mageView,這條屬性的值是我們上面定義的String類型的imageUrl,從名稱中看到這里我們可能會塞給他一個(gè)url。
activity代碼如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
org.loader.app8.Custom binding = DataBindingUtil.setContentView(this,
R.layout.activity_main);
binding.setImageUrl("http://images.csdn.net/20150810/Blog-Image%E5%89%AF%E6%9C%AC.jpg");
}
}
果然在這里我們set了一個(gè)url,那圖片怎么加載呢?這里就要使用到我們剛才說的BindingAdapter注解了。
public class Utils {
@BindingAdapter({"bind:image"})
public static void imageLoader(ImageView imageView, String url) {
ImageLoaderUtils.getInstance().displayImage(url, imageView);
}
}
我們定義了一個(gè)Utils類,這個(gè)類你可以隨便起名,該類中只有一個(gè)靜態(tài)的方法imageLoader,該方法有兩個(gè)參數(shù),一個(gè)是需要設(shè)置數(shù)據(jù)的view, 一個(gè)是我們需要的url。值得注意的是那個(gè)BindingAdapter注解,看看他的參數(shù),是一個(gè)數(shù)組,內(nèi)容只有一個(gè)bind:image,僅僅幾行代碼,我們不需要 手工調(diào)用Utils.imageLoader,也不需要知道imageLoader方法定義到哪了,一個(gè)網(wǎng)絡(luò)圖片加載就搞定了,是不是很神奇,這里面起關(guān)鍵作用的就是BindingAdapter 注解了,來看看它的參數(shù)怎么定義的吧,難道是亂寫?當(dāng)然不是,這里要遵循一定的規(guī)則,
以bind:開頭,接著書寫你在控件中使用的自定義屬性名稱。
這里就是image了,不信來看。
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:image="@{imageUrl}"/>
13.Converters
Converter是什么呢?舉個(gè)例子吧:假如你的控件需要一個(gè)格式化好的時(shí)間,但是你只有一個(gè)Date類型額變量咋辦?肯定有人會說這個(gè)簡單,轉(zhuǎn)化完成后在設(shè)置,恩,這也是一種辦法,但是DataBinding還給我們提供了另外一種方式,雖然原理一樣,但是這種方式使用的場景更多,那就是——Converter。和上面的BindingAdapter使用方法一樣,這也是一個(gè)注解。下面還是以一段代碼的形式進(jìn)行學(xué)習(xí)。
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class=".Custom">
<variable
name="time"
type="java.util.Date" />
</data>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{time}"/>
</layout>
看TextView的text屬性,我們需要一個(gè)String類型的值,但是這里確給了一個(gè)Date類型的,這就需要我們?nèi)ザxConverter去轉(zhuǎn)換它,
activity代碼如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
org.loader.app9.Custom binding = DataBindingUtil.setContentView(this,
R.layout.activity_main);
binding.setTime(new Date());
}
}
去給這個(gè)Date類型的變量設(shè)置值。怎么去定義Converter呢? 看代碼:
public class Utils {
@BindingConversion
public static String convertDate(Date date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(date);
}
}
和上面一樣,我們不需要關(guān)心這個(gè)convertDate在哪個(gè)類中,重要的是他的@BindingConversion注解,這個(gè)方法接受一個(gè)Date類型的變量,正好我們的android:text設(shè)置的就是一個(gè)Date類型的值,在方法內(nèi)部我們將這個(gè)Date類型的變量轉(zhuǎn)換成String類型的日期并且返回。這樣UI上就顯示出我們轉(zhuǎn)化好的字符串。
歡迎加入QQ群:104286694