学习笔记:DataBinding(一):上手DataBinding

DataBinding是什么

数据绑定库是一种支持库,借助该库,您可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。

使用DataBinding之前:

    findViewById<TextView>(R.id.sample_text).apply {
     
        text = viewModel.userName
    }

使用DataBinding之后:

<TextView
        android:text="@{viewmodel.userName}" />

如上在布局中使用@{}进行单向绑定,就完成了之前使用代码查找Textview再设置值的操作。


为什么要有DataBinding,它能解决什么问题?

视图绑定、数据驱动、事件处理,减少模板代码,增加代码及逻辑清晰度,提高开发效率和维护效率;

自动空检测,避免空指针异常;

防止内存泄露,提高应用性能


使用入门

DataBinding支持在Android OS 4.0 及以上使用;支持Android Gradle Plugin 1.5及以上,但最好使用最新的plugin版本。

哪个moudle使用,就在哪个moudle的build.gradle中添加如下代码即可使用DataBinding:

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

数据类

// 使用Kotlin中的data关键字创建User数据类
data class User(val firstName: String,val lastName: String)

布局文件

打开布局文件,选中根布局,按住 Alt + 回车键,点击Convert to data binding layout,就可以转换为DataBinding 的布局格式

学习笔记:DataBinding(一):上手DataBinding_第1张图片

<!-- activity_main.xml -->

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

    <data>
        <variable name="user" type="com.example.databindingsample.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>

DataBinding布局文件略有不同,以layout根标记开始,然后是数据元素和我们之前的视图根元素:

  1. 作为根布局是DataBinding固定写法
  2. 是声明数据区
  3. 中声明了一个user变量,类型是User类型
  4. 第一个Textview通过DataBinding语法@{}设置text为user.firstName
  5. 第二个Textview通过DataBinding语法@{}设置text为user.lastName

rebuild project会生成相关的bind类, 绑定类名称根据布局文件生成,activity_main.xml则生成ActivityMainBinding.java

学习笔记:DataBinding(一):上手DataBinding_第2张图片

该类包含所有布局属性到布局视图的绑定,自动生成了对应的getter/setter方法

public abstract class ActivityMainBinding extends ViewDataBinding {
     
  @Bindable
  protected User mUser;

  protected ActivityMainBinding(DataBindingComponent _bindingComponent, View _root,
      int _localFieldCount) {
     
    super(_bindingComponent, _root, _localFieldCount);
  }

  public abstract void setUser(@Nullable User user);

  @Nullable
  public User getUser() {
     
    return mUser;
  }
  ……
}

MainActivity.kt

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?){
        super.onCreate(savedInstanceState)
		
        // 通过DataBindingUtil设置布局文件
        val binding: ActivityMainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main)
        
        // 通过生成的绑定类,给布局中的user变量赋值
        // 相当于binding.setUser(new User("Test","User"))
        binding.user = User("Test","User")
    }
}

运行后显示效果如下:

学习笔记:DataBinding(一):上手DataBinding_第3张图片


自定义绑定类名称

上面说明了绑定类的生成规则,activity_main.xml则生成ActivityMainBinding.java,下面我们来自定义绑定类的名称:


	
    
		……
    

	……

学习笔记:DataBinding(一):上手DataBinding_第4张图片

    
		……
    

学习笔记:DataBinding(一):上手DataBinding_第5张图片
默认

    
		……
    

学习笔记:DataBinding(一):上手DataBinding_第6张图片


表达式

DataBinding 支持在布局文件中使用以下运算符、表达式和关键字

  • 算术 + - / * %
  • 字符串合并 +
  • 逻辑 && ||
  • 二元 & | ^
  • 一元 + - ! ~
  • 移位 >> >>> <<
  • 比较 == > < >= <=
  • Instanceof
  • Grouping ()
  • character, String, numeric, null
  • Cast
  • 方法调用
  • Field 访问
  • Array 访问 []
  • 三元 ?:

目前不支持以下操作

  • this
  • super
  • new
  • 显示泛型调用

其他:

  • 属性引用
android:text="@{user.lastName}"

生成的数据绑定代码自动检查空值并避免空指针异常。如果user为null ,那么 user.name默认值为null ; 如果有类似于age字段int类型,user为null, 则user.age默认值为0

答案就在自动生成的ActivityMainBindingImpl.java中:

