繼續上篇的博客《[Android官方數據綁定框架DataBinding(一)](http://blog.csdn.net/qibin0506/article/details/47393725)》我們繼續學習Data Binding的使用。
十、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();
}
}
~~~
重點來了,我們這里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>
~~~
如果你看過上篇博客,那么這里也很簡單,簡單說一下吧。兩個TextView分別綁定了Student的name和age字段,而且給name添加了一個點擊事件,點擊后會調用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個參數,第一個參數是一個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);
}
}
~~~
主要還是來看`click`方法中,這里我們需要獲取TextView的實例,用來改變他的顏色,我們是通過`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](https://developer.android.com/tools/data-binding/guide.html)
博客源碼下載:[代碼下載,戳這里](http://download.csdn.net/detail/qibin0506/9013513)
- 前言
- 高逼格UI-ASD(Android Support Design)
- AndroidSupportDesign之TabLayout使用詳解
- RecyclerView的高級用法——定制動畫
- Android官方數據綁定框架DataBinding(一)
- Android官方數據綁定框架DataBinding(二)
- 你所不知道的Activity轉場動畫——ActivityOptions
- RecyclerView+ImageLoader打造多選圖庫
- Android Material Design動畫
- RecyclerView添加Header的正確方式
- CoordinatorLayout高級用法-自定義Behavior