当前位置:   article > 正文

Android 官方框架DataBinding学习笔记

loginmessagev2binding.inflate()

DataBinding是谷歌官方发布的一个框架,它的目的是降低布局和逻辑的耦合性,使代码的逻辑更清晰。它能够很简单的省去findViewById()的步骤,大量减少Activity的代码,数据直接能写在layout文件上,而且它能自动进行空检测,很多地方对象为空不会引起空指针异常。

下面我将从以下几个方面介绍DataBinding框架:

  1. DataBinding在AndroidStudio下的环境搭建
  2. DataBinding的简单使用
  3. DataBinding的事件处理
  4. layout文件细节
  5. 观察者对象
  6. 生成Binding
  7. 属性setters

###DataBinding在AndroidStudio下的环境搭建

由于DataBinding是谷歌的官方框架,所以环境搭建很简单,只需在model下的build.gradle文件上加上如下代码:

  1. android {
  2. ···
  3. dataBinding {
  4. enabled = true
  5. }
  6. ···
  7. }
  8. 复制代码

不过这要求你的Gradle是 1.5.0-alpha1或者更新的版本,AndroidStudio1.3或更新的版本才可以。DataBinding是一个兼容库,他能运行在Android2.1以上(Api level7+)。

###DataBinding的简单使用

DataBinding的layout文件与我们一般写的layout文件不一样,它包含数据和视图两方面,所以其的layout文件如下:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <layout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. tools:context="com.example.databindingtest.MainActivity">
  5. <data>
  6. <variable
  7. name="user"
  8. type="com.example.databindingtest.User"/>
  9. </data>
  10. <LinearLayout
  11. android:layout_width="match_parent"
  12. android:layout_height="match_parent"
  13. android:orientation="vertical">
  14. <TextView
  15. android:layout_width="wrap_content"
  16. android:layout_height="wrap_content"
  17. android:text="@{user.firstName}"/>
  18. <TextView
  19. android:layout_width="wrap_content"
  20. android:layout_height="wrap_content"
  21. android:text="@{user.lastName}"/>
  22. </LinearLayout>
  23. </layout>
  24. 复制代码

user是一个java对象,它是被用于这个layout的一个变量。 User类如下

  1. public class User
  2. {
  3. public String firstName;
  4. public String lastName;
  5. public User(String firstName, String lastName)
  6. {
  7. this.firstName = firstName;
  8. this.lastName = lastName;
  9. }
  10. }
  11. 复制代码

当在layout文件中写@{user.firstName}时,会使用user对象的firstName属性。 一个用DataBinding的方式写出的layout文件会产生一个类,类名为layout文件名的驼峰式写法加上Binding,所以activity_main.xml会对应于ActivityMainBinding类,这个类包含了layout文件的性能,包括数据和视图两部分,可以用如下方式绑定Activity和布局文件:

  1. DataBindingUtil.setContentView(MainActivity.this, R.layout.main_activity);
  2. 复制代码

完整的Activity文件如下:

  1. public class MainActivity extends AppCompatActivity
  2. {
  3. private ActivityMainBinding mBinding;
  4. private User mUser;
  5. @Override
  6. protected void onCreate(Bundle savedInstanceState)
  7. {
  8. super.onCreate(savedInstanceState);
  9. mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
  10. mUser = new User("李", "晓峰");
  11. mBinding.setUser(mUser);
  12. mBinding.setHandler(new MyHandlers());
  13. mBinding.setPresenter(new Presenter());
  14. }
  15. }
  16. 复制代码

运行效果:

如此就完成了DataBinding的最简单体验。

也可以把User类写成这样:

  1. public class User
  2. {
  3. public String getFirstName()
  4. {
  5. return firstName;
  6. }
  7. public void setFirstName(String firstName)
  8. {
  9. this.firstName = firstName;
  10. }
  11. public String getLastName()
  12. {
  13. return lastName;
  14. }
  15. public void setLastName(String lastName)
  16. {
  17. this.lastName = lastName;
  18. }
  19. private String firstName;
  20. private String lastName;
  21. public User(String firstName, String lastName)
  22. {
  23. this.firstName = firstName;
  24. this.lastName = lastName;
  25. }
  26. }
  27. 复制代码