学习笔记:DataBinding(一):上手DataBinding_第7张图片

	@Override
    protected void executeBindings() {
     
        long dirtyFlags = 0;
        synchronized(this) {
     
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        // 1. 设置默认值
        java.lang.String userFirstName = null;
        int userAge = 0;
        com.example.databindingsample.User user = mUser;

        if ((dirtyFlags & 0x3L) != 0) {
     
            
			// 2. 判空
            if (user != null) {
     
                // read user.firstName
                userFirstName = user.getFirstName();
                // read user.age
                userAge = user.getAge();
            }
        }
        // batch finished
        if ((dirtyFlags & 0x3L) != 0) {
     
            // api target 1

            // 3. 赋值                         			
            androidx.databinding.adapters.TextViewBindingAdapter
                .setText(this.mboundView1, userFirstName);
            this.mboundView2.setText(userAge);
        }
    }
  • ??空合并运算符, 左操作数不为空,就使用左操作数,为空则使用右操作数
android:text="@{user.displayName ?? user.lastName}"

相当于:

android:text="@{user.displayName != null ? user.displayName : user.lastName}"
  • []集合引用
<data>
	<!-- java.lang包外的需要导包,或者使用全类名 -->
    <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]}"


在xml中< 需要转义,所以List要写成List<String>

  • 字符串字面值
android:text='@{map["firstName"]}'
android:text="@{map[`firstName`]}"

可以使用单引号包围属性值,在表达式中使用双引号;或者使用双引号包围属性值,在表达式里使用反单引号 **` **

  • 资源引用
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

有些资源引用需要用显示类型,如下:

Type Normal reference Expression reference
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
color int @color @color
ColorStateList @color @colorStateList

目前发现不支持:@mipmap/


事件处理

方法引用

当表达式求值为方法引用时,数据绑定将方法引用和所有者对象包装在侦听器中,并在目标视图上设置该侦听器。如果表达式计算结果为null,则数据绑定不会创建侦听器,而是设置一个空侦听器。

class MyHandler{
     

    fun customOnClick(v: View){
     
        Toast.makeText(v.context,"点击成功", Toast.LENGTH_LONG).show()
    }
}
override fun onCreate(savedInstanceState: Bundle?) {
     
        super.onCreate(savedInstanceState)

        val binding: ActivityMainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main)
        binding.user = User("Test","User",100)

        binding.myHandler = MyHandler()
    }
<data>

    <variable
    	name="myHandler"
    	type="com.example.databindingsample.MyHandler" />
data>

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:onClick="@{myHandler::customOnClick}"
    android:text="@{String.valueOf(user.age)}" />

在表达式中,可以引用方法,但该方法除了方法名(权限修饰符、返回值类型、形参列表)都要和真实的侦听器方法保持一致。

例如上面的代码,方法名可以随便定义,但参数一定要和OnClickListener中的onClick(View v)方法保持一致,比如我把自定义方法中的View参数去除,编译报错如下

错误: 找不到符号
import com.example.databindingsample.databinding.ActivityMainBindingImpl;
                                                ^
符号:   类 ActivityMainBindingImpl
位置: 程序包 com.example.databindingsample.databinding


* What went wrong:
Execution failed for task ':databindingsample:compileDebugJavaWithJavac'.
> android.databinding.tool.util.LoggedErrorException: Found data binding errors.
  ****/ data binding error ****msg:Listener class android.view.View.OnClickListener with method onClick did not match signature of any method myHandler::customOnClick 

侦听器绑定

与方法引用不同,在侦听器绑定中,只需要返回值和真实的侦听器的返回值保持一致即可。

fun onClick(v: View,user: User){
     
        val textView = v as TextView
        Toast.makeText(mContext,"点击了" + textView.text, Toast.LENGTH_LONG).show()
    }
 <TextView
     android:id="@+id/tv_name"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:text="@{user.firstName}"
     android:onClick="@{(tvName) -> tvName.isEnabled() ? myHandler.onClick(tvName,user) : void }"/>

Lambda表达式左侧的参数,是以真实的侦听器为准的


import、variable、includes

import使我们在xml中更容易引用,如下:

// 导入前
<variable
	name="user"
	type="com.example.databindingsample.User" />
<variable
	name="user2"
	type="com.example.databindingsample.User" />
// 导入后
<import type="com.example.databindingsample.User" />
<variable
	name="user"
	type="User" />
<variable
	name="user2"
	type="User" />

类名相同时,import也支持别名

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

variable声明变量上面已经用了很多次了,我们在标签里声明的variable都会在生成从bind类中生成对应的getter/setter方法,并且设置了默认值

includes

在activity_main.xml中增加include标签引入布局,并传递数据


<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:bind="http://schemas.android.com/apk/res-auto">

    <data>

        <import type="com.example.databindingsample.User" />

        <variable
            name="user"
            type="User" />

    data>

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


        <include
            layout="@layout/test"
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:layout_gravity="center"
            bind:user="@{user}" />

    LinearLayout>

layout>

test.xml


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

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

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

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/colorPrimaryDark"
            android:gravity="center"
            android:text="@{user.lastName}"
            android:textColor="@android:color/white"
            android:textSize="20sp" />

    LinearLayout>
layout>

如上,即可在test.xml中使用user变量

但下面这种作为的直接子元素的布局方式是不支持变量传递的:


<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>
   <merge>
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   merge>
layout>

参考资料:

  • 《官方文档》

你可能感兴趣的:(【Jetpack】,jetpack,databinding)