今天來了解一下Android最新給我們帶來的數據綁定框架——Data Binding Library。數據綁定框架給我們帶來了更大的方便性,以前我們可能需要在Activity
裏寫很多的findViewById
,煩人的代碼也增加了我們代碼的耦合性,現在我們馬上就可以拋棄那麼多的findViewById
。說到這裏,有人可能會有個疑問:我使用一些註解框架也可以不用findViewById
啊,是的,但是註解註定要拖慢我們代碼的速度,Data
Binding則不會,官網文檔說還會提高解析XML的速度,最主要的Data Binding並不是單單減少了我們的findViewById
,更多好處請往下看文章。
一、環境
在開始使用新東西之前,我們需要稍微的配置一下環境,這裏要求你的Android Studio版本是1.3+,使用eclipse的同學暫時還沒有辦法使用該框架,請換用Android Studio。還有,在開始之前,請更新你的Support
repository
到最新的版本。
萬事俱備,那我們就開始搭配環境!
新建一個project
,在dependencies
中添加以下依賴
- classpath "com.android.databinding:dataBinder:1.0-rc1"
新建module
,並且在module
的build.gradle文件中添加
- apply plugin: 'com.android.application'
- apply plugin: 'com.android.databinding'
ok,到現在爲止,我們的環境就準備完畢了,下面我們就開始Data Binding的學習啦。
二、Data Binding嘗試
在代碼開始,我們並不直接進入新東西的講解,而且以一段代碼展現Data Binding的魅力。
首先我們需要一個Java
bean
,很簡單,一個學生類。
- public class Student {
- private String name;
- private String addr;
- public Student() {
- }
- public Student(String name, String addr) {
- this.name = name;
- this.addr = addr;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public String getAddr() {
- return this.addr;
- }
- public void setAddr(String addr) {
- this.addr = addr;
- }
- }
再來看看我們佈局文件怎麼寫:
- <layout xmlns:android="http://schemas.android.com/apk/res/android">
- <data>
- <variable
- name="stu"
- type="org.loader.androiddatabinding.Student" />
- </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="@{stu.addr}"/>
- </LinearLayout>
- </layout>
可以看到我們的xml佈局和以前還有有一定的差別的,但是差別也不是很大。
最後來看看
Activity
怎麼寫。
- public class MainActivity extends AppCompatActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
- binding.setStu(new Student("loader", "山東萊蕪"));
- }
- }
Activity
的代碼非常簡單,就添加了兩行代碼,而且,值得注意的是:我們並沒有findViewById
然後再去setText
。
這段小代碼運行的結果大家可能已經猜到了,就是在界面上顯示loader
和山東萊蕪
兩句話。
)
在看完小實例後,大家是不是感覺棒棒噠? 沒有了之前的find控件,沒有了setText,Activity
代碼更加簡潔明瞭!
下面開始,我們進入Data Binding的學習!
三、 初始Data Binding
上面的代碼算是帶領我們進入了Data Binding的世界,那我們先從佈局文件開始入手Data Binding吧。再來看看上面的佈局文件。
- <layout xmlns:android="http://schemas.android.com/apk/res/android">
- <data>
- <variable
- name="stu"
- type="org.loader.androiddatabinding.Student" />
- </data>
- ...
- </layout>
我們的根節點變成了
layout
,在layout
的子節點中分成兩部分,第一部分是data
節點,第二部分纔是我們之前的根節點,在data
節點下我們又定義了一個variable
, 從名稱上看,這應該是一個變量,變量的名稱是
stu
,類型是org.loader.androiddatabinding.Student
,這類似我們在java文件中這麼定義:
- org.loader.androiddatabinding.Student stu;
Student
完整的包名,一個還好,如果這裏我們需要多個Student
呢?要累死?
NO,NO,NO,我們還可以向寫java文件那樣導入包。
- <layout xmlns:android="http://schemas.android.com/apk/res/android">
- <data>
- <import type="org.loader.app2.Student" />
- <variable
- name="stu"
- type="Student" />
- </data>
- ...
- </layout>
這樣寫,就類似於java的
import org.loader.app2.Student;...Student stu;...
既然變量我們定義好了,那該怎麼使用呢?還是看上面的xml文件。
- <layout xmlns:android="http://schemas.android.com/apk/res/android">
- ...
- <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="@{stu.addr}"/>
- </LinearLayout>
- </layout>
恩,注意看兩個
TextView
的android:text
,它的值是一個以@
開始,以{}包裹的形式出現,而內容呢?是stu.name
。stu就是我們上面定義的variable
, name還記得嗎?是我們
Student
類中的一個變量。其實這裏就會去調用stu.getName()
方法。 好了,很快,我們就入門了Data Binding,下面讓我們來多定義幾個變量試試看。
- <layout xmlns:android="http://schemas.android.com/apk/res/android">
- <data>
- <import type="org.loader.app2.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
類型的變量我們並沒有導包,這裏說明一下,和在java裏一樣,java.lang
包裏的類,我們是可以不用導包的,再往下,一個boolean
和int
類型的變量,都是java基本類型的,所以說嘛,在這裏定義變量,你就想成是在java裏定義就ok。 再來看看這幾個
TextView
,第二個,我們直接使用@{str}
來爲android:text
設置成上面定義個str
的值,繼續往下要注意了,我們使用了
android:text="@{String.valueOf(num)}"
來設置了一個int
類型的變量,大家都知道我們在給android:text
設置int
類型的值時一定要轉化爲String
類型,要不它就認爲是資源文件了,這裏我們還學到了一點,在xml中,我們不僅可以使用變量,而且還可以調用方法!
四、 變量定義的高級部分
在上面,我們學會了如何去在xml中定義變量,但是不知道你發現沒?我們沒有定義像List
、Map
等這樣的集合變量。那到底能不能定義呢?答案肯定是可以的,而且定義的方式和我們上面的基本一致,區別就在於我們還需要爲它定義key的變量,例如:
- <layout xmlns:android="http://schemas.android.com/apk/res/android">
- <data>
- <import type="org.loader.app2.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: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)}"/>
- <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[`name`]}"/>
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@{array[0]}"/>
- </LinearLayout>
- </layout>
這段代碼比較長,但是我們僅關心那幾個集合和數組,可以看到我們定義集合和定義普通變量一樣,只不過這裏我們還指定了一些的泛型,例如:ArrayList<String>
。
下面我們還爲下面使用這些集合準備了幾個key,也都是變量。
繼續看看怎麼使用,和我們在java中使用不同,這裏都是以:集合變量名[key]的形式使用,如果你的key是一個字面字符串可以使用反引號,也可以使用轉義後的雙引號。恩,這裏也沒有什麼可以說的了,大家多看幾遍就掌握了,都是概念性的東西,記住就ok。
五、在java代碼中使用
前面定義了這麼多變量,但是我們還沒有給他們賦值!在哪賦值呢?肯定是在java代碼中使用了,大部分情況我們還是在Activity
中去使用它,以前我們都是在onCreate
方法中通過setContentView
去設置佈局,但現在不一樣了,現在我們是用過DataBindingUtil
類的一個靜態方法setContentView
設置佈局,同時該方法會返回一個對象,什麼對象?這個對象有點特殊,它是一個自動生成的類的對象,看下面:
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- ActivityMainBinding binding = DataBindingUtil.setContentView(this,
- R.layout.activity_main);
- }
看到
ActivityMainBinding
了嗎?就是它!那自動生成有什麼規則了沒?當然有了,記好了:
將我們佈局文件的首字母大寫,並且去掉下劃線,將下劃線後面的字母大寫,加上Binding組成。
看看上面的類,是不是符合這個規則。繼續看看這個對象哪來的,是通過
- DataBindingUtil.setContentView(this, R.layout.activity_main);
返回的,DataBindingUtil.setContentView的兩個參數分別是當前
Activity
和佈局文件。那接下來,就是我們關心的給變量賦值了。
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- ...
- binding.setStu(new Student("loader"));
- binding.setStr("string");
- binding.setError(false);
- ArrayList<String> list = new ArrayList<String>() {
- {
- add("arraylist");
- }
- };
- binding.setList(list);
- binding.setListKey(0);
- HashMap<String, String> map = new HashMap<String, String>() {
- {
- put("name", "hashmap");
- }
- };
- binding.setMap(map);
- // binding.setMapKey("name");
- String[] array = new String[1];
- array[0] = "array";
- binding.setArray(array);
- binding.setArrayKey(0);
- }
一連串的binding.setXXX,這個XXX是什麼呢?就是我們在xml中定義的那些變量首字母大寫了!也沒好好說的吧,多看幾遍。
六、 表達式
短暫的幸福時光,我們還是要告別java代碼了,繼續回到xml中,這一塊,我們來學習一下表達式,什麼?這玩意在xml中還支持表達式!
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text='@{error ? "error" : "ok"}'/>
還記得上面我們定義了一個boolean的變量沒有用到,這裏我們就用到了,看好
android:text
,這裏是一個三元表達式,如果error是true,則text就是error,否則是ok。這裏還支持null合併操作,什麼是null合併,相信看一眼你就知道了
- android:text='@{str==null ?? "not null"}'
簡單解釋一下,如果str是null,text的值就是str本身,否則就是”not null”。
它還支持一下表達式:
- Mathematical + - / * %
- String concatenation +
- Logical && ||
- Binary & | ^
- Unary + - ! ~
- Shift >> >>> <<
- Comparison == > < >= <=
- instanceof
- Grouping ()
- Literals - character, String, numeric, null
- Cast
- Method calls
- Field access
- Array access []
- Ternary operator ?:
但是它不支持一下表達式:
- this
- super
- new
- Explicit generic invocation
七、 其他遺漏點
說到這裏,xml中的事情基本算完了,但是還有幾個小地方沒有說,順便說一下。
1. 設置別名
假如我們import了兩個相同名稱的類咋辦?別怕,別名來拯救你!例如:
- ...
- <data>
- <import type="xxx.Name" alias="MyName">
- <import type="xxx.xx.Name">
- </data>
- <TextView xxx:@{MyName.getName()}>
- <TextView xxx:@{Name.getName()}>
- ...
- 自定義Binding名稱
還記得系統爲我們生成好的那個binding類名嗎?如果只能使用那樣的是不是有點太暴力了?好在google對我們還算友好了,允許我們自定義binding名稱,定製名稱也很簡單,就是給data一個class字段就ok。
例如:
那麼:DataBindingUtils.setContentView返回的binding類就是:你的應用包名.Custom
。
八、事件綁定
大家都知道,在xml中我們可以給button
設置一個onClick
來達到事件的綁定,現在DataBinding也提供了事件綁定,而且不僅僅是button
。
來看一下:
- <layout xmlns:android="http://schemas.android.com/apk/res/android">
- <data>
- <import type="org.loader.app3.EventHandlers" />
- <variable
- name="handlers"
- type="EventHandlers" />
- </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="CLICK ME"
- android:onClick="@{handlers.handleClick}"/>
- </LinearLayout>
- </layout>
定義了一個
EventHandlers
類型的handlers
變量,並在onClick的時候執行EventHandlers
的handleClick
方法。 繼續看看EventHandlers是怎麼寫的。
- public class EventHandlers {
- public void handleClick(View view) {
- Toast.makeText(view.getContext(), "you clicked the view", Toast.LENGTH_LONG).show();
- }
- }
很簡單,就是簡單的Toast
了一下,這裏要注意的是,handlerClick
方法需要一個View
的參數。
九、 數據對象
我們學會了通過binding爲我們的變量設置數據,但是不知道你有沒有發現一個問題,當我們數據改變的時候會怎樣?數據是跟隨着改變呢?還是原來的數據呢?這裏告訴你答案:很不幸,顯示的還是原來的數據?那有沒有辦法讓數據源發生變化後顯示的數據也隨之發生變化?先來想想ListView
是怎麼做的, ListView
的數據是通過Adapter
提供的,當數據發生改變時,我們通過notifyDatasetChanged
通過UI去改變數據,這裏面的原理其實就是內容觀察者,慶幸的是DataBinding也支持內容觀察者,而且使用起來也相當方便!
BaseObservable
我們可以通過Observable的方式去通知UI數據已經改變了,當然了,官方爲我們提供了更加簡便的方式BaseObservable
,我們的實體類只需要繼承該類,稍做幾個操作,就能輕鬆實現數據變化的通知。如何使用呢?
首先我們的實體類要繼承BaseObservale
類,第二步在Getter
上使用註解@Bindable
,第三步,在Setter
裏調用方法notifyPropertyChanged
,第四步,完成。就是這麼簡單,下面我們來實際操作一下。
首先定義一個實體類,並繼承BaseObservable
- public class Student extends BaseObservable {
- private String name;
- public Student() {
- }
- public Student(String name) {
- this.name = name;
- }
- @Bindable
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- notifyPropertyChanged(org.loader.app4.BR.name);
- }
- }
觀察getName方法,我們使用了
@Bindable
註解,觀察setName,我們調用了notifyPropertyChanged
方法,這個方法還需要一個參數,這裏參數類似於R.java
,保存了我們所有變量的引用地址,這裏我們使用了name。 再來看看佈局文件。
- <layout xmlns:android="http://schemas.android.com/apk/res/android">
- <data class=".Custom">
- <import type="org.loader.app4.Student" />
- <variable
- name="stu"
- type="Student"/>
- <variable
- name="click"
- type="org.loader.app4.MainActivity" />
- </data>
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:onClick="@{click.click}"
- android:text="@{stu.name}"/>
- </layout>
不多說了,我們給
TextView
設置了文本,還有點擊事件。Activity,
- public class MainActivity extends AppCompatActivity {
- private Student mStu;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- org.loader.app4.Custom binding = DataBindingUtil.setContentView(this,
- R.layout.activity_main);
- mStu = new Student("loader");
- binding.setStu(mStu);
- binding.setClick(this);
- }
- public void click(View view) {
- mStu.setName("qibin");
- }
- }
這段代碼,首先顯示的是loader,當我們點擊TextView
時,界面換成qibin。
ObservableFields家族
上面使用BaseObservable
已經非常容易了,但是google工程師還不滿足,繼續給我們封裝了一系列的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();
- }
很簡單,只有三個ObservableField變量,並且沒有getter和setter,因爲我們不需要getter和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);
- ...
也不多說了。
Observable Collections
既然普通的變量我們有了ObservableFields的分裝,那集合呢?當然也有啦,來看着兩個: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文件,怎麼設置數據,
- 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);
太簡單了,簡直和
List
、Map
使用方法一模一樣!!! 十、inflate
不知道大家注意沒有,上面的代碼我們都是在activity中通過DataBindingUtil.setContentView
來加載的佈局的,現在有個問題了,如果我們是在Fragment
中使用呢?Fragment
沒有setContentView
怎麼辦?不要着急,Data
Binding也提供了inflate
的支持!
使用方法如下,大家肯定會覺得非常眼熟。
- MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
- MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
接下來,我們就嘗試着在
Fragment
中使用一下Data
Binding吧。 首先還是那個學生類,
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的代碼,
- 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();
- }
- }
- <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>
- 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個參數,第一個參數是一個LayoutInflater對象,正好,我們這裏可以使用onCreateView的第一個參數,第二個參數是我們的佈局文件,第三個參數是一個ViewGroup,第四個參數是一個boolean類型的,和在LayoutInflater.inflate
一樣,後兩個參數決定了是否想Container
中添加我們加載進來的佈局。
下面的代碼和我們之前寫的並無差別,但是有一點,onCreateView
方法需要返回一個View對象,我們從哪獲取呢?ViewDataBinding
有一個方法getRoot
可以獲取我們加載的佈局,是不是很簡單?
來看一下效果:
十一、 Data Binding VS RecyclerView
有了上面的思路,大家是不是也會在ListView和RecyclerView中使用了?我們僅以一個RecyclerView來學習一下。
首先來看看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>
可以看到,還是用了那個Student實體,這樣得代碼,相信你也已經看煩了吧。
那我們來看看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
設置了一個Adapter,相信最主要的代碼就在這個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;
- }
- }
果然,這個adapter的寫法和我們之前的寫法不太一樣,首先看看ViewHolder,在這個holder裏,我們保存了一個
ViewDataBinding
對象,並給它提供了Getter
和Setter
方法,
這個ViewDataBinding
是幹嘛的?我們稍後去講。繼續看看onCreateViewHolder
,在這裏面,我們首先調用DataBindingUtil.inflate
方法返回了一個ViewDataBinding
的對象,這個ViewDataBinding
是個啥?我們以前沒見過啊,這裏告訴大家我們之前返回的那些都是ViewDataBinding
的子類!繼續看代碼,我們new了一個holder,參數是肯定是我們的item佈局了,繼續看,接着我們又把binding設置給了holder,最後返回holder。這時候,我們的holder裏就保存了剛剛返回的ViewDataBinding
對象,幹嘛用呢?繼續看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
這樣方法去設置變量,那這個setVariable
呢?
爲什麼沒有setStu
,這裏要記住,ViewDataBinding
是我們之前用的那些binding的父類,只有自動生成的那些子類纔會有setXXX
方法,那現在我們需要在ViewDataBinding
中設置變量咋辦?這個類爲我們提供了setVariable
去設置變量,第一個參數是我們的變量名的引用,第二個是我們要設置的值。第二行代碼,executePendingBindings
的作用是幹嘛的?官方的回答是:當數據改變時,binding會在下一幀去改變數據,如果我們需要立即改變,就去調用
executePendingBindings
方法。
所以這裏的作用就是去讓數據的改變立即執行。
ok,現在看起來,我們的代碼更加簡潔了,而且不需要保存控件的實例,是不是很爽? 來看看效果:
十二、 View with ID
在使用Data Binding的過程中,我們發現並沒有保存View的實例,但是現在我們有需求需要這個View的實例咋辦?難道走老路findViewById
?當然不是啦,當我們需要某個view的實例時,我們只要給該view一個id,然後Data
Binding框架就會給我們自動生成該view的實例,放哪了?當然是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
了一個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
類的實例直接去獲取的。
只要我們給了view一個id,那麼框架就會在ViewDataBinding中自動幫我們保存這個view的實例,變量名就是我們設置的id。
十三、 自定義setter
想想這樣的一種情景,一個ImageView
需要通過網絡去加載圖片,那我們怎麼辦?看似好像使用DataBinding不行,恩,我們上面所學到東西確實不能夠解決這個問題,但是DataBinding框架給我們提供了很好的擴展,允許我們自定義setter,那該怎麼做呢?這裏就要引出另一個知識點——BindingAdapter
,這是一個註解,參數是一個數組,數組中存放的是我們自定義的’屬性’。接下來就以一個例子學習一下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>
這裏我們增加了一個命名空間
app
,並且注意ImageView的app:image
屬性,這裏和我們自定義view時自定義的屬性一樣,但是這裏並不需要我們去重寫ImageView,這條屬性的值是我們上面定義的String類型的imageUrl,從名稱中看到這裏我們可能會塞給他一個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了一個url,那圖片怎麼加載呢?這裏就要使用到我們剛纔說的BindingAdapter註解了。
- public class Utils {
- @BindingAdapter({"bind:image"})
- public static void imageLoader(ImageView imageView, String url) {
- ImageLoaderUtils.getInstance().displayImage(url, imageView);
- }
- }
我們定義了一個Utils
類,這個類你可以隨便起名,該類中只有一個靜態的方法imageLoader,該方法有兩個參數,一個是需要設置數據的view,
一個是我們需要的url。值得注意的是那個BindingAdapter
註解,看看他的參數,是一個數組,內容只有一個bind:image
,僅僅幾行代碼,我們不需要
手工調用Utils.imageLoader,也不需要知道imageLoader方法定義到哪了,一個網絡圖片加載就搞定了,是不是很神奇,這裏面起關鍵作用的就是BindingAdapter
註解了,來看看它的參數怎麼定義的吧,難道是亂寫?當然不是,這裏要遵循一定的規則,
以bind:開頭,接着書寫你在控件中使用的自定義屬性名稱。
這裏就是image
了,不信來看。
- <ImageView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- app:image="@{imageUrl}"/>
看看運行結果:
十四、 Converters
Converter是什麼呢?舉個例子吧:假如你的控件需要一個格式化好的時間,但是你只有一個Date
類型額變量咋辦?肯定有人會說這個簡單,轉化完成後在設置,恩,這也是一種辦法,但是DataBinding還給我們提供了另外一種方式,雖然原理一樣,但是這種方式使用的場景更多,那就是——Converter。和上面的BindingAdapter
使用方法一樣,這也是一個註解。下面還是以一段代碼的形式進行學習。
- <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屬性,我們需要一個String類型的值,但是這裏確給了一個Date類型的,這就需要我們去定義Converter去轉換它,
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());
- }
- }
去給這個Date類型的變量設置值。怎麼去定義Converter呢? 看代碼:
- public class Utils {
- @BindingConversion
- public static String convertDate(Date date) {
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
- return sdf.format(date);
- }
- }
和上面一樣,我們不需要關心這個convertDate在哪個類中,重要的是他的@BindingConversion
註解,這個方法接受一個Date類型的變量,正好我們的android:text設置的就是一個Date類型的值,在方法內部我們將這個Date類型的變量轉換成String類型的日期並且返回。這樣UI上就顯示出我們轉化好的字符串。
看看效果:
好了,到這裏DataBinding的知識我們就算學習完了,在學完之後發現這東西也沒什麼難度,學會使用就ok了,而且android官網也有非常詳細的文檔,
這兩篇博客只是系統的去講解了DataBinding的使用,大家在以後使用的過程中發現忘記怎麼用了,可以再來翻看博客或者直接去官方查看。
ok, 那就到這裏吧,下次見。
參考鏈接:https://developer.android.com/tools/data-binding/guide.html
博客源碼下載:代碼下載,戳這裏