此时布局文件不能直接调用userfirstName属性,如果你Ctrl+左键点击ActivityMainBinding类,会跳到对应的布局文件,这也就能理解为什么布局文件不能访问user的除public权限外的其余的属性和方法了。 但是其实布局文件不用做任何修改,也能达到同样的效果,这是因为@{user.firstName}会去调用usergetFirstName()方法,也可以把布局文件写成@{user.getFirstName},将会直接调用getFirstName()方法。

如果布局文件写成@{user.firstName}User类同时包含firstName()方法和getFirstName()方法,会优先调用哪个方法呢? 将User类改成

  1. public class User
  2. {
  3. ···
  4. public String getFirstName()
  5. {
  6. Log.e("User", "getFirstName");
  7. return firstName;
  8. }
  9. public String firstName()
  10. {
  11. Log.e("User", "firstName");
  12. return "12132";
  13. }
  14. ···
  15. }
  16. 复制代码

打印日志如下:

  1. com.example.databindingtest E/User: getFirstName
  2. 复制代码

所以说getFirstName()方法被调用了,而firstName()方法并没有被调用。

###DataBinding的事件处理 DataBinding允许我们直接将事件写在控件上,事件的属性名决定于Listener的方法名,例如长按事件View.OnLongClickListener有方法onLongClick(),那么其对应的属性为android:onLongClick。 有两种处理事件的方式:

  • Method References 事件为一个对象及其的方法
  • Listener Bindings 事件为一个Lambda表达式

######Method References Method References设置事件和使用android:onClick属性,将方法写在Activity是很相似的,主要的区别是DataBinding的Method References对表达式的检验是在编译期,因此如果方法为空或者不正确,会在编译期被发现。且它的方法不必写在Activity里。

事件处理类:

  1. public class MyHandlers
  2. {
  3. public void click(View view)
  4. {
  5. Log.e("MyHandlers","onClick");
  6. }
  7. public boolean longClick(View view)
  8. {
  9. Log.e("MyHandlers","onLongClick");
  10. return true;
  11. }
  12. }
  13. 复制代码

注意,方法的输入参数和返回值必须和事件监听器的方法一样。 xml文件:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <layout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. tools:context="com.example.databindingtest.MainActivity">
  5. <variable
  6. name="handler"
  7. type="com.example.databindingtest.MyHandlers"/>
  8. </data>
  9. <LinearLayout
  10. android:layout_width="match_parent"
  11. android:layout_height="match_parent"
  12. android:orientation="vertical">
  13. <Button
  14. android:layout_width="wrap_content"
  15. android:layout_height="wrap_content"
  16. android:onClick="@{handler::click}"
  17. android:onLongClick="@{handler::longClick}"
  18. android:text="button"/>
  19. </LinearLayout>
  20. </layout>
  21. 复制代码

Activity加上

  1. mBinding.setHandler(new MyHandlers());
  2. 复制代码

分别点击和长按按钮,打印日志如下:

  1. com.example.databindingtest E/MyHandlers: onClick
  2. com.example.databindingtest E/MyHandlers: onLongClick
  3. 复制代码

######Listener Bindings Listener Bindings是当事件发生的时候绑定表达式,它和Method References是类似的,但是它能绑定任意的输入类型方法,而不必和事件监听器的一样,不过返回值要和事件监听器一样。它的表达式要写成Lambda表达式。

事件处理类:

  1. public class Presenter
  2. {
  3. public void click()
  4. {
  5. Log.e( "Presenter"," onClick");
  6. }
  7. public boolean longClick()
  8. {
  9. Log.e( "Presenter","onLongClick");
  10. return true;
  11. }
  12. }
  13. 复制代码

xml文件:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <layout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. tools:context="com.example.databindingtest.MainActivity">
  5. <data>
  6. <variable
  7. name="presenter"
  8. type="com.example.databindingtest.Presenter"/>
  9. </data>
  10. <LinearLayout
  11. android:layout_width="match_parent"
  12. android:layout_height="match_parent"
  13. android:orientation="vertical">
  14. <Button
  15. android:layout_width="wrap_content"
  16. android:layout_height="wrap_content"
  17. android:onClick="@{()->presenter.click()}"
  18. android:onLongClick="@{()->presenter.longClick()}"
  19. android:text="button"/>
  20. </LinearLayout>
  21. </layout>
  22. 复制代码

