DataBinding使用全面详解

作者: 我是FadeMan | 来源:发表于2017-05-05 10:39 被阅读23702次

    一、DataBinding使用

    <h2 id="1-1">1.使用环境</h2>

    DataBinding是一个support library,所以它可以支持所有的android sdk,最低可以到android2.1(API7)。
    使用DataBinding需要Android Gradle插件的支持,版本至少在1.5以上,需要的Android studio的版本在1.3以上。

    在Android Studio上使用,需要在module级别的build.gradle上添加对DataBinding的支持:

    android {
        ....
        dataBinding {
            enabled = true
        }
    }
    

    如果是在library中使用,那么使用使用该library的module也需要在build.gradle添加。
    <h2 id="1-2">2.xml布局文件数据绑定</h2>
    DataBinding的layout files和普通的非DataBinding布局文件是有一些区别的,下面是一个基础的使用了DataBinding的布局文件:

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
     <data>
       <variable name="user" type="com.example.User"/>
     </data>
    <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"/>
     </LinearLayout>
    </layout>
    

    变量user作为被绑定的数据,在layout文件中是这样描述和使用的:

    <variable name="user" type="com.example.User"/>
    

    layout中view的属性值通过"@{}"这样的语法表达方式和数据user实现绑定,本例中将TextView的text值设置为user对象的fisrtName了:

    <TextView android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@{user.firstName}"/>
    

    3.定义数据绑定的Data对象

    Data对象官方文档中POJO类和Java Bean都可以,这里我建议使用如下Java Bean:

        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;
         }
        }
    

    Note:但是定义一个如上的数据,并不能满足刷新UI的要求,我们需要的Data 还得是一个Observable Data。

    DataBinding中有三种不同的数据,object、field、collection。

    Observable Objects

    Observable是提供添加移除监听的一个java接口,DataBinding基于此接口提供了一个基础类BaseObserable,我们可以这样使用它,通过Bindale注解绑定一个getter,当data属性发生改变在setter中发出通知,这样就实现了响应

    private static class User extends BaseObservable {
       private String firstName;
       private String lastName;
       @Bindable
       public String getFirstName() {
           return this.firstName;
       }
       @Bindable
       public String getLastName() {
           return this.lastName;
       }
       public void setFirstName(String firstName) {
           this.firstName = firstName;
           notifyPropertyChanged(BR.firstName);
       }
       public void setLastName(String lastName) {
           this.lastName = lastName;
           notifyPropertyChanged(BR.lastName);
       }
    }
    

    ObseravbleField

    google为我们提供了一些Obserable类: ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable

    public static class User {
       public final ObservableField<String> firstName =
           new ObservableField<>();
       public final ObservableField<String> lastName =
           new ObservableField<>();
       public final ObservableInt age = new ObservableInt();
    }
    

    ObseravbleCollection

    ** ObservableArrayMap **

    ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
    user.put("firstName", "Google");
    user.put("lastName", "Inc.");
    user.put("age", 17);
    

    在xml中使用:

    <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);
    

    xml使用:

    <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"/>
    

    4.绑定数据

    Android studio会根据layout文件自动生成一个默认的Binding类,类名是根据layout文件名生成的,并有"Binding"后缀结束。例如:activity_main.xml生成的Binding类为ActivityMainBinding,可用如下方式使用Binding类:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
       User user = new User("Test", "User");
       binding.setUser(user);
    }
    

    启动程序,将会看到user的数据已经在ui中显示了,或者我们也可以这样实现:

    MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
    

    在ListView或者RecyclerView的adpater中item里使用DataBinding

    ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
    //or
    ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
    

    5.事件处理

    DataBinding允许我们在xml中view的一些事件属性(如onClick等)中填写DataBinding表达式,也可以通过绑定listener的方式去实现。归纳起来就是:
    方法引用和监听绑定,下面介绍着两种方式:

    方法引用

    public class MyHandlers {
        public void onClickFriend(View view) { ... }
    }
    

    布局文件

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
       <data>
           <variable name="handlers" type="com.example.Handlers"/>
           <variable name="user" type="com.example.User"/>
       </data>
       <LinearLayout
           android:orientation="vertical"
           android:layout_width="match_parent"
           android:layout_height="match_parent">
           <TextView android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:text="@{user.firstName}"
               android:onClick="@{handlers::onClickFriend}"/>
       </LinearLayout>
    </layout>
    

    监听绑定

    public class Presenter {
        public void onSaveClick(Task task){}
    }
    

    布局文件

      <?xml version="1.0" encoding="utf-8"?>
      <layout xmlns:android="http://schemas.android.com/apk/res/android">
          <data>
              <variable name="task" type="com.android.example.Task" />
              <variable name="presenter" type="com.android.example.Presenter" />
          </data>
          <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
              <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
              android:onClick="@{() -> presenter.onSaveClick(task)}" />
          </LinearLayout>
      </layout>
    

    6.imports

    导入类:

    <data>
        <import type="android.view.View"/>
    </data>
    

    现在可以在xml中使用View类的静态资源:

    <TextView
       android:text="@{user.lastName}"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
    

    也可以为导入类,重新定义一个别名:

    <import type="android.view.View"/>
    <import type="com.example.real.estate.View"
            alias="Vista"/>
    

    下面是个集合的例子:

    <data>
        <import type="com.example.User"/>
        <import type="java.util.List"/>
        <variable name="user" type="User"/>
        <variable name="userList" type="List<User>"/>
    </data>
    

    7.自定义Binding类类名

    生成当前目录下的ContactItemBinding

    <data class="ContactItem">
        ...
    </data>
    

    也可以通过下面的方式指定生成类存放的目录

    <data class="com.example.ContactItem">
    ...
    </data>
    

    8.表达式

    支持的运算符:
    <ul>
    <li>数学运算符: + - / * %</li>
    <li>字符串拼接: +</li>
    <li>逻辑运算符: && ||</li>
    <li>二进制: & | ^</li>
    <li>一元运算符: +</li>
    <li>位运算符: >> >>> <<</li>
    <li>比较: == > < >= <=</li>
    <li>instanceof</li>
    <li>()</li>
    <li>数据类型: character, String, numeric, null</li>
    <li>类型转换(ClassCast)</li>
    <li>方法回调(Method calls)</li>
    <li>数据属性</li>
    <li>数组:[]</li>
    <li>三元操作符:?</li>
    </ul>

    列如:

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

    一些在java中常用而DataBinding xml中不支持的:

    <ul>
    <li>this
    <li>super
    <li>new
    <li>泛型
    </ul>
    一个比较有意思的“??”操作符:

    android:text="@{user.displayName ?? user.lastName}"
    

    它等于:

    android:text="@{user.displayName != null ? user.displayName : user.lastName}"
    

    9.Binding类的其他生成方式

    前面,我们提到了一个获取Binding类的方法,我们还可以这样

    MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
    MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
    

    或者:

    MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
    

    二、DataBinding高级使用

    1.动态变量

    有时候我们可能不知道Binding类的名称,比如RecyclerView.Adapter中item布局可能有很多,并不会对应特定的Binding类,但是仍然需要通过** onBindViewHolder(VH, int)**去绑定数据,下面的列子是,所有的子布局都有一个"item"变量,通过ViewDataBinding基类去完成绑定:

    public void onBindViewHolder(BindingHolder holder, int position) {
       final T item = mItems.get(position);
       holder.getBinding().setVariable(BR.item, item);
       holder.getBinding().executePendingBindings();
    }
    

    Immediate Binding

    当一个变量被绑定或者绑定的对象发生变化是,DataBinding会让这些改变排队去在下一帧刷险之前改变,有些时候binding效果必须立刻执行,这时候可以使用executePendingBindings()

    Background Thread
    只要绑定数据不是一个collection,我们可以在非ui主线程去改变数据,不会有任何线程切换问题,DataBinding会自动处理。

    2.Attribute Setters

    当一个被绑定的数据的值发生改变时,Binding类会自动寻找该view上的绑定表达式上的方法去改变view,通过google数据绑定框架我们可以去自定义这些方法。

    对于一个xml的attribute,data binding会去寻找setAttribute方法,xml属性的命名空间是没有关系的。比如TextView上的一个属性android:text,会去寻找setText(String)。如果表达式返回的是int则会去寻找setText(int),所以必须确保xml中表达式返回正确的数据类型,必要时需要数据转换。我们可以比较容易地为任何属性创造出setter去使用dataBinding。比如support包下的DrawerLayout没有任何属性,但是确有很多setter,下面利用这些已有的setter中的一个:

    <android.support.v4.widget.DrawerLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:scrimColor="@{@color/scrim}"
    app:drawerListener="@{fragment.drawerListener}"/>
    

    自定义setters

    一些xml属性需要自己去定义并实现逻辑,比如android:paddingLeft。但是setPadding(left,top,right,bottom)是存在的,那么我们可以同BindingAdapter注解去自定义个自己的setter:

    @BindingAdapter("android:paddingLeft")
    public static void setPaddingLeft(View view, int padding) {
       view.setPadding(padding,
                       view.getPaddingTop(),
                       view.getPaddingRight(),
                       view.getPaddingBottom());
    }
    

    Note:开发者自定义的BindingAdapter和android自带的发生冲突时,data bingding会优先采用开发者自定义的。

    多参数的BindingAdapter

    @BindingAdapter({"bind:imageUrl", "bind:error"})
    public static void loadImage(ImageView view, String url, Drawable error) {
       Picasso.with(view.getContext()).load(url).error(error).into(view);
    }
    

    BindingAdpater方法可以对属性的旧值和新值进行处理

    @BindingAdapter("android:paddingLeft")
    public static void setPaddingLeft(View view, int oldPadding, int newPadding) {
       if (oldPadding != newPadding) {
           view.setPadding(newPadding,
                           view.getPaddingTop(),
                           view.getPaddingRight(),
                           view.getPaddingBottom());
       }
    }
    

    事件处理的列子

    @BindingAdapter("android:onLayoutChange")
    public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,
           View.OnLayoutChangeListener newValue) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            if (oldValue != null) {
                view.removeOnLayoutChangeListener(oldValue);
            }
            if (newValue != null) {
                view.addOnLayoutChangeListener(newValue);
            }
        }
    }
    

    3.双向绑定

    在xml属性上使用语法"{@=}",
    使用该方法就是双向绑定了

         <data>
           <variable name="user" type="com.example.User"/>
         </data>
        <LinearLayout
           android:orientation="vertical"
           android:layout_width="match_parent"
           android:layout_height="match_parent">
           <TextView android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:text="@{=user.firstName}"/>
         </LinearLayout>
    

    Note:需要注意的是,使用该语法必须要要反向绑定的方法,android原生view都是自带的,所以使用原生控件无须担心,但是自定义view的话需要我们通过InverseBindingAdapter注解类实现,下面是个例子

         @InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
         public static String captureTextValue(TextView view, CharSequence originalValue) {
                     CharSequence newValue = view.getText();
                  CharSequence oldValue = value.get();
                     if (oldValue == null) {
                         value.set(newValue);
                     } else if (!contentEquals(newValue, oldValue)) {
                         value.set(newValue);
                     }
                 }
    

    4.Converters

    有时候我们想这样写xml属性

        <View
       android:background="@{isError ? @color/red : @color/white}"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>
    

    但是xml属性的setter是一个drawable,我们可以通过BindingConversion实现

    @BindingConversion
    public static ColorDrawable convertColorToDrawable(int color) {
       return new ColorDrawable(color);
    }
    

    由于这个注解至是发生在setter层面上,所以并不支持下面的混合写法

    <View
       android:background="@{isError ? @drawable/error : @color/white}"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>
    

    三、关于DataBinding的一些个人看法

    1.DataBinding使用心得

    使用xml进行view布局
    采用符合Java Bean规范的数据原型
    规范的自定义View
    禁止在BindingAdapter中的setter方法中改变数据或者做数据处理
    不建议用BindingConversion处理数据转换
    不建议在xml布局中处理view事件
    不建议在xml中使用复杂的表达式

    2.DataBinding使用的一些思考

    DataBinding的不足之处:

    1.DataBinding在xml提供了丰富的操作符,但是由于Android studio天生的xml语法检查的贫弱,xml布局中的表达式逻辑错误,不能准确定位,导致debug难度增加,事实上一些BindingAdapter的错误在build的时候也会被提示xml错误。

    2.对自定义view的要求比较高,需要自定义绑定方法,如BindingAdapter等。

    3.可能由于java 8移除apt,采用了新的API的缘故,所以即使Android Studio2.2已经开始支持java 8特性,但是需要开启jack编译链,DataBinding与之冲突,导致在代码中不能使用lambda表达式等java 8特性。值得欣慰的是,这一问题将在Android Studio2.4中得到解决。

    PS:数据绑定的应用软件开发的一种趋势,使用DataBinding的优点显而易见,但是使用的时候我们也需要小心。

    相关文章

      网友评论

      • 苾笙锁龠:您好,我想请问一下,就是为什么我不能binding.setUserInfo呀,在xml中我有引用<data>
        <variable
        name="userInfo"
        type="com.fangao.mydatatest.UserInfo"/></data>
        我是FadeMan:@苾笙锁龠 在具体的view中比如id为sample的一个view,通过android:data="@{userInfo}"绑定,然后就可以使用binding.sample.setUserInfo这个方法了。
      • baby_honour:双向不是@={}吗?
      • 3a9bb1e9e436:@InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
        public static String captureTextValue(TextView view, CharSequence originalValue) {
        CharSequence newValue = view.getText();
        CharSequence oldValue = value.get();
        if (oldValue == null) {
        value.set(newValue);
        } else if (!contentEquals(newValue, oldValue)) {
        value.set(newValue);
        }
        }
        这个的getText()怎么会是新的值?originalValue没有用到,value又是从哪里来的
      • 十年砍柴_b0f2:最好给一个源代码连接,再有就是你这里面代码错误太多了,还有缺少很多关键的地方
      • PeterHe888:为什么无法生成dataBinding类呢?
        3a9bb1e9e436:@PeterHe888 在gradle右下角的gradle console里面看,如果没有gradle console就调出来
        PeterHe888:@tvvbbb 好的,我看看
        3a9bb1e9e436:你的xml有错误,你build一下,就能看到错误信息
      • 橘子周二:博主,我在使用时遇到如下错误信息,卡了好几天。希望你可以办我分析
        ···xml
        <data class="IBindMapResultPlus">

        <variable
        name="item"
        type="com.saic.roewe.v1.model.MapResult" />

        <variable
        name="click"
        type="android.view.View.OnClickListener" />

        <import type="android.view.View" />

        <variable
        name="distance"
        type="String" />

        </data>
        //参数使用片段
        <TextView
        android:id="@+id/send"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="@{click}"
        android:paddingEnd="@dimen/dp_10"
        android:paddingLeft="@dimen/dp_10"
        android:paddingTop="@dimen/dimen_8dip"
        android:text="@string/send"
        android:textAllCaps="false"
        android:textColor="@color/white"
        android:textSize="@dimen/text_14sp" />
        ```
        报错信息
        Error:Execution failed for task ':app:compileDebugJavaWithJavac'.
        > java.lang.RuntimeException: Found data binding errors.
        ****/ data binding error ****msg:Cannot find the setter for attribute 'android:onClick' with parameter type android.view.View.OnClickListener on android.widget.TextView.
        file:G:\ASProject\thcc-android-zhusw97-1449\app\src\main\res\layout\item_my_favorite_new_copy.xml
        loc:114:39 - 114:43
        ****\ data binding error ****
        ****/ data binding error ****msg:Cannot find the setter for attribute 'android:onClick' with parameter type android.view.View.OnClickListener on android.widget.TextView.
        file:G:\ASProject\thcc-android-zhusw97-1449\app\src\main\res\layout\item_map_result_plus.xml
        loc:99:39 - 99:43
        ****\ data binding error ****
        橘子周二: @Fade了的Man 不可以定义变量为接口?
        我是FadeMan: @_RANGO databinding的变量不要为方法。只能是具体的type,也就是class.比如com.example.test,变量名为aaa,那么android:onclick="aaa::click",手机打字不方便,见谅,大概如此
        我是FadeMan: @_RANGO 之前的回复没有看清楚你的问题 重新回答。你的变量应用有问题,click应该链接到具体的类中
      • 青蛙要fly:<data>
        <import type="com.example.User"/>
        <import type="java.util.List"/>
        <variable name="user" type="User"/>
        <variable name="userList" type="List<User>"/>
        </data>


        这个也会报错。 type="List<User>"这部分
        我是FadeMan: @青蛙要fly 抱歉,最近没怎么看简书。用<&gt转义的方法表示<>试一下。简书自动将他们显示为符号了
      • 青蛙要fly:<data>
        <import type="android.databinding.ObservableList"/>
        <import type="com.example.my.app.Fields"/>
        <variable name="user" type="ObservableList<Object>"/>
        </data>

        这一段。照着你写的,我也像你这样引进了
        <import type="android.databinding.ObservableList"/>
        <variable name="user" type="ObservableList<Object>"/>
        但是 type="ObservableList<Object>"会提示报错,然后我把<Object>去掉是可以的,到底应该是什么
        森林的小木屋:与元素类型 "variable" 相关联的 "type" 属性值不能包含 '<' 字符。转义"<"也不能在这里。这种写法感觉有问题,应该是这样
        <ListView
        android:id="@+id/tasks_list"
        app:items="@{viewmodel.items}"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
        //在viewmodel中声明
        public final ObservableList<Object> items = new ObservableArrayList<>();
        3a9bb1e9e436:因为你没有把object都引进,你再import了object就好了

      本文标题:DataBinding使用全面详解

      本文链接:https://www.haomeiwen.com/subject/ssmjtxtx.html