专栏名称: xiangzhihong
前端跨平台工程师、客户端工程师
目录
相关文章推荐
观海新闻  ·  周杰伦方紧急声明:无任何法律效力! ·  19 小时前  
观海新闻  ·  周杰伦方紧急声明:无任何法律效力! ·  19 小时前  
河北交通广播  ·  【992 | 万象】邓超、孙俪因这事冲上热搜! ·  昨天  
河北交通广播  ·  【992 | 刚刚】最新房价,公布了 ·  2 天前  
51好读  ›  专栏  ›  xiangzhihong

Android Jetpack架构组件(八)之DataBinding

xiangzhihong  · 掘金  ·  · 2021-01-18 14:43

正文

阅读 42

Android Jetpack架构组件(八)之DataBinding

一、DataBinding简介

在传统的Android应用开发中,布局文件通常只负责应用界面的布局工作,如果需要实现页面交互就需要调用setContentView()将Activity、fragment和XML布局文件关联起来。然后通过控件的id找到控件,接着在页面中通过代码对控件进行逻辑处理。在这种传统的开发方式中,页面承担了大部分的工作量,大量的逻辑处理需要在Activity、Fragment中进行处理,因此页面显得臃肿不堪,维护起来也很困难,为了减轻页面的工作量,Google提出了DataBiiding(视图绑定)。

DataBinding的出现让布局文件承担了原本属于Activity、Fragment页面的部分逻辑,使得Activity页面和XML布局之间的耦合度进一步降低。DataBinding主要有以下特点:

  • 代码更加简介,可读性高,能够在XML文件中实现UI控制。
  • 不再需要findViewById操作,
  • 能够与Model实体类直接绑定,易于维护和扩展。

事实上,DataBinding和MVVM架构是分不开的,DataBinding正是Google为了能够更好的实现MVVM架构而实现的。

二、DataBinding基本使用

2.1 开启viewBinding

视图绑定功能可按模块启用,要在某个模块中启用视图绑定,请将 viewBinding 元素添加到build.gradle 文件中,如下所示。

android {
        ...
        viewBinding {
            enabled = true
        }
    }
复制代码

DataBinding 是一个 support 包,添加完后上面的脚本后会发现工程的的External Libraries中多了四个aar包。分别是adapters、commen、runtime和viewbinding。

使用DataBinding时,如果希望在生成绑定类时忽略某个布局文件,可以将 tools:viewBindingIgnore="true" 属性添加到相应布局文件的根视图中,如下所示。

<LinearLayout
   ...
    tools:viewBindingIgnore="true" >
   ...
</LinearLayout>
复制代码

2.2 修改布局文件

使用DataBinding的第一步,就是先改造XML文件。其实改造布局文件也特别简单,只需要在原来文件内容的基础上,最外层改为 <layout> 标签即可,如下所示。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
 
       ... 
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" 
            android:textSize="24dp"
            android:text="HelloWord" />

       ... 
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
复制代码

当然,我们也可以选中根布局,按住【Alt + 回车键】然后选择 【Convert to data binding layout】也可以生成 DataBinding 需要的布局规则。在布局最外层加layout标签后,重新编译项目,DataBinding库就会生成对应的Binding类,该类用来实现XML布局文件与Model类的绑定,代码如下。

public class ActivityMainBindingImpl extends ActivityMainBinding  {

    @Nullable
    private static final androidx.databinding.ViewDataBinding.IncludedLayouts sIncludes;
    @Nullable
    private static final android.util.SparseIntArray sViewsWithIds;
    static {
        sIncludes = null;
        sViewsWithIds = null;
    }
    // views
    @NonNull
    private final androidx.constraintlayout.widget.ConstraintLayout mboundView0;
    // variables
    // values
    // listeners
    // Inverse Binding Event Handlers

    public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
        this(bindingComponent, root, mapBindings(bindingComponent, root, 1, sIncludes, sViewsWithIds));
    }
    private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
        super(bindingComponent, root, 0
            );
        this.mboundView0 = (androidx.constraintlayout.widget.ConstraintLayout) bindings[0];
        this.mboundView0.setTag(null);
        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;
    }

    @Override
    public boolean setVariable(int variableId, @Nullable Object variable)  {
        boolean variableSet = true;
            return variableSet;
    }

    @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;
    /* flag mapping
        flag 0 (0x1L): null
    flag mapping end*/
    //end
}
复制代码

