Data Binding 的使用之一:简单的数据绑定

一、简介

    Data Binding是google发布的用以实现数据和UI绑定的框架,使用此框架可方便的实现MVVM开发模式。借用阮一峰老师对MVVM模式的概括:“MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。Angular 和 Ember 都采用这种模式。”

使用Data Binding的优点:
1. 避免了反复使用findViewById函数查找view,直接将view作为ViewDataBinding的成员变量。
2. 数据采用双向绑定,借助android.databinding.BaseObservable类,实现数据与视图两者的同步问题
3. 提供了多种表达式,可在xml布局文件中直接使用表达式完成数据处理
4. 为view提供多种callback,并提供lambda表达式功能,实现view的事件响应

二、使用步骤

0.开发环境

    IDE:Android Studio

1.在module的build.gradle文件中,打开dataBinding

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

由于build.gradle更改后,Android Studio会在右上角自动提示是否sync now,点击sync now

2.Data Bindingviewmodel进行解耦,避免在controller中或者在presenter中进行view与数据的处理。使用时,在界面对应的layout文件中,添加标签


    ...

使原来的布局放置在两个layout标签中,即layout标签为根标签。注意layout为小写。并将命名空间xmlns移到layout标签下,即:

    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
或者
    <layout xmlns:android="http://schemas.android.com/apk/res/android">

3.当添加完layout标签后,Android Studio就会自动根据layout布局文件的名称,生成对应的java文件。
如,在activity_main.xml文件中添加layout根标签后,会生成ActivityMainBinding.java,此类继承自android.databinding.ViewDataBinding

由此可见,动态生成的ViewDataBinding类的命名规则是:
根据相应的xml文件名称,去掉下划线,采用驼峰法,首字母大写,最后添加Binding后缀,activity_main.xml对应的ViewDataBinding类为ActivityMainBinding.java

4.在java文件中使用Data Binding时,去掉之前的context.setContentView,采用DataBindingUtil的函数:

    public static  T setContentView(Activity activity, int layoutId)

针对此例,在activityonCreate函数中使用如下代码替换setContentView(R.layout.activity_main);

    ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

binding对象根据对应的layout文件中的内容,实时动态的改变。如当为某个view添加id后,binding对象会自动根据新添加的id生成对应的成员变量,此例中,我为一个TextView添加了android:id="@+id/title_text_view"binding对象中自动添加了titleTextView成员变量,这样也就省去了findViewById的工作。同时我们可以看到,binding对象的命名规则是将布局文件中id的命名去掉下划线,转换为驼峰命名。如果文件中id的命名中没有下划线,则不会进行改变。

此时文件为:

package cn.carbs.android.testdatabinding;

import android.databinding.DataBindingUtil;
import android.databinding.ViewDataBinding;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import cn.carbs.android.testdatabinding.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        binding.titleTextView.setText("hello data binding");
    }
}

MainActivity对应的布局文件为:


<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <RelativeLayout
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context="cn.carbs.android.testdatabinding.MainActivity">

        <TextView
            android:id="@+id/title_text_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!" />
    RelativeLayout>
layout>

运行后,activity中的TextView显示"hello data binding"

到此为止我们采用了在java代码中使用binding.viewName.setXxx()的方式对view进行数据填充绑定。
下面使用另一种方式:在layout文件中添加data标签进行数据绑定:

5.接着上面的步骤,在布局文件中,layout标签下添加data标签,并将data标签做为layout标签中的首个子标签

    <data>
        "aStudent"
            type="cn.carbs.android.testdatabinding.Student"/>
    data>