Activity加上

  1. mBinding.setPresenter(new Presenter());
  2. 复制代码

日志如下:

  1. com.example.databindingtest E/Presenter: onClick
  2. com.example.databindingtest E/Presenter: onLongClick
  3. 复制代码

所以我们在使用MVP模式的时候,就可以不必去Activity里去绑定控件的事件与Presenter里的方法了。

在这里我们没有传递view属性到View.onClick方法中, Listener bindings提供给我们两种选择:忽略方法的所有输入参数或者为方法的所有输入参数命名。 例如我们可以写成:

  1. android:onClick="@{(view)->presenter.click()}"
  2. 复制代码

如果我们想要在表达式中使用view,那么可以这样写:

  1. public void click(View view)
  2. {
  3. if (view instanceof Button)
  4. {
  5. Log.e("Presenter", " onClick");
  6. }
  7. }
  8. android:onClick="@{(button)->presenter.click(button)}"
  9. 复制代码

表达式也可以有自己的输入参数:

  1. public void click(Task task)
  2. android:onClick="@{()->presenter.click(task)}"
  3. 复制代码
  1. public void click(View view,Task task)
  2. android:onClick="@{(view)->presenter.click(view,task)}"
  3. 复制代码

有一些点击事件与android:onClick有冲突,DataBinding给他们分配了一些特别的属性名:

ViewListener SetterAttribute
SearchViewsetOnSearchClickListener(View.OnClickListener)android:onSearchClick
ZoomControlssetOnZoomInClickListener(View.OnClickListener)android:onZoomIn
ZoomControlssetOnZoomOutClickListener(View.OnClickListener)android:onZoomOut

###layout文件细节 前面我们对于Databinding的布局文件做了简单的介绍,现在详细介绍布局文件细节。 ######类的导入 就像Java一样,类的使用可以是带包名的类名,也支持导入:

  1. <data>
  2. <import type="com.example.databindingtest.User"/>
  3. <variable
  4. name="user"
  5. type="com.example.databindingtest.User"/>
  6. </data>
  7. 复制代码

java.lang包下的类不需要导入,可以直接使用

  1. <variable
  2. name="str"
  3. type="String"/>
  4. 复制代码

在布局文件中可以直接使用类的静态变量和方法,不需要调用ViewDataBinding.setXxx()

  1. public class StaticClass
  2. {
  3. public static String name = "StaticClass";
  4. public static void printf(View v)
  5. {
  6. Log.e("StaticClass", "printf");
  7. }
  8. }
  9. 复制代码
  1. <Button
  2. android:layout_width="wrap_content"
  3. android:layout_height="wrap_content"
  4. android:text="@{StaticClass.name}"
  5. android:onClick="@{StaticClass::printf}"/>
  6. 复制代码

注意,此时使用Listener Bindings引用方法会报错。

类名支持设置别名:

  1. <import
  2. alias="V"
  3. type="android.view.View"/>
  4. 复制代码
  1. <TextView
  2. android:layout_width="wrap_content"
  3. android:layout_height="wrap_content"
  4. android:text="@{user.lastName}"
  5. android:visibility="@{user.adult?V.VISIBLE:V.GONE}"/>
  6. 复制代码

这样能防止出现类名相同的情况而造成的类名无法识别。

定义和使用集合:

  1. <data>
  2. <import type="com.example.User"/>
  3. <import type="java.util.List"/>
  4. <variable name="userList" type="List<User>"/>
  5. </data>
  6. 复制代码
  1. <TextView
  2. android:id="@+id/name"
  3. android:layout_width="wrap_content"
  4. android:layout_height="wrap_content"
  5. android:text="@{userList[0].firstName}"/>
  6. 复制代码

其中"<"和">"要使用Html的转义代替,此时AndroidStudio可能会爆红,可是是正确的,可以正确运行。

Map集合与其类似

  1. android:text="@{map["key"]}"
  2. 复制代码

但此时引号有冲突了,所以将外层引号改成单引号:

  1. android:text='@{map["key"]}'
  2. 复制代码

######Binding类名自定义 Binding类的类名可以自定义:

  1. <data class="ContactItem">
  2. ...
  3. </data>
  4. 复制代码

指定包名:

  1. <data class="com.example.ContactItem">
  2. ...
  3. </data>
  4. 复制代码