生成的ActivityMainBindingImpl代码位于app/build目录下。生成Binding类的名字很特殊,它与XML布局文件的名字有对应关系,具体的联系就是,以XML布局文件为准,去掉下划线,所有单次以大驼峰的形式按顺序拼接,最后再加上Binding。比如,我的XML布局文件名是activity_main.xml,生成的Binding类名就是ActivityMainBinding。

2.3 绑定布局

没有使用DataBinding的时候,为了将XML布局文件与Activity进行绑定,需要调用Activity的setContentView()方法,或者是在Fragment中调用LayoutInflate的inflate()方法来进行布局的绑定。如果使用了DataBinding之后,就需要使用DataBindingUtil类来进行视图的绑定,如下所示。

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding binding; 
​
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding= DataBindingUtil.setContentView(this,R.layout.activity_main);     
    }
}
复制代码

使用DataBindingUtil类的setContentView()方法对Activity进行绑定,其返回值就是工具生成的Binding类。如果是Fragment,则对应的绑定布局的代码如下。

    private ActivityMainBinding binding;

    @Override
    public View onCreateView (LayoutInflater inflater,
                              ViewGroup container,
                              Bundle savedInstanceState) {
        binding = ActivityMainBinding.inflate(inflater, container, false);
        View view = binding.getRoot();
        return view;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        binding = null;
    }
复制代码

2.4 添加data标签

经过前面的步骤后,我们已经使用DataBinding将XML文件与UI组件绑定起来,如果要在XML文件中接受Model数据,就需要用到data标签与variable标签。

在XML文件的layout标签下,创建data标签,在data标签中再创建variable标签,variable标签主要用到的就是name属性和type属性,类似于Java语言声明变量时,需要为该变量指定类型和名称。新建一个名为UserModel的实体类,代码如下。

public class UserModel {
    private String firstName;
    private String lastName;

    public UserModel(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return this.firstName;
    }

    public String getLastName() {
        return this.lastName;
    }
}
复制代码

然后在布局的 data 标签里声明要使用到的变量名、类的全路径等信息,如下所示。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
      <variable
          name="user"
          type="com.xzh.jetpack.UserModel" />
    </data>

    ... //省略其他代码
</layout>
复制代码

如果 User 有多处用到,也可以直接将之 import 进来,这样就不用每次都指明整个包名路径了,而 java.lang.* 包中的类会被自动导入,所以可以直接使用。

<data>
   <import type="com.xzh.jetpack.UserModel" />
   <variable
       name="user"
       type="UserModel" />
  </data>
复制代码

如果存在 import 的类名相同的情况,可以使用 alias 指定别名,如下所示。

    <data>
        <import type="com.xzh.jetpack.UserModel" />
        <import
            alias="TempUser"
            type="com.xzh.jetpack.uer.UserModel" />
        <variable
            name="userInfo"
            type="User" />
        <variable
            name="tempUserInfo"
            type="TempUser" />
    </data>
复制代码

2.5 使用variable

在XML文件中声明好variable属性后,接下来就可以在XML使用它了。使用variable属性时需要使用到布局表达式: @{ } 。可以在布局表达式@{ }中获取传入variable对象的值,如下所示。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <import type="com.xzh.jetpack.UserModel" />
        <variable
            name="user"
            type="UserModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.firstName}"
            android:hint="Tv1"
            android:textSize="24dp" />

        <TextView
            android:id="@+id/tv2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.lastName}"
            android:textSize="24dp" />

    </LinearLayout>
</layout>
复制代码

然后,我们在UI界面中使用Binding类为每一个variable标签使用set方法传递数据,如下所示。

 binding= DataBindingUtil.setContentView(this,R.layout.activity_main);
 UserModel user =new UserModel("zhang", "beijing");
 binding.setUser(user);
复制代码

经过上面的处理后,我们已经给UserModel对象设置给了Binding类,所以这里直接运行代码就可以看到效果了。

