美文网首页Android开发
Android Data-Binding简记

Android Data-Binding简记

作者: Joker_Lee | 来源:发表于2016-08-07 11:40 被阅读0次

    从我的CSDN博客http://blog.csdn.net/dahaohan 迁移的第一篇博文。

    What's Data-Binding?

    看过我之前转发的博文Android App的设计架构:MVC,MVP,MVVM经验谈
    可以了解到移动端App开发架构从传统MVC-->MVP-->MVVM的一些进展和演化,而目前发展成的MVVM架构则需要使用Data-Binding机制来完成View和ViewModel之间的通信。
    2015年google I/0开发者大会发布的Data-binding库,使得开发者可以更加简洁优雅的编写代码实现复杂的业务逻辑,而不必去关注数据变更后UI View的更新问题。View的变化会直接影响ViewModel,ViewModel的变化或者内容也会直接体现在View上,这就是Data Binding Library默默帮您完成的工作。
    Android官方Data Binding介绍:https://developer.android.com/topic/libraries/data-binding/index.html

    Data Binding Requirements

    The Data Binding Library offers both flexibility and broad compatibility — it's a support library, so you can use it with all Android platform versions back to Android 2.1 (API level 7+).
    To use data binding, Android Plugin for Gradle 1.5.0-alpha1 or higher is required.
    Also, make sure you are using a compatible version of Android Studio. Android Studio 1.3 and later provides support for data binding.

    Data binding 是一个类似support-v4/v7的支持库,API 7+都可以使用;
    Android Studio1.3+
    Android Gradle插件 1.5.0 +

    How To Use Data Binding?

    要使用Data binding功能需要在你app module 的build.gradle中开启。

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

    <h3>Data binding Layout

    使用Data binding核心是将viewModel数据嵌入到布局layout xml文件,故而layout xml文件与之前的纯view布局文件的构成略有不同。

    //以下为官方给出的最简单的一个示例:
    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
    
        <data>
            <variable
                name="user"
                type="test.example.com.databindingtest.User">
            </variable>
        </data>
    
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <TextView
                android:id="@+id/firstname_text"
                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>
    
    public class User {
       public final String firstName;
       public final String lastName;
       public User(String firstName, String lastName) {
           this.firstName = firstName;
           this.lastName = lastName;
       }
    }
    
    

    Data binding的布局文件的根节点为layout标签,由一个数据data标签以及一个根视图标签构成。
    data标签声明了该View使用的数据模型viewModel为变量名为user的User类对象,然后在Root View标签下通过
    @{“变量名" + "." + ”属性名/函数名“}来给指定的view设置值。

    PS:

    //对于User类的另一种写法
    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;
       }
    }
    

    关于Data binding框架识别语句 @{user.firstName} 的一个机制是对于第一种写法访问User类的 firstName属性,而第二种写法则是访问User类的getFirstName()方法,或者也有可能访问firstName()方法如果存在的话。

    <h3>Binding Data

    编写完成data binding layout之后,类似IDE自动编译资源文件产生资源ID R文件,默认将以layout xml文件的名字为基础生成一个继承自ViewDataBinding的类,此例xml文件为activity_main.xml故而生成ActivityMainBinding.java
    生成的ViewDataBinding类默认命名规则为,布局xml文件名去掉"_"以驼峰式写法+“Binding”,当然也支持自定义命名,后续提及

    对于Android Studio IDE可以在
    app/build/intermediates/classes/debug/+"对应包目录"+databinding文件夹下查看该生成的ViewDataBinding类


    这里写图片描述

    PS:注意这里生成的ActivityMainBinding内的以下几个属性和函数

    public class ActivityMainBinding extends android.databinding.ViewDataBinding  {
        ..........
        .....
        // views
        //在layout xml内定义了id的 android:id="@+id/firstname_text"
        //Viewdatabinding将会以驼峰命名生成对应:
        //public final android.widget.TextView firstnameText;
        //通过获得的binding类以及view的id名称可以直接获取该view的引用,而不需要再findViewById
        public final android.widget.TextView firstnameText;
    
        //未设置id的view则只会定义为private,外部将不可访问
        private final android.widget.LinearLayout mboundView0;
        private final android.widget.TextView mboundView2;
    
        //layout内声明的变量,对应成员变量以及默认以驼峰命名生成set/get函数setUser
        //用于给dataBinding设置数据viewModel
        // variables
        private test.example.com.databindingtest.User mUser;
        ........
        public void setUser(test.example.com.databindingtest.User user) {
            this.mUser = user;
            synchronized(this) {
                mDirtyFlags |= 0x1L;
            }
            notifyPropertyChanged(BR.user);
            super.requestRebind();
        }
        public test.example.com.databindingtest.User getUser() {
            return mUser;
        }
        ........
        ..................
      }
    

    略微查看默认生成的ActivityMainBinding类,可以看出binding类就是管理并维护View与View Model的关系的核心。包括给将user数据对应值赋值给对应的view视图;当user内值变化时更新视图;视图交互事件调用viewmodel的业务逻辑等。

    ViewDataBinding相当于一个联系对应layout中View与对应ViewModel(data标签下的数据)的框架,我们使用时需要将对应的View和ViewModel实例对象传递给该ViewDataBinding框架,Data Binding库已经提供了多种方法来给实现:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
       //获取ViewDataBinding实例对象的同时已经通过传递对应的View对象或者layout文件参数,
       //将对应的View实例对象传递给该ViewDataBinding框架
       
       //Activity最常用的取代之前的setContentView方法
       MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
       //可直接通过id名称访问对应View变量
       dataBinding.firstnameText.setText("hello");
       
       //使用默认生成的ActivityMainBinding直接bind对应View
       dataBinding = ActivityMainBinding.bind(viewRoot);
       //或者如下
       dataBinding = ActivityMainBinding.inflate(getLayoutInflater());
       
       ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
        parent, attachToParent);
        
       //给DataBinding框架设置viewModel数据源User
       User user = new User("Test", "User");
       //set方法根据layout中声明的数据类型自动生成
       binding.setUser(user);
    }
    

    上述步骤完成运行即可发现,user各属性数据自动绑定到了各自的View上,但是这只体现了dataBinding单方面从ViewModel到View的数据绑定;如何实现user数据更新然后DataBinding自动更新UiView? View的交互事件如何绑定到ViewModel的业务逻辑?

    Data Binding View Event Handling##

    类似data binding提供的View的数据绑定,在layout的view标签内同样允许使用表达式直接引用相应的函数处理分发的事件。使用函数引用的View 属性由对应的事件listener的函数方法决定:

        //View一般有View.OnLongClickListener/ View.OnClickListener
        //对应的两个方法为onLongClick/onClick故而data binding在view标签下有如下属性:
        android:onClick="@{user.onLastNameClick}"
        android:onLongClick="@{user.onLastNameLongClick}"
    

    若在User类加入以下函数,则在View内完整引用监听click事件写法如下:

    public class User {
        ....
        ........
        public void onLastNameClick(View v){
            Log.d("Test"," onLastNameClick ");
        }
        public boolean onLastNameLongClick(View v){
            Log.d("Test"," onLastNameLongClick ");
            return false;
        }
        ....
        .......
    }
    
    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
        <data>
            <variable
                name="user"
                type="test.example.com.databindingtest.User">
            </variable>
        </data>
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <TextView
                android:id="@+id/firstname_text"
                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}"
                android:onClick="@{user.onLastNameClick}"
                android:onLongClick="@{user.onLastNameClick}"/>
        </LinearLayout>
    </layout>
    

    进行编译后运行即可发现点击交互事件可以顺利触发和处理。

    Android官方对于View标签引用的事件处理函数的要求说明:

    In your expressions, you can reference methods that conform to the signature of the listener method. When an expression evaluates to a method reference, Data Binding wraps the method reference and owner object in a listener, and sets that listener on the target view. If the expression evaluates to null, Data Binding does not create a listener and sets a null listener instead.
    

    官方说明View处理事件引用的方法的签名要与对应的clickListener的方法签名相符,而方法的签名侧重的是方法名和方法参数的顺序、类型、个数;这里测试之后其实需要的是与对应的clickListener的方法的参数以及返回值一致。

    编译期间将对View#onClick attribute的表达式引用的方法进行合法性检查,若是方法参数/返回值对应不上,则会出现编译错误:

    Error:(26, 36) Listener class android.view.View.OnLongClickListener with method onLongClick did not match signature of any method user.onLastNameLongClick 
    

    若编译正确,实质其实还是将该方法包装进一个对应的listener然后给view设置对应监听接口。

    Data Binding listening data/properties changes

    Data binding真正的好处提现在data(user)数据变化时,能够自动更新对应的UI显示,如何使用这个核心功能呢?
    <h3>Observable 对象

    private static class User extends BaseObservable {
       private String firstName;
       private String lastName;
        
       //Bindable注解告诉data binding框架需要侦听该值的变化
       //编译期间生成的类似R文件的一个BR文件将会有该属性的一个public资源Id
       @Bindable
       public String getFirstName() {
           return this.firstName;
       }
       @Bindable
       public String getLastName() {
           return this.lastName;
       }
       public void setFirstName(String firstName) {
           this.firstName = firstName;
           //通过对应属性的资源ID告诉data binding框架该属性发生变化需要更新ui什么的
           notifyPropertyChanged(BR.firstName);
       }
       public void setLastName(String lastName) {
           this.lastName = lastName;
           notifyPropertyChanged(BR.lastName);
       }
    }
    

    限定了修改属性数据的方法在对应的set方法内,所以在set方法更新属性后通知data binding框架属性的更新即可。

    <h3>ObservableFields

    其实与上述的BaseObservable对象是类似的原理,只是将整个类的范围缩小到个别需要侦听的属性上,提供了ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable.

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

    事实上每一个ObservableFields都继承自BaseObservable包含了单独一个属性值,内部默认封装了set/get方法,将上述BaseObservable的方法封装好了,也是在set方法之后通知data-binding框架做出一些更新操作。

    <h3>Observable Collections

    对于一些需要使用到list/map等集合数据类型来说有: ObservableArrayMap,ObservableArrayList等

    ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
    user.put("firstName", "Google");
    user.put("lastName", "Inc.");
    user.put("age", 17);
    
    <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"/>
    

    PS: Android Studio IDE目前并不能很完美的支持到data标签的一些变量的import或者是类型声明;但是并不会影响编译运行。


    这里写图片描述
    ObservableArrayList<Object> user = new ObservableArrayList<>();
    user.add("Google");
    user.add("Inc.");
    user.add(17);
    
    <data>
        <import type="android.databinding.ObservableList"/>
        <variable name="user" type="ObservableList<Object>"/>
    </data>
    …
    <TextView
       android:text="@{user[0]}"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>
    <TextView
       android:text="@{String.valueOf(1 + (Integer)user[2])}"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>
    
    

    Some Details

    关于data标签支持import类似java的import,以及其对于一些类似map/list/array等集合类型的数据的语法简要介绍:

    //下面的 < 为符号'<'字符实体
    <data>
        <import type="android.util.SparseArray"/>
        <import type="java.util.Map"/>
        <import type="java.util.List"/>
        <variable name="list" type="List<String>"/>
        <variable name="sparse" type="SparseArray<String>"/>
        <variable name="map" type="Map<String, String>"/>
        <variable name="index" type="int"/>
        <variable name="key" type="String"/>
    </data>
    
    …
    android:text="@{list[index]}"
    …
    android:text="@{sparse[index]}"
    …
    android:text="@{map[key]}"
    
    //集合类型数据都是类似数组使用的'[]'访问特定的数据,对于数组[]内是对应的下标,对于map则是对应的key值
    
    

    PS: Map的key为一个String时,可能遇到引号内要使用引号包括字符串的冲突,这时使用:

    //单引号在外包括,内部使用双引号标示字符串
    android:text='@{map["firstName"]}'
    //或者外部使用双引号,内部用back quote反引号标示字符串(反引号即'~'按键)
    android:text="@{map[`firstName`}"
    
    //或者使用"即双引号的java字符实体来替代双引号
    android:text="@{map["firstName"]}"
    
    

    About More Details

    关于data binding的细节知识推荐阅读:
    官方介绍文档:https://developer.android.com/topic/libraries/data-binding/index.html
    比较全面的官方文档的翻译档:http://www.jianshu.com/p/b1df61a4df77
    结合实例的介绍:https://github.com/LyndonChin/MasteringAndroidDataBinding

    相关文章

      网友评论

        本文标题:Android Data-Binding简记

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