######DataBinding运算符和空检测 DataBinding的表达式是支持简单的运算符的

  • Mathematical (数学计算符) + - / * %
  • String concatenation(字符串拼接符) +
  • Logical(逻辑运算符) && ||
  • Binary(位运算符) & | ^
  • Unary(单目运算符) + - ! ~
  • Shift(位移运算符) >> >>> <<
  • Comparison(比较运算符) == > < >= <=
  • instanceof
  • Grouping ()
  • Literals - character, String, numeric, null
  • Cast
  • Method calls
  • Field access
  • Array access []
  • Ternary operator(三元运算符) ?:

基本上和Java保持一致。

举例:

  1. android:text="@{String.valueOf(index + 1)}"
  2. android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
  3. android:transitionName='@{"image_" + id}'
  4. 复制代码

不支持this,super,new三个关键词。 空检测:

  1. android:text="@{user.displayName ?? user.lastName}"
  2. 复制代码

它会根据??运算符左边的对象是否为空来选择值,左边为空现在左边,否则选择右边。 等价于:

  1. android:text="@{user.displayName != null ? user.displayName : user.lastName}"
  2. 复制代码

注意,DataBinding自动进行了很多空指针检测,对象为空调用它的属性或方法不会引起程序崩溃,而是赋予默认值。例如对于这个

  1. <TextView
  2. android:layout_width="wrap_content"
  3. android:layout_height="wrap_content"
  4. android:text="@{user.lastName}"
  5. android:onClick="@{(button)->user.click(button)}"/>
  6. 复制代码

如果user对象为空,那么user.lastName会被分配它的默认值,对象是nullint0等等;TextView的点击事件会相当于没有设置。 ######资源使用 在表达式中使用xml文件定义的资源是可以的:

  1. android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
  2. 复制代码

常用的资源和其对应注解如下:

Type(类型)Normal Reference(普通引用)Expression Reference(表达式引用)
String[]@array@stringArray
int[]@array@intArray
TypedArray@array@typedArray
Animator@animator@animator
StateListAnimator@animator@stateListAnimator
color int@color@color
ColorStateList@color@colorStateList

###观察者对象 在我们目前的代码中,如果对象改变了某个属性,UI是无法自动更新的,其实很好理解,在User.setFirstName()方法中,它只是改变了user类的属性,而没有通知UI层,DataBinding已经封装好了通知UI层的方法:

  1. public class User extends BaseObservable {
  2. private String firstName;
  3. private String lastName;
  4. @Bindable
  5. public String getFirstName() {
  6. return this.firstName;
  7. }
  8. @Bindable
  9. public String getLastName() {
  10. return this.lastName;
  11. }
  12. public void setFirstName(String firstName) {
  13. this.firstName = firstName;
  14. notifyPropertyChanged(BR.firstName);
  15. }
  16. public void setLastName(String lastName) {
  17. this.lastName = lastName;
  18. notifyPropertyChanged(BR.lastName);
  19. }
  20. }
  21. 复制代码

在字段的get方法或定义处添加@Bindable注解,就可以在BR类生成对应的字段,然后继承BaseObservable类,就可以调用通知UI层具体属性修改的方法了。 在按钮的点击事件上加上如下方法实现,就能看到UI的更改动画:

  1. valueAnimator = ValueAnimator.ofInt(0, 100);
  2. valueAnimator.setDuration(10000);
  3. valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
  4. {
  5. @Override
  6. public void onAnimationUpdate(ValueAnimator valueAnimator)
  7. {
  8. int animatedValue = (int) valueAnimator.getAnimatedValue();
  9. mUser.setFirstName("李"+animatedValue);
  10. }
  11. });
  12. valueAnimator.start();
  13. 复制代码

凡是在layout文件里面出现了的属性,均可以在BR类里面找到其的对应。

######ObservableFields 使用@Bindable注解并调用notifyPropertyChanged(BR.xxx)方法能达到自动更新UI的目的,可是过于繁琐,所以DataBinding为我们封装好了更简单易用的类ObservableFields,它的源代码很简单:

  1. public class ObservableField<T> extends BaseObservable implements Serializable {
  2. private T mValue;
  3. public ObservableField(T value) {
  4. mValue = value;
  5. }
  6. public ObservableField() {
  7. }
  8. public T get() {
  9. return mValue;
  10. }
  11. public void set(T value) {
  12. if (value != mValue) {
  13. mValue = value;
  14. notifyChange();
  15. }
  16. }
  17. }
  18. 复制代码