2.6 响应事件

前面我们介绍了DataBinding的一些基本用法,我们可以在布局文件中对控件某些属性进行赋值,使得Model类数据直接绑定在布局中,而且Model属性发生变化时,布局文件中的内容可以即时刷新。除了这些简单的使用场景外,我们还可以使用DataBinding响应用户事件。

我们对布局文件做一下修改,在里面添加一个控件,然后在Activity中添加如下代码。

 binding.btn1.setOnClickListener(new View.OnClickListener() {
            @Override
     public void onClick(View v) {

       }
  });
复制代码

除此之外,我们还可以使用另外一种方式。布局表达式不仅可以传入对象的属性,也可以调用对象的方法。首先创建一个工具类,在类中定义响应事件的方法,如下所示。

public class ButtonClickListener {
    public void onClick(View view) {
        Log.d("ButtonClickListener","onClick...");
    }
}
复制代码

然后在布局文件中添加点击事件的代码,如下所示。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="btnHandler"
            type="com.xzh.jetpack.databinding.ButtonClickListener" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical">

       ...//省略其他代码

        <Button
            android:id="@+id/btn1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="按钮"
            android:textSize="24dp"
            android:onClick="@{btnHandler.onClick}"/>

    </LinearLayout>
</layout>
复制代码

在上面的代码中,首先在data标签中为ButtonClickListener类声明对象,在Button的onClick属性中传入布局表达式即可。

2.7 include标签

在Android应用开发中,为了能够让布局文件得到复用,在编写布局的时候我们经常会使用include标签,相同结构与内容的布局文件就可以在多处使用。但是如果一个布局文件中使用了DataBinding,同时也使用了include标签,那么如何使用nclude标签引入的布局文件中中的数据呢。

此时,我们需要在同一级页面的include标签中,通过命名控件xmlns:app来引入布局变量User,将数据对象传递给二级页面,如下所示。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="user"
            type="com.xzh.jetpack.UserModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical">

        <include
            layout="@layout/layout_data_binding"
            app:persondata="@{user}" />
        
        ... //省略其他代码
    </LinearLayout>
</layout>
复制代码

布局表达式中直接传入页面变量user,include标签属性值可以任意取名,但是要注意的是,在二级页面的variable标签中的name属性,必须与一级页面中的include标签属性名一致,如layout_data_binding的代码所示。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="userData"
            type="com.xzh.jetpack.UserModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

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

    </LinearLayout>
</layout>
复制代码

三、BindingAdapter

3.1 BindingAdapter简介

使用DataBinding库时,DataBinding会针对控件属性生成对应的XXXBindingAdapter类,如TextViewBindingAdapter类,其对TextView的每个可以使用DataBinding的属性都生成了对应的方法,而且每个方法都使用了@BindingAdapter注解,注解中的参数就是对应View的属性。

@RestrictTo(RestrictTo.Scope.LIBRARY)
@SuppressWarnings({"WeakerAccess", "unused"})
@BindingMethods({
        @BindingMethod(type = TextView.class, attribute = "android:autoLink", method = "setAutoLinkMask"),
        @BindingMethod(type = TextView.class, attribute = "android:drawablePadding", method = "setCompoundDrawablePadding"),
        @BindingMethod(type = TextView.class, attribute = "android:editorExtras", method = "setInputExtras"),
        @BindingMethod(type = TextView.class, attribute = "android:inputType", method = "setRawInputType"),
        @BindingMethod(type = TextView.class, attribute = "android:scrollHorizontally", method = "setHorizontallyScrolling"),
        @BindingMethod(type = TextView.class, attribute = "android:textAllCaps", method = "setAllCaps"),
        @BindingMethod(type = TextView.class, attribute = "android:textColorHighlight", method = "setHighlightColor"),
        @BindingMethod(type = TextView.class, attribute = "android:textColorHint", method = "setHintTextColor"),
        @BindingMethod(type = TextView.class, attribute = "android:textColorLink", method = "setLinkTextColor"),
        @BindingMethod(type = TextView.class, attribute = "android:onEditorAction", method = "setOnEditorActionListener"),
})

public class






请到「今天看啥」查看全文