Android Jetpack 架构组件之 DataBinding

一、DataBinding 简介:

DataBinding ,顾名思义即为数据绑定,其推出目的就是为了减少繁琐的代码,使代码更加的简洁、可读性更强。使用 DataBinding 会让我们的布局文件不简简单单的只有一个布局文件的作用,还包含和很多的逻辑。可以大量减少 Java代码。

同时DataBinding还会让我们的代码更有层级,结构更加的清晰完善,数据能够单向或者双向绑定到布局文件当中。这样有助于防止内存泄露,而且能够自动进行空检测以避免空指针异常。

二、 DataBinding 的集成

**DataBinding只能运行在Android 4.0(API>14)或更高版本的设备上。**使用DataBinding之前我们需要完成基本配置:

1、在 App的build.gradle 里面引入对DataBinding的支持:

//引入对 DataBinding 的支持
android {
    buildFeatures {
        dataBinding = true
        viewBinding = true// for view binding
    }
}

2、在 gradle.properties 文件当中添加数据绑定的编译器:

android.databinding.enableV2=true

3、在布局文件当中,选中根布局的ViewGroup,然后按住Alt + 回车键,如图:

Android Jetpack 架构组件之 DataBinding
对比我们Alt + 回车键操作之前能够发现,xml布局文件里面新增了标签。具体情况我们稍后细说。到这里,DataBinding的继承配置工作就完成了。接下来就是主菜:DataBinding的应用。

三、DataBinding 的简单使用

DataBinding有很多应用场景和方法,下面我们只介绍一下 DataBinding 的简单的使用,其他的在另外一部分里面介绍。

data标签构建了View和Model之间的连接通道。通过data标签就可以把Model层与 View层绑定在一起。具体使用如下:

1、我们首先创建一个数据类型:

data class Student(var name:String, var grade:String, var age:String) {   ...... }

2、接下来的操作基本上都是在xml布局文件里面:

首先,在xml的data标签当中声明要使用到的变量、类的全路径;
然后,引用数据类型;
最后,在需要的地方给属性赋予默认值。关于xml里面的操作情况见下面的代码以及关键信息和字段的说明:

name:为方便我们使用,给数据类起的别名,比如我们的数据类型是Student,下面方式一和方式二里面的name都是我们给Student起的别名,我们总不至于直接使用Student,这样跟Student.kt重合不容易区分开,容易出问题。

type:指定我们要引用的数据类的完整路径名,比如“com.androidjetpack.livedata.Student”是Student的完整路径。

import:如果我们使用的Student类型会在很多地方用到,我们也可以采用 import 的方式引进来,这样我们就不用每次都指明整个包名的路径了。

<data>
    <!--方式一-->
    <variable
        name="sudentInfo"
        type="com.androidjetpack.databinding.Student" />
    <!--方式二-->
    <import type="com.androidjetpack.databinding.Student"/>
    <variable
        name="mStudent"
        type="Student" />
</data>

实际使用中数据的引用方式:

@{***.***}

另外,在初始状态下Student的所有成员属性都是没有值的,不方便我们在不居中观察状态.
因此我们有两个方案来解决:
一是:

tools:text="***"

tools开头的属性在布局的时候会显示出来,但在正式运行的时候不会。

二是:

android:text="@{***.***, default = vaue}"

方案二有个缺陷:value值里面不能包含标点符号,无论中英文标点符号,只能包含少量的特殊字符,比如“$”、“_”等等。而方案一则灵活不受限制。