在每次调用set()方法的时候,均会调用notifyChange()方法,这个方法也是BaseObservable提供的,效果等同于notifyPropertyChanged(BR._all)

使用举例: 实体类

  1. public class Person
  2. {
  3. public ObservableField<String> name = new ObservableField<>();
  4. public ObservableField<String> address = new ObservableField<>();
  5. public ObservableInt age = new ObservableInt();
  6. }
  7. 复制代码

布局文件

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <layout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. xmlns:app="http://schemas.android.com/apk/res-auto">
  5. <data>
  6. <variable
  7. name="person"
  8. type="com.example.databindingtest.Person"/>
  9. </data>
  10. <LinearLayout
  11. android:orientation="vertical"
  12. android:layout_width="match_parent"
  13. android:layout_height="match_parent"
  14. tools:context="com.example.databindingtest.SecondActivity">
  15. <TextView
  16. android:text='@{person.name}'
  17. android:id="@+id/tv_name"
  18. android:layout_width="wrap_content"
  19. android:layout_height="wrap_content"/>
  20. </LinearLayout>
  21. </layout>
  22. 复制代码

改变属性的方法调用

  1. Person person = new Person();
  2. mBinding.setPerson(person);
  3. person.name.set("lei");
  4. 复制代码

当数据是集合时,使用ObservableArrayMap

  1. ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
  2. mBinding.setUser(user);
  3. user.put("firstName","1437");
  4. user.put("lastName","dufjklsg");
  5. 复制代码
  1. <data>
  2. <variable
  3. name="user"
  4. type="android.databinding.ObservableArrayMap<String,Object>"/>
  5. </data>
  6. ···
  7. <LinearLayout
  8. android:orientation="vertical"
  9. android:layout_width="match_parent"
  10. android:layout_height="match_parent"
  11. tools:context="com.example.databindingtest.SecondActivity">
  12. <TextView
  13. android:layout_width="wrap_content"
  14. android:layout_height="wrap_content"
  15. android:text='@{user["firstName"]}'/>
  16. <TextView
  17. android:layout_width="wrap_content"
  18. android:layout_height="wrap_content"
  19. android:text='@{user["lastName"]}'/>
  20. </LinearLayout>
  21. 复制代码

在布局文件和java代码上的写法和普通集合基本一致。 如果key不是字符串而是数字下标,则使用ObservableArrayList和之前ArrayList的用法在形式上基本上一致。

###生成Binding 之前我们已经展示了一种生成Binding类绑定Activity的方法

  1. mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
  2. 复制代码

还有另一种方式:

  1. mBinding = ActivitySecondBinding.inflate(getLayoutInflater());
  2. setContentView(mBinding.getRoot());
  3. 复制代码

第一行生成Binding类,第二行绑定Activity,这种方式在使用RecyclerView的时候会很有用,能够拿到Binding类,还可以通过ViewDataBinding.getRoot()获取根布局。

适配器的例子:

  1. public class MessageAdapter extends RecyclerView.Adapter<MessageAdapter.MyViewHolder>
  2. {
  3. private LayoutInflater mInflater;
  4. private ArrayList<Message> messageArrayList;
  5. private Context context;
  6. public MessageAdapter(ArrayList<Message> messageArrayList, Context context)
  7. {
  8. this.messageArrayList = messageArrayList;
  9. this.context = context;
  10. mInflater = LayoutInflater.from(context);
  11. }
  12. @Override
  13. public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
  14. {
  15. return MyViewHolder.create(mInflater);
  16. }
  17. @Override
  18. public void onBindViewHolder(MyViewHolder holder, int position)
  19. {
  20. holder.mBinding.setMessage(messageArrayList.get(position));
  21. }
  22. @Override
  23. public int getItemCount()
  24. {
  25. return messageArrayList == null ? 0 : messageArrayList.size();
  26. }
  27. static class MyViewHolder extends RecyclerView.ViewHolder
  28. {
  29. private ThirdBinding mBinding;
  30. private MyViewHolder(ThirdBinding binding)
  31. {
  32. super(binding.getRoot());
  33. mBinding = binding;
  34. }
  35. private static MyViewHolder create(LayoutInflater inflater)
  36. {
  37. ThirdBinding mBinding = ThirdBinding.inflate(inflater);
  38. return new MyViewHolder(mBinding);
  39. }
  40. private void bindData(Message message)
  41. {
  42. mBinding.setMessage(message);
  43. }
  44. }
  45. }
  46. 复制代码

