本博客轉自郭霖公衆號:http://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650236908&idx=1&sn=9e53f42e18a81795ef0cfe6fe3959ec2&scene=24&srcid=0910cK3vXJpNzY0CO28i1Qhs#wechat_redirecthtml
說到DataBinding,就有必要先提起MVVM設計模式。
Model–View–ViewModel(MVVM) 是一個軟件架構設計模式,相比MVVM,你們對MVC或MVP可能會更加熟悉。java
在Google I/O 2015上,伴隨着Android M預覽版發佈的Data Binding兼容函數庫。
不知道要扯什麼了,仍是直接上代碼,來看看Data Binding的魅力吧。android
Data Binding對使用的環境仍是有必定要求的(這貨有點挑)
Android Studio版本在1.3以上
gradle的版本要在1.5.0-alpha1以上
須要在Android SDK manager中下載Android Support repository
而後在對應的Module的build.gradle中添加git
android {
....
dataBinding {
enabled =true } }
Gradle須要升級版本的能夠參考升級Gradle版本github
建立一個User類swift
public class User { private String firstName; private String lastName; public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public String getFirstName() { return this.firstName; } public String getLastName() { return this.lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public void setFirstName(String firstName) { this.firstName = firstName; } }
在activity_main.xml中佈局設計模式
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <import type="com.example.gavin.databindingtest.User"/> <variable name="user" type="User" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}" android:textSize="20sp" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.lastName}" android:textSize="25sp" /> </LinearLayout> </layout>
這裏跟平時的佈局有點不一樣,最外層是layout,裏面分別是是data以及咱們的佈局。
data:聲明瞭須要用到的user對象,type用因而定路徑。
能夠在TextView中的看到android:text="@{user.firstName}", 這是什麼鬼,沒見過這麼寫的!!!
(不急,繼續往下看)數組
看看下面的MainActivity網絡
public class MainActivity extends AppCompatActivity { private ActivityMainBinding binding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = DataBindingUtil.setContentView(this, R.layout.activity_main); User user = new User("Micheal", "Jack"); binding.setUser(user); } }
問我ActivityMainBinding哪來的?我怎麼知道...
ActivityMainBinding是根據佈局文件的名字生成的,在後面加了Binding。
運行下看看效果吧架構
有點懵逼了,就綁定了下而已,這些數據是怎麼顯示到界面上的。
他是怎麼工做的?
原來Data Binding 在程序代碼正在編譯的時候,找到全部它須要的信息。而後經過語法來解析這些表達式,最後生成一個類。
經過反編譯咱們能夠看到,Data Binding爲咱們生成了databinding包,以及ActivityMainBinding類(反編譯能夠參考這裏)![]()
看看咱們在onCreate中最後調用的binding.setUser(user),在ActivityMainBinding中能夠看到這個方法。![]()
setUser方法
我想就是這個 super.requestRebind()對數據進行了綁定,至於裏面怎麼實現的,有待進一步研究。
上面只是用一個簡單的例子,展現了Data Binding的用法,若是想在實際項目中使用,可不是上面這例子能夠搞定的。下面就來講說Data Bindig的更多用法。
<data class="MainBinding"> .... </data>
class對應的就是生成的Data Binding名跟Java中的用法類似,佈局文件中支持import的使用,原來的代碼是這樣
<data> <variable name="user" type="com.example.gavin.databindingtest.User" /> </data>
使用import後能夠寫成這樣:
<data> <import type="com.example.gavin.databindingtest.User"/> <variable name="user" type="User" /> </data>
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
遇到相同的類名的時候:
<data> <import type="com.example.gavin.databindingtest.User" alias="User"/> <import type="com.example.gavin.mc.User" alias="mcUser"/> <variable name="user" type="User"/> <variable name="mcUser" type="mcUser"/> </data>
使用alias設置別名,這樣user對應的就是com.example.gavin.databindingtest.User,mcUser就對應com.example.gavin.mc.User,而後
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}"/>
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
當須要用到一些包時,在Java中能夠自動導包,不過在佈局文件中就沒有這麼方便了。須要使用import導入這些包,才能使用。如,須要用到View的時候
<data> <import type="android.view.View"/> </data> ... <TextView ... android:visibility="@{user.isStudent ? View.VISIBLE : View.GONE}" />
注意:只要是在Java中須要導入包的類,這邊都須要導入,如:Map、ArrayList等,不過java.lang包裏的類是能夠不用導包的
在佈局中,不只可使用
android:text="@{user.lastName}"
還可使用表達式如:
在User中添加boolean類型的isStudent屬性,用來判斷是否爲學生。
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text='@{user.isStudent? "Student": "Other"}' android:textSize="30sp"/>
注意:須要用到雙引號的時候,外層的雙引號改爲單引號。
還能夠這樣用
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="學生" android:visibility="@{user.isStudent ? View.VISIBLE : View.GONE}" android:textSize="30sp"/>
這裏用到的View須要在data中聲明
<data> <import type="android.view.View"/> </data>
注意:android:visibility="@{user.isStudent ? View.VISIBLE : View.GONE}",可能會被標記成紅色,不用管它編譯會經過的
除了經常使用的操做法,另外還提供了一個 null 的合併運算符號 ??,這是一個三目運算符的簡便寫法。
contact.lastName ?? contact.name
至關於
contact.lastName != null ? contact.lastName : contact.name
所支持的操做符以下:
數學運算符 + - / * %
字符串拼接 +
邏輯運算 && ||
二進制運算 & | ^
一元運算符 + - ! ~
位運算符 >> >>> <<
比較運算符 == > < >= <=
instanceof
Grouping ()
文字 - character, String, numeric, null
類型轉換 cast
方法調用 methods call
字段使用 field access
數組使用 [] Arrary access
三元運算符 ? :
除了文字的設置,網絡圖片的顯示也是咱們經常使用的。來看看Data Binding是怎麼實現圖片的加載的。
首先要提到BindingAdapter註解,這裏建立了一個類,裏面有顯示圖片的方法。
public class ImageUtil { /** * 使用ImageLoader顯示圖片 * @param imageView * @param url */ @BindingAdapter({"bind:image"}) public static void imageLoader(ImageView imageView, String url) { ImageLoader.getInstance().displayImage(url, imageView); } }
(這方法必須是public static的,不然會報錯)
這裏只用了bind聲明瞭一個image自定義屬性,等下在佈局中會用到。
這個類中只有一個靜態方法imageLoader,裏面有兩參數,一個是須要設置圖片的view,另外一個是對應的Url,這裏使用了ImageLoader庫加載圖片。
看看吧它的佈局是什麼樣的吧
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data > <variable name="imageUrl" type="String"/> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center" > <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" app:image = "@{imageUrl}"/> </LinearLayout> </layout>
最後在MainActivity中綁定下數據就能夠了
binding.setImageUrl(
"http://115.159.198.162:3000/posts/57355a92d9ca741017a28375/1467250338739.jpg");
哇靠!!!就這樣?我都沒看出來它是怎麼設置這些圖片的。
無論了,先看看效果。(其中的原理之後慢慢嘮,這裏就負責說明怎麼使用,這篇已經夠長了,不想再寫了)
使用BindingAdapter的時候,我這還出現了這樣的提示,不過不影響運行。不知道大家會不會...
![]()
【已解決】
感謝顏路同窗指出@BindingAdapter({"bind:image"}) 改爲 @BindingAdapter({"image"}) 就不會有警告了
在MainActivity中聲明方法:
//參數View必須有,必須是public,參數View不能改爲對應的控件,只能是View,不然編譯不經過 public void onClick(View view) { Toast.makeText(this,"點擊事件", Toast.LENGTH_LONG).show(); }
佈局中:
<data> ... <variable name="mainActivity" type="com.example.gavin.databindingtest.MainActivity"/> </data> .... <Button ... android:onClick="@{mainActivity.onClick}" />
最後記得在MainActivity中調用
binding.setMainActivity(this);
(發現:佈局文件中,variable中的name,在binding中都會生成一個對應的set方法,如:setMainActivity。有set方法,那就應該有get方法,試試getMainActivity,還真有)
運行下看看效果
固然若是你不想吧點擊事件寫在MainActivity中,你把它單獨寫在一個類裏面:
public class MyHandler { public void onClick(View view) { Toast.makeText(view.getContext(), "點擊事件", Toast.LENGTH_LONG).show(); } }
<data> ... <variable name="handle" type="com.example.gavin.databindingtest.MyHandler"/> </data> .... <Button ... android:onClick="@{handle.onClick}" /> </data>
在MainActivity調用
binding.setHandle(new MyHandler());
public static String mName = "MM";
佈局中 <data> ... <variable name="mainActivity" type="com.example.gavin.databindingtest.MainActivity"/> </data> <Button ... android:text="@{mainActivity.mName}" />
注意:這個變量必須是public staticprivate ActivityMainBinding binding; private User user; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = DataBindingUtil.setContentView(this, R.layout.activity_main); user = new User("Micheal", "Jack"); binding.setUser(user); binding.setHandle(new MyHandler()); delay(); } /** * 兩秒後改變firstName */ private void delay() { new Handler().postDelayed(new Runnable() { @Override public void run() { user.setFirstName("Com"); binding.setUser(user); } }, 2000); }
看看調用的這個setUser是什麼:使用上面的代碼實現了UI的更新你就知足了?其實官方爲咱們提供了更加簡便的方式,使User繼承BaseObservable,代碼以下
public class User extends BaseObservable { private String firstName; private String lastName; public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } @Bindable public String getFirstName() { return this.firstName; } @Bindable public String getLastName() { return this.lastName; } public void setLastName(String lastName) { this.lastName = lastName; notifyPropertyChanged(BR.lastName); } public void setFirstName(String firstName) { this.firstName = firstName; notifyPropertyChanged(BR.firstName); } }
只要user發生變化,就能達到改變UI的效果。在MainActivity中只要調用如下代碼
user.setFirstName("Com");
有了BaseObservable就夠了?不不不,我比較懶,不想寫那麼多@Bindable和notifyPropertyChanged。萬一裏面有幾十個屬性,那不寫哭起來?並且還有可能寫丟了。
Data Binding的開發者貼心得爲咱們準備了一系列的ObservableField,包括: ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat,ObservableDouble, 以及 ObservableParcelable看看它們的用法
ObservableField的使用
一、建立User2
public class User2 { public final ObservableField<String> firstName = new ObservableField<>(); public final ObservableField<String> lastName = new ObservableField<>(); public final ObservableInt age = new ObservableInt(); public final ObservableBoolean isStudent = new ObservableBoolean(); }
這類裏面沒有Get/Set。
二、佈局文件
<TextView ... android:text="@{user2.firstName}" /> <TextView ... android:text="@{user2.lastName}" /> <TextView ... android:text="@{String.valueOf(user2.age)}" />
三、MainActivity中
mUser2 = new User2(); binding.setUser2(mUser2); mUser2.firstName.set("Mr"); mUser2.lastName.set("Bean"); mUser2.age.set(20); mUser2.isStudent.set(false);
這裏new了一個User2對象後,直接就綁定了。以後只要mUser2中的數據發生變化,UI也會隨之更新。
除了這幾個Map跟List也是必不可少的,Data Binding爲咱們提供了 ObservableArrayMap和ObservableArrayList。
ObservableArrayMap的使用
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>(); user.put("firstName", "Google"); user.put("lastName", "Inc."); user.put("age", 17);
<data>
<import type="android.databinding.ObservableMap"/> <variable name="user" type="ObservableMap<String, Object>"/> </data> … <TextView android:text='@{user["lastName"]}' android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:text='@{String.valueOf(1 + (Integer)user["age"])}' android:layout_width="wrap_content" android:layout_height="wrap_content"/>
ObservableArrayList的使用
ObservableArrayList<Object> user = new ObservableArrayList<>(); user.add("Google"); user.add("Inc."); user.add(17);
<data> <import type="android.databinding.ObservableList"/> <import type="com.example.my.app.Fields"/> <variable name="user" type="ObservableList<Object>"/> </data> … <TextView android:text='@{user[Fields.LAST_NAME]}' android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}' android:layout_width="wrap_content" android:layout_height="wrap_content"/>
在佈局中使用中文時,編譯沒法經過。
android:text='@{user2.isStudent?"學生":"非學生"}'
感謝呂檀溪同窗的解決方案:
這是java環境的問題,在系統環境變量中增長一個變量,變量名爲: JAVA_TOOL_OPTIONS, 變量值爲:-Dfile.encoding=UTF-8,保存。要重啓一次電腦,中文就解決了,可是在某些地方,編譯的時候控制檯會出現部分亂
前面說了那麼多基礎的用法,可仍是不能達到咱們的需求。幾乎在每一個app中都有列表的存在,RecyclerView或ListView,從上面所說的彷佛還看不出Data Binding在RecyclerView或ListView中是否也能起做用。(用屁股想也知道,Google的開發團對怎麼可能會犯這麼低級的錯誤)。下面以RecyclerView爲例子:
一、直接看Item的佈局(user_item.xml):
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="user2" type="com.example.gavin.databindingtest.User2" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="10dp" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user2.firstName}"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="·"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user2.lastName}"/> <View android:layout_width="0dp" android:layout_height="0dp" android:layout_weight="1"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text='@{user2.age+""}'/> </LinearLayout> </layout>
二、RecyclerView的數據綁定是在Adapter中完成的,下面看看Adapter(這裏使用了一個Adapter,若是你在使用的時候發現RecyclerView的動畫沒了,去這裏尋找答案)
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyHolder> { private List<User2> mData = new ArrayList<>(); public MyAdapter(List<User2> data) { this.mData = data; } @Override public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) { return MyHolder.create(LayoutInflater.from(parent.getContext()), parent); } @Override public void onBindViewHolder(MyHolder holder, int position) { holder.bindTo(mData.get(position)); } @Override public int getItemCount() { if (mData == null) return 0; return mData.size(); } static class MyHolder extends RecyclerView.ViewHolder { private UserItemBinding mBinding; static MyHolder create(LayoutInflater inflater, ViewGroup parent) { UserItemBinding binding = UserItemBinding.inflate(inflater, parent, false); return new MyHolder(binding); } private MyHolder(UserItemBinding binding) { super(binding.getRoot()); this.mBinding = binding; } public void bindTo(User2 user) { mBinding.setUser2(user); mBinding.executePendingBindings(); } } }
三、最後在佈局和MainActivity中的使用跟平時的用法同樣
佈局中加入RecyclerView:
<android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent"/>
MainActivity中:
List<User2> data = new ArrayList<>(); for (int i = 0; i < 20; i++) { User2 user2 = new User2(); user2.age.set(30); user2.firstName.set("Micheal " + i); user2.lastName.set("Jack " + i); data.add(user2); } RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view); LinearLayoutManager layoutManager = new LinearLayoutManager( this, LinearLayoutManager.VERTICAL, false); recyclerView.setLayoutManager(layoutManager); recyclerView.setAdapter(new MyAdapter(data));
這樣就能夠了。
不過,在自動生成的ActivityMainBinding中,咱們能夠看到根據RecyclerView的id,會自動生成一個recyclerView。
因此在MainActivity中,咱們能夠不用findViewById,直接使用binding.recyclerView。
LinearLayoutManager layoutManager = new LinearLayoutManager( this, LinearLayoutManager.VERTICAL, false); binding.recyclerView.setLayoutManager(layoutManager); binding.recyclerView.setAdapter(new MyAdapter(data));
來看看效果吧:
user.age爲int類型,須要這樣用
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text='@{""+user.age}'/>
或者
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{String.valueOf(user.age)}"/>
Google官方(權威,不過全英文。點擊事件寫的好像不對,後來去其餘地方查的):
Realm(十分全面):
CSDN-亓斌(有點像google文檔的翻譯版,總體結果類似):
陽春麪的博客(好奇怪的名字)
源碼地址https://github.com/Gavin-ZYX/DataBindingTest