<layout xmlns:android=http://schemas.android.com/apk/res/android
    xmlns:tools=http://schemas.android.com/tools
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <!--方式一-->
        <variable            
	        name="sudentInfo"      
	        type="com.androidjetpack.livedata.Student" />
        <!--方式二-->
        <import type="com.androidjetpack.livedata.Student"/>
        <variable            
	        name="mStudent"      
	        type="Student" />
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"        
        android:layout_height="match_parent"
        tools:context=".livedata.LiveDataActivity"     
        android:background="@mipmap/fire">
        
        <TextView
            android:id="@+id/tvName"	
            android:layout_width="wrap_content"	
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"  
            android:layout_marginTop="10dp"  
            android:text="@{mStudent.name}"     
            tools:text="名字"    
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>
            
        <TextView
            android:id="@+id/tvGrade"    
            android:layout_width="wrap_content"     
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"   
            android:layout_marginTop="10dp"    
            android:text="@{mStudent.grade}"     
        	tools:text="年级" 
        	app:layout_constraintLeft_toRightOf="@+id/tvName" 
        	app:layout_constraintTop_toTopOf="parent"/>
        	
        <TextView
            android:id="@+id/tvAge"   
            android:layout_width="wrap_content"		
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"   	
            android:layout_marginTop="10dp"
            android:text="@{mStudent.age, default=年龄18}"
            app:layout_constraintLeft_toRightOf="@+id/tvGrade"
            app:layout_constraintTop_toTopOf="parent"/>
    </androidx.constraintlayout.widget.ConstraintLayout>

layout布局文件写好之后,再Build→→Rebuild Project,编译完成后可以发现左边截图的情况,表示我们的操作是成功的。
Android Jetpack 架构组件之 DataBinding
默认情况下,这个类名是基于布局文件的名称创建的,将其转换为Pascal大小写并向其添加Binding后缀。上面的布局文件名是activity_live_data.xml,因此相应的生成类是 ActivityLiveDataBinding

3、接下来我们就需要在Activity当中为「userInfo」赋值:

val binding: ActivityLiveDataBinding = DataBindingUtil.setContentView(this,R.layout.activity_live_data)

上面这行代码的几个工具类的说明:

ActivityLiveDataBinding:我们上面截图里面生成的类;
DataBindingUtil:一个DataBinding的辅助工具类;
activity_live_data:数据绑定所在的布局文件。

binding.mStudent = Student("陈程", "9年级", "15")

最后是运行效果:
Android Jetpack 架构组件之 DataBinding

四、DataBinding进阶

1、避免使用复杂的表达式

在官方的指南里有这样的写法:

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

或者这样的

<TextView
    android:layout_width="wrap_content"    	
    android:layout_height="wrap_content"
    android:text="@{String.valueOf(index + 1)}"	
	android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
	android:transitionName='@{"image_" + id}'	
	android:text="@{@string/nameFormat(firstName, lastName)}"
	android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
	android:text="@{map[`firstName`]}"/>

需要注意:

这些都不代表最佳实践,只能说支持这样的写法、用法上面的写法、用法它有一个很大的弊端,相信很多刚入门DataBinding的人都遇到过找不到binding文件的错,然后被劝退,原因无外乎就是表达式写错、variable的类路径不对或者没import相应的类(比如View)等等,都与复杂的表达式有关系,而DataBinding报错也不太智能,有时不能准确定位。

所以建议不要在xml里进行复杂的数据绑定,复杂的逻辑处理尽量放到ViewModel里xml只绑定简单的基础数据类型。比如下面的操作:

<data>    	
	<variable name="vm" 
		type="com.example.UserViewModel"/>		
</data>
<TextView
    android:text="@{ vm.capitalizeLastName}"  
    android:layout_width="wrap_content"  
    android:layout_height="wrap_content"
    android:hint="@{ vm.index}"   
    android:visibility="@{vm.showName}"  	
    android:transitionName='@{ vm.traName}'/>

2、点击事件的命名和处理

DataBinding支持的写法有以下几种:

android:onClick="@{presenter.onClick()}" //1.方法引用
android:onClick="@{()->presenter.onClick()}" //2.lamda表达式
android:onClick="@{(view)->presenter.onClick(view)}" //3.lamda表达式
android:onClick="@{()->presenter.onClick(item)}"//4.带参数lamda表达式
android:onClick="@{(view)->presenter.onClick(view, item)}"//5.带参数lamda表达式