######自动生成控件对象 在生成Binding类的同时,DataBinding会根据我们在布局文件中设置的id自动生成对应的字段:

  1. <Button
  2. android:id="@+id/button_test"
  3. android:layout_width="wrap_content"
  4. android:layout_height="wrap_content"
  5. android:text="12312"/>
  6. 复制代码

在具体的Binding类上的字段

  1. public final Button buttonTest;
  2. 复制代码

依旧像之前自动转换成驼峰式命名,使用直接调用这个字段就可以:

  1. mBinding.buttonTest.setText("");
  2. 复制代码

这就是我们完全没必要使用findViewById()了。

######变量的set,get方法

正如我们之前所看到的那样,我们在data标签下所申明的变量会生成对应的setget方法:

  1. <variable
  2. name="user"
  3. type="com.example.databindingtest.User"/>
  4. 复制代码
  1. mBinding.setUser(mUser);
  2. mBinding.getUser();
  3. 复制代码

###属性setters

对于在布局文件中控件的每一个用表达式描述的属性,DataBinding会试着寻找方法来设置属性。属性的名称空间不必匹配,仅仅根据属性名本身。例如,用表达式关联的TextView的属性android:text会寻找方法setText(String),如果表达式返回的是int,则会寻找方法setText(int)。如果在已给出的属性中没有某一个属性名,但是有set方法,那么我们能很简单的设置属性。例如对于DrawerLayout,他有方法public void setScrimColor(@ColorInt int color)但是没有属性android:scrimColor,我们可以自动的设置上这个属性:

  1. <android.support.v4.widget.DrawerLayout
  2. android:layout_width="wrap_content"
  3. android:layout_height="wrap_content"
  4. app:scrimColor="@{@color/scrim}"
  5. app:drawerListener="@{fragment.drawerListener}"/>
  6. 复制代码

基于此,我们就能很简单的自定义控件,而不必去写属性值的xml文件,例如写一个能设置头的TextView:

  1. public class HeadTextView extends android.support.v7.widget.AppCompatTextView
  2. {
  3. public HeadTextView(Context context, @Nullable AttributeSet attrs)
  4. {
  5. super(context, attrs);
  6. }
  7. public void setStartText(String startText)
  8. {
  9. String text = startText + getText().toString();
  10. setText(text);
  11. }
  12. }
  13. 复制代码
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <layout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:app="http://schemas.android.com/apk/res-auto"
  4. xmlns:bind="http://schemas.android.com/apk/res-auto"
  5. xmlns:tools="http://schemas.android.com/tools"
  6. tools:context="com.example.databindingtest.MainActivity">
  7. <data>
  8. <variable
  9. name="head"
  10. type="String"/>
  11. </data>
  12. <LinearLayout
  13. android:layout_width="match_parent"
  14. android:layout_height="match_parent"
  15. android:orientation="vertical">
  16. <com.example.databindingtest.HeadTextView
  17. android:layout_width="wrap_content"
  18. android:layout_height="wrap_content"
  19. android:text="183****0038"
  20. app:startText="@{head}"/>
  21. </LinearLayout>
  22. </layout>
  23. 复制代码
  1. mBinding.setHead("电话号码:");
  2. 复制代码

当然,这个自定义控件还有更多细节需要完善。

注意,app:startText的属性值必须要是表达式,而不能像常规的那样写成app:startText="电话号码:",只有表达式才能引起DataBinding的机制。不过可以写成app:startText="@{@string/phone}",因为表达式里是可以引用资源的。

一些属性有set方法但是名称不匹配,可以使用BindingMethods注解来联系起方法和属性,例如对于android:hint属性,它对应的方法是setImageTintList(ColorStateList),而不是setTint,可以在类的上面加上以下注解来完成匹配:

  1. @BindingMethods({
  2. @BindingMethod(type = ImageView.class,
  3. attribute = "android:tint",
  4. method = "setImageTintList"),
  5. })
  6. 复制代码

注意,此处官方文档写的有误。

