一、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 + 回车键,如图:
对比我们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,编译完成后可以发现左边截图的情况,表示我们的操作是成功的。
默认情况下,这个类名是基于布局文件的名称创建的,将其转换为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")
最后是运行效果:
四、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()
}
}
其实仔细观察发现:上面的第二种方法跟传统的方式一样,没什么本质区别。