name是变量的名称,type是变量的类型,相当于cn.carbs.android.testdatabinding.Student aStudent;语句。(Student是一个java bean

在相应的View中采用如下方式绑定数据:

    id="@+id/title_text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{aStudent.name}" />

在对应的java文件中,相应的ViewDataBinding对象就会根据变量名称(data标签中variable标签下的name指定的变量名称),生成set方法,此例中,可通过如下方式,在java类中绑定数据

    Student s = new Student();
    s.name = "Rick";
    ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    binding.setAStudent(s);

即java类中负责设置beanxml布局文件中,负责将bean的信息分别设置到对应的view上。
另外,android:text中的文字可以添加表达式,如下所示:

    android:text='@{String.valueOf(1) + "_prefix_" + aStudent.name}'

可进行字符串连接,String的转换等。如果需要使用到双引号,即添加指定的字符串,那么将外层的双引号改为单引号即可

6.在xml布局文件中绑定事件:
android:onClick android:onLongClick android:onTextChanged等,其中onClickAndroid中本身带有的属性,而android:onLongClick android:onTextChanged则是Data Binding添加的。
首先在声明响应onClick等事件的类,如:

   public class Presenter{
        public void onClick(View v){
            //do something...
        }
    }

其次在xml文件中,添加此类对应的对象

    "aPresenter"
        type="cn.carbs.android.testdatabinding.MainActivity.Presenter" />

并在TextView中添加对应的onclick说明

    android:onClick="@{aPresenter.onClick}"

三、原理说明

注意到,在编译运行后,在app\build\generated\source\apt\debug\your_package_name\databinding文件夹中找到ActivityMainBinding.java文件,如下所示:

package cn.carbs.android.testdatabinding.databinding;
import cn.carbs.android.testdatabinding.R;
import cn.carbs.android.testdatabinding.BR;
import android.view.View;
public class ActivityMainBinding extends android.databinding.ViewDataBinding  {

    private static final android.databinding.ViewDataBinding.IncludedLayouts sIncludes;
    private static final android.util.SparseIntArray sViewsWithIds;
    static {
        sIncludes = null;
        sViewsWithIds = new android.util.SparseIntArray();
        sViewsWithIds.put(R.id.title_text_view, 1);
    }
    // views
    public final android.widget.RelativeLayout activityMain;
    public final android.widget.TextView titleTextView;
    // variables
    // values
    // listeners
    // Inverse Binding Event Handlers

    public ActivityMainBinding(android.databinding.DataBindingComponent bindingComponent, View root) {
        super(bindingComponent, root, 0);
        final Object[] bindings = mapBindings(bindingComponent, root, 2, sIncludes, sViewsWithIds);
        this.activityMain = (android.widget.RelativeLayout) bindings[0];
        this.activityMain.setTag(null);
        this.titleTextView = (android.widget.TextView) bindings[1];
        setRootTag(root);
        // listeners
        invalidateAll();
    }

    @Override
    public void invalidateAll() {
        synchronized(this) {
                mDirtyFlags = 0x1L;
        }
        requestRebind();
    }

    @Override
    public boolean hasPendingBindings() {
        synchronized(this) {
            if (mDirtyFlags != 0) {
                return true;
            }
        }
        return false;
    }

    public boolean setVariable(int variableId, Object variable) {
        switch(variableId) {
        }
        return false;
    }

    @Override
    protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
        switch (localFieldId) {
        }
        return false;
    }

    @Override
    protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        // batch finished
    }
    // Listener Stub Implementations
    // callback impls
    // dirty flag
    private  long mDirtyFlags = 0xffffffffffffffffL;

    public static ActivityMainBinding inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot) {
        return inflate(inflater, root, attachToRoot, android.databinding.DataBindingUtil.getDefaultComponent());
    }
    public static ActivityMainBinding inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot, android.databinding.DataBindingComponent bindingComponent) {
        return android.databinding.DataBindingUtil.inflate(inflater, cn.carbs.android.testdatabinding.R.layout.activity_main, root, attachToRoot, bindingComponent);
    }
    public static ActivityMainBinding inflate(android.view.LayoutInflater inflater) {
        return inflate(inflater, android.databinding.DataBindingUtil.getDefaultComponent());
    }
    public static ActivityMainBinding inflate(android.view.LayoutInflater inflater, android.databinding.DataBindingComponent bindingComponent) {
        return bind(inflater.inflate(cn.carbs.android.testdatabinding.R.layout.activity_main, null, false), bindingComponent);
    }
    public static ActivityMainBinding bind(android.view.View view) {
        return bind(view, android.databinding.DataBindingUtil.getDefaultComponent());
    }
    public static ActivityMainBinding bind(android.view.View view, android.databinding.DataBindingComponent bindingComponent) {
        if (!"layout/activity_main_0".equals(view.getTag())) {
            throw new RuntimeException("view tag isn't correct on view:" + view.getTag());
        }
        return new ActivityMainBinding(bindingComponent, view);
    }
    /* flag mapping
        flag 0 (0x1L): null
    flag mapping end*/
    //end
}

Data Binding在java编译之前,首先会使用apt技术,解析相关的xml等文件,并生成类文件,因此Data Binding使用的并非反射机制,这样也就避免了使用反射造成的性能损耗。

四、注意事项

1.类似于ButterKnifeGreenDao3.x等第三方扩展库,Data Binding会在运行时,根据布局文件通过一定的规则自动生成java文件,如果发现相应的类没有自动生成(Android Studio中会报错,找不到响应的类文件),可以采用clean或者rebuild的方式,让AndroidStudio主动生成代码。

参考:http://www.imooc.com/learn/719

你可能感兴趣的:(android)