选择很多,而且五花八门,有的喜欢直接使用viewmodel里的方法,有的直接将Activity或者fragment作为handler,还有的可能会在activity/fragment里写一个内部类作为presenter,然后由于方法名也可以自定义,所以很可能出现你叫presenter.save(),另一个叫**(v)->handler.onSave(v)**的情况。
比较糟糕的写法:

<layout>
    <data>
        <variable        
	        name="vm"        
	        type="io.ditclear.app.viewmodel.AnimalViewModel"/>
        <variable        
	        name="handler"        
	        type="io.ditclear.app.view.AnimalActivity"/>
        <variable        
	        name="presenter"        
	        type="io.ditclear.app.view.AnimalActivity.Presenter"
    </data>
    <LinearLayout
        tools:context="io.ditclear.app.view.AnimalActivity">
        
        <Button            
        android:onClick="@{vm.shoutWhat()}"/>
        <Button            
        android:onClick="@{(v)->handler.shout(v)}"/>
        <Button           
         android:onClick="@{()->presenter.onShout()}"/>
    </LinearLayout>
</layout>

比较好的写法有两种:

一是不依赖于dataBinding,但是也不用传统通过OnClickListener接口实现。

Xml里面实现如下:

<TextView
    android:id="@+id/tvAge"      
    android:layout_width="wrap_content"    
    android:layout_height="wrap_content"
    android:onClick="ageClick"     
    android:text="@{mStudent.age, default=年龄18}"
    app:layout_constraintLeft_toRightOf="@+id/tvGrade"
    app:layout_constraintTop_toTopOf="parent"/>

对应的kotlin代码里面:

fun ageClick(mView:View){    
	println("试试这样的写法????????????????????")	
}

第二就是,通过dataBinding实现:

<layout>
    <data>
        <variable            
	        name="vm"            
	        type="io.ditclear.app.viewmodel.AnimalViewModel"/>
	        
        <variable            
	        name="presenter"      
	        type="io.ditclear.app.helper.Presenter"
    </data>
    <LinearLayout           
    	tools:context="io.ditclear.app.view.AnimalActivity">
    	
        <Button            
	        android:id="@+id/save_btn"       
	        android:onClick="@{(v)->presenter.onClick(v)}"/>
	        
        <Button    		
	        android:id="@+id/submit_btn"    
	        android:onClick="@{(v)->presenter.onClick(v)}"/>
    </LinearLayout>
</layout>

然后kotlin代码里这样的接口:

interface Presenter : View.OnClickListener {    
	override fun onClick(v: View)	
}

这里推荐使用(v)->presenter.onClick(v)的写法,原因之一是比较直观一点,其二是需要参数view。

接着在activity/fragment中来实现Presenter接口,处理点击事件

class AnimalActivity : AppCompatActivity(), Presenter {
    private var mBinding: AnimalActivityBinding? = null
    private var mViewModel: AnimalViewModel? = null
    
    override fun onCreate(@Nullable savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        mBinding = DataBindingUtil.setContentView(this, R.layout.animal_activity)
        val animal = Animal("dog", 1)
        mViewModel = AnimalViewModel(animal)
        mBinding.setVm(viewModel)
        mBinding.setPresenter(this)
    }

    @SingleClick
    fun onClick(v: View) {
        //根据id进行区分
        when (v.id) {
            R.id.save_btn -> save()
            R.id.delete_btn -> delete()
            R.id.submit_btn -> submit()
        }
}
    private fun save() {//调用viewModel的方法
        mViewModel.save()
    }
	
	private fun delete() {//调用viewModel的方法
        mViewModel.delete()
    }
	
	private fun submit() {//调用viewModel的方法
        mViewModel.submit()
    }
}

其实仔细观察发现:上面的第二种方法跟传统的方式一样,没什么本质区别。

上一篇:Jetpack 高级程序开发组件


下一篇:深入剖析原理!Jetpack-MVVM-高频提问和解答,附超全教程文档