Android的框架已经帮我们把framework层的属性做了匹配。 对于HeadTextView,可以改成:

  1. @BindingMethods({
  2. @BindingMethod(type = TextView.class,
  3. attribute = "app:startText",
  4. method = "setStartText111"),
  5. })
  6. public class HeadTextView extends android.support.v7.widget.AppCompatTextView
  7. {
  8. public HeadTextView(Context context, @Nullable AttributeSet attrs)
  9. {
  10. super(context, attrs);
  11. }
  12. public void setStartText111(String startText)
  13. {
  14. String text = startText + getText().toString();
  15. setText(text);
  16. }
  17. }
  18. 复制代码

我们可以自己为属性写set方法,例如对于android:paddingLeft属性,并没有对应的方法,而有方法setPadding(left, top, right, bottom)存在,使用BindingAdapter注解能定制属性的set方法。

  1. @BindingAdapter("android:paddingLeft")
  2. public static void setPaddingLeft(View view, int padding) {
  3. view.setPadding(padding,
  4. view.getPaddingTop(),
  5. view.getPaddingRight(),
  6. view.getPaddingBottom());
  7. }
  8. 复制代码

之前的自定义控件可以这样修改:

  1. public class AttrAdapter
  2. {
  3. @BindingAdapter("app:startText")
  4. public static void setHead(TextView textView, String head)
  5. {
  6. textView.setText(head + textView.getText());
  7. }
  8. }
  9. 复制代码
  1. <import type="com.example.databindingtest.AttrAdapter"/>
  2. 复制代码
  1. <TextView
  2. android:layout_width="wrap_content"
  3. android:layout_height="wrap_content"
  4. android:text="183****0038"
  5. app:startText="@{@string/phone}"/>
  6. 复制代码

也就是说我们可以在已有的控件上任意的拓展属性!

我们也可以用适配器来接受多个属性:

  1. @BindingAdapter({"bind:imageUrl", "bind:error"})
  2. public static void loadImage(ImageView view, String url, Drawable error) {
  3. Picasso.with(view.getContext()).load(url).error(error).into(view);
  4. }
  5. 复制代码
  1. <ImageView app:imageUrl="@{venue.imageUrl}"
  2. app:error="@{@drawable/venueError}"/>
  3. 复制代码

app:imageUrlapp:error两个属性都被设置了的时候,会调用loadImage方法。

适配器里也可以接受之前的属性:

  1. @BindingAdapter("android:paddingLeft")
  2. public static void setPaddingLeft(View view, int oldPadding, int newPadding)
  3. {
  4. Log.e("AttrAdapter", "oldPadding=" + oldPadding + " newPadding=" + newPadding);
  5. if (oldPadding != newPadding)
  6. {
  7. view.setPadding(newPadding,
  8. newPadding,
  9. view.getPaddingRight(),
  10. view.getPaddingBottom());
  11. }
  12. }
  13. 复制代码
  1. <variable
  2. name="left"
  3. type="int"/>
  4. 复制代码
  1. <TextView
  2. android:paddingLeft="@{left}"
  3. android:layout_width="wrap_content"
  4. android:layout_height="wrap_content"
  5. android:text="183****0038"
  6. app:startText="@{@string/phone}"/>
  7. 复制代码
  1. mBinding.setLeft(10);
  2. 复制代码

打印Log如下:

  1. com.example.databindingtest E/AttrAdapter: oldPadding=0 newPadding=10
  2. 复制代码

表达式的输入值可以与属性要求的值不一样,它会自动寻找要求的方法:

  1. <TextView
  2. android:layout_width="wrap_content"
  3. android:layout_height="wrap_content"
  4. android:text="@{user}"/>
  5. 复制代码
  1. @BindingAdapter("android:text")
  2. public static void setText(TextView view, User s)
  3. {
  4. view.setText(s.toString());
  5. }
  6. 复制代码

###结语

至此,DataBinding的主要特性已经学习完毕了,绝大部分内容只是对官方文档的简单翻译。 在这里我想说一句,学习新的知识最正确的方式是直接看官方文档,因为那是第一手的资料,如果英文实在是太差,才考虑去看别人写的相关文章。

测试代码的GitHub地址

转载于:https://juejin.im/post/5a40a5dcf265da43310e0c4a

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/320711?site
推荐阅读
相关标签
  

闽ICP备14008679号