企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
继续上篇的博客《[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`可以获取我们加载的布局,是不是很简单? 来看一下效果: ![](https://box.kancloud.cn/2016-02-18_56c55b3a63787.jpg "") 十一、 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,现在看起来,我们的代码更加简洁了,而且不需要保存控件的实例,是不是很爽? 来看看效果: ![](https://box.kancloud.cn/2016-02-18_56c55b3a7689f.jpg "") 十二、 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&lt;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}"/> ~~~ 看看运行结果: ![](https://box.kancloud.cn/2016-02-18_56c55b3a91055.jpg "") 十四、 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上就显示出我们转化好的字符串。 看看效果: ![](https://box.kancloud.cn/2016-02-18_56c55b3aa4a08.jpg "") 好了,到这里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)