美文网首页工作生活
Android DataBinding

Android DataBinding

作者: SimpleFunc | 来源:发表于2019-06-30 14:51 被阅读0次

    2016-08-12 13:00

    Android DataBinding 是google在android上对MVVM设计模式的一个实现框架,目前还在测试阶段,稳定性,性能和兼容性还存在一些问题,所以目前在稳定版本的App上应用的还很少。

    Android DataBinding主要实现了View和ViewModel的双向绑定,包括用户的响应。并且实现了自动更新。

    Android DataBinding是一个support library,可以兼容android2.1(API Level 7)及以上的版本,开发需要使用Gradle1.5.0-alphal或更高版本的插件

    开发环境

    在你的module中的build.gradle文件中添加dataBinding配置:

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

    添加完成之后,使用gradle同步项目,会自动下载支持类库。

    布局

    <?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>  
    

    Android DataBinding的布局文件必须与<layout>标签为起点。

    <data>标签内就是这个布局要绑定的数据。
    variable表示一个可能会在这个布局中作用的属性。
    一个<data>标签内可以包含多个variable标签,即一个布局可以绑定多个数据对象类
    variable的name就是这个布局中引用该数据对象的名称,type为该数据对象对应的类,通常为包名+类名

    布局属性的设置使用“@{ }”语法

    数据对象

    POJO:

    public class User {  
       public final String firstName;  
       public final String lastName;  
       public User(String firstName, String lastName) {  
           this.firstName = firstName;  
           this.lastName = lastName;  
       }  
    }  
    

    常在应用程序的数据读取一次,不会改变.还可以改为javabean对象:

    public class User {  
       private final String firstName;  
       private final 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;  
       }  
    }  
    

    从数据绑定的角度来看,这两个类是没什么区别的。用于设置TextView的android:文本的表达式@{user.firstName}将访问第一个类中的firstName字段和后一个类的getFirstName()方法
    另外,如果对象中firstName()方法存在,它还将访问firstName()方法。

    数据绑定

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

    默认情况下,绑定类的名称是基于布局文件的名称生成的,它是将布局文件名开头大写并加上“Binding”而成。这个类拥有所有从属性(例如用户变量)到布局的绑定关系并知道如何赋值绑定表达式。最简单的方法创建绑定的方法就是通过反射。

    事件处理

    事件出来主要是处理用户交互的事件响应,如点击,长按,控件的拖动等,有两种方式来实现事件处理:

    Method References: 和android的现在的方式类似,在layout文件中绑定事件对应的方法。
    如:

    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>
    

    Listener Bindings: 和Method References很相似,不过可以使用lambda表达式传递任意的数据。不过只有Gradle 2.0及以上才支持。

    如:

    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>
    

    如果点击事件执行的方法中要使用当前的View,也可以通过参数传过去:

     android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"
    

    在lambda表达式中可以传递多个参数。如果表达是中参数无NULL,Data Binding会返回默认个的值。表达式中还可以使用void的关键字

    android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
    

    避免复杂的listener表达式,会使代码难以阅读和维护,表达式应该只传递有效的参数。

    布局细节

    1.import

    import和Java的import的功能类似,在layout文件中使用该标签导入对应的类就可以使用该类的属性和方法

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

    在布局中就可以使用view的属性和方法

    <TextView
       android:text="@{user.lastName}"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
    
    <data>  
        <import type="com.example.User"/>  
        <import type="java.util.List"/>  
        <variable name="user" type="User"/>  
        <variable name="userList" type="List<User>"/>  
    </data>  
    

    导入类型时也可以使用类型的静态方法和属性:

    <data>  
        <import type="com.example.MyStringUtils"/>  
        <variable name="user" type="com.example.User"/>  
    </data>  
    …  
    <TextView  
       android:text="@{MyStringUtils.capitalize(user.lastName)}"  
       android:layout_width="wrap_content"  
       android:layout_height="wrap_content"/>  
    

    变量:

    在<data>标签中可以声明多个不同类型的变量

    <data>
        <import type="android.graphics.drawable.Drawable"/>
        <variable name="user"  type="com.example.User"/>
        <variable name="image" type="Drawable"/>
        <variable name="note"  type="String"/>
    </data>
    

    在编译时会检查引入的变量,如果一个变量继承了Observable或Observable集合对象,那会通过反射得到,如没有实现Observable接口,则数据的变化不会被观察。

    2.自定义绑定类型名称

    绑定类可以被重命名或放在不同的包中通过class标签

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

    生成绑定类ContactItem将会存在module包下的databinding包中。如果想让生成的类放在module中的另一个包下,可以加入前缀:

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

    也可以使用包名的全称:

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

    3.includes

    和layout的include标签一致,可以在一个布局中引入另一个布局,并绑定数据:

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

    merge标签不支持include数据绑定

    表达式语法

    表达式语法跟java语法很像,下面的一样的语法:

    · 数学计算 + - / * %

    · 字符串连接 +

    · 逻辑运算符&& ||

    · 位运算& | ^

    · 一元运算 + - ! ~

    · 位移>> >>> <<

    · 比较== > < >= <=

    · instanceof

    · Grouping ()

    · 文字 - character, String, numeric, null

    · Cast

    · 方法调用

    · 字段访问

    · 数组访问 [ ]

    · 三元运算符?

    如:

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

    不支持的:

    · this

    · super

    · new

    · Explicit generic invocation

    NULL 判断:

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

    与下面的一致:

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

    4.集合:

    array:

    <data>
        <import type="android.util.SparseArray"/>
        <import type="java.util.Map"/>
        <import type="java.util.List"/>
        <variable name="list" type="List&lt;String&gt;"/>
        <variable name="sparse" type="SparseArray&lt;String&gt;"/>
        <variable name="map" type="Map&lt;String, String&gt;"/>
        <variable name="index" type="int"/>
        <variable name="key" type="String"/>
    </data>
    …
    android:text="@{list[index]}"
    …
    android:text="@{sparse[index]}"
    …
    android:text="@{map[key]}"
    

    Map:

    由于Map的key为字符串,在XML中引用map的子项也是双引号,所以可以使用单引号和`:

    android:text='@{map["firstName"]}'  
    //or
    android:text="@{map[`firstName`}"  
    android:text="@{map["firstName"]}"  
    

    5.Resources:

    与布局一致,可以使用表达式:

    android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
    

    字符串格式化:

    android:text="@{@string/nameFormat(firstName, lastName)}"  
    

    复数:

    android:text="@{@plurals/banana(bananaCount)}"
    

    如果复数有多个参数,那所有参数都应该传入:

    Have an orange
    Have %d oranges
    android:text="@{@plurals/orange(orangeCount, orangeCount)}"
    

    6.数据对象

    要使数据对象可监控,必须实现Observable接口,有三种不同的可观察的数据机制:Observable objects, observable fields, and observable collections.

    · Observable objects:

    一个可以监控对象的接口实现,可以把监听器和对象绑定,可以监听所有属性的变化.要实现一个可监控数据对象,该对象(POJO)需继承BaseObservable对象:

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

    @Bindable是一个注解,来标注该属性是可以监控的,在编译时会生成BR类,放在module的包下.
    数据发生改变时,应该在setter方法用调用notifyPropertyChanged方法来通知监听器。

    · ObservableFields:

    如果一个类中只有几个变量需要监控,那可以不集成BaseObservable类,使用ObservableFields来监控。
    一些基本类型的字段监控类: ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, 和ObservableParcelable.

    ObservableFields是一个独立的可监控对象,只能监控一个属性。原始的版本使用时为了避免装箱和拆箱,使用public final 修饰

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

    赋值和获取:

    user.firstName.set("Google");
    int age = user.age.get();
    
    

    · Observable Collections:

    ObservableArrayMap:

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

    layout:

    <data>
        <import type="android.databinding.ObservableMap"/>
        <variable name="user" type="ObservableMap&lt;String, Object&gt;"/>
    </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&lt;Object&gt;"/>
    </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"/>
    

    7.Views With IDs

    当一个Layout中的view声明了ID时,会自动生成对应的public final变量。数据绑定通过View的结构提取出有ID的view。这种机制比调用findViewById更高效

    <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}"
       android:id="@+id/firstName"/>
           <TextView android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:text="@{user.lastName}"
      android:id="@+id/lastName"/>
       </LinearLayout>
    </layout>
    

    数据绑定在上面的布局下将会自动生成下面的变量:

    public final TextView firstName;

    public final TextView lastName;

    Variables:

    每个layout中声明的Variables对象,都应该声明对应的操作方法:

    <data>
        <import type="android.graphics.drawable.Drawable"/>
        <variable name="user"  type="com.example.User"/>
        <variable name="image" type="Drawable"/>
        <variable name="note"  type="String"/>
    </data>
    
    
    public abstract com.example.User getUser();
    public abstract void setUser(com.example.User user);
    public abstract Drawable getImage();
    public abstract void setImage(Drawable image);
    public abstract String getNote();
    public abstract void setNote(String note);
    

    8.ViewStubs

    ViewStub和常规的View不太一样,它开始的时候是不可见的,可以延迟到运行时填充布局资源。

    由于ViewStub会在视图层消失,为了正常的连接,对应的绑定对象也要随之消失。由于视图层是final类型的,ViewStubProxy对象替代ViewStub 后,开发者可以访问 ViewStub,并且当 ViewStub 在视图层中被加载时,开发者也可以访问加载的视图。

    当ViewStub填充其他布局时,新的布局中要建立绑定关系。因此,ViewStubProxy 对象要监听ViewStub的OnInflateListener并建立绑定。开发者可以在ViewStubProxy 对象上建立一个OnInflateListener,绑定建立后,便可调用OnInflateListener。

    高级绑定

    1,动态变量

    布局有时候要绑定的数据对象是不确定的,例如:RecyclerView.Adapter,必须在onBindViewHolder中指定绑定值,才可以识别出对应的绑定类。

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

    上面这个例子中,RecyclerView绑定的所有layout有一个item变量,BindingHolder有一个getBinding方法来获取ViewDataBinding对象

    · 立即绑定

    当一个变量或Observable对象发生变化时,绑定应该在下一帧之前按序进行改变,但是有时候需要立即执行绑定,可以调用 executePendingBindings()方法强制执行绑定

    · 后台线程:
    可以改变你的数据对象在后台线程,除了集合。数据绑定将会本地化保存每一个变量/字段,防止并发错误

    Attribute Setters:

    当一个绑定的值发生变化时,自动生成的数据绑定对象必须调用对应的setter方法在界面上赋值,数据绑定框架有几种方法去自定义绑定的方法

    1.自动Setters

    数据绑定会自动寻找参数对应的setter方法,跟命名空间无关,只跟属性的名称相关。
    例如:textview 在不居中通过android:text赋值之后,会自动寻找setText(String)方法,如果表达式返回的是Int类型,熟悉绑定会自动寻找setText(Int)方法。所以要确保表达式返回的类型正确。若需要可以转换。即使没有给定的属性,数据绑定也会执行。可以通过数据绑定调用setter方法创建属性。例如,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}"/>  
    

    2.重命名Setters

    一些属性的setter方法与名称不一致,可以通过BindingMethods注解关联名称和方法。
    例如:可以将android:tint 属性与setImageTintList(ColorStateList)方法相关联

    @BindingMethods({  
           @BindingMethod(type = "android.widget.ImageView",  
                          attribute = "android:tint",  
                          method = "setImageTintList"),  
    })  
    

    开发者不需要重命名setter方法,数据绑定框架已经实现。

    3.自定义setter

    有一些参数需要自定义绑定逻辑,如:android:paddingLeft属性没有setter方法,但是有一个setPadding(left,top,right,bottom)方法,@BindingAdapter可以自定义怎么绑定该属性

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

    BindingAdapter是很有用的去自定义别的属性,例如,自定的加载器可以利用空线程加载图片。
    当适配器冲突的时候,开发者定义的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);
    }
    
    <ImageView app:imageUrl="@{venue.imageUrl}"
    app:error="@{@drawable/venueError}"/>
    

    绑定适配器的方法可能需要持有旧的值,采用旧值和新值的方法应该首先具有属性的所有旧值,然后是新值,

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

    当一个监听器有多个方法的时候,必须拆分进多个监听器。例如 View.OnAttachStateChangeListener有两个方法:

    @TargetApi(VERSION_CODES.HONEYCOMB_MR1)
    public interface OnViewDetachedFromWindow {
        void onViewDetachedFromWindow(View v);
    }
    
    @TargetApi(VERSION_CODES.HONEYCOMB_MR1)
    public interface OnViewAttachedToWindow {
        void onViewAttachedToWindow(View v);
    }
    

    因为改变一个监听器方法的时候会影响到另一个,所以要提供三个绑定适配器,添加一个适配器来同时处理两个事件:

    @BindingAdapter("android:onViewAttachedToWindow")
    public static void setListener(View view, OnViewAttachedToWindow attached) {
        setListener(view, null, attached);
    }
    
    @BindingAdapter("android:onViewDetachedFromWindow")
    public static void setListener(View view, OnViewDetachedFromWindow detached) {
        setListener(view, detached, null);
    }
    
    @BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"})
    public static void setListener(View view, final OnViewDetachedFromWindow detach,
            final OnViewAttachedToWindow attach) {
        if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
            final OnAttachStateChangeListener newListener;
            if (detach == null && attach == null) {
                newListener = null;
            } else {
                newListener = new OnAttachStateChangeListener() {
                    @Override
                    public void onViewAttachedToWindow(View v) {
                        if (attach != null) {
                            attach.onViewAttachedToWindow(v);
                        }
                    }
    
                    @Override
                    public void onViewDetachedFromWindow(View v) {
                        if (detach != null) {
                            detach.onViewDetachedFromWindow(v);
                        }
                    }
                };
            }
            final OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view,
                    newListener, R.id.onAttachStateChangeListener);
            if (oldListener != null) {
                view.removeOnAttachStateChangeListener(oldListener);
            }
            if (newListener != null) {
                view.addOnAttachStateChangeListener(newListener);
            }
        }
    }
    

    android.databinding.adapters.ListenerUtil类用来帮助跟踪之前的监听器,保证之前的监听器被移除

    3.转换器

    当一个对象从绑定表达式中被返回,对象的setter方法将会从自动,重命名和自定义setter中选择,对象会被转化为setter指定的类型
    例如:

    <TextView
       android:text='@{userMap["lastName"]}'
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>
    

    userMap 是一个ObservableMaps,userMap返回一个对象,对象将会被转化为字段对应的类型在setter方法中setText(CharSequence)。当参数类型不明确的时候,开发者应该在表达式中转化

    自定义转化器

    有时候转换器应该自动转化两个特殊类型的对象,如:

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

    background参数类型是Drawable,而color是Int,无论预期的是Drawable还是int类型,int类型都应该被转换为colorDrawable.

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

    Android studio对DataBinding的支持

    Android studio支持DataBinding的多种代码边距功能,例如:

    语法高亮显示,在编辑器中显示语法错误,xml代码自动补全,引用,代码源码导航。

    布局预览也支持在表达式中设置默认值,例如:

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

    在预览页面中该TextView的文本将会显示为PLACEHOLDER。如果在设计时需要显示不同的值,也可以使用tools参数代替表达式中的default值。

    相关文章

      网友评论

        本文标题:Android DataBinding

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