Android Jetpack之DataBinding(一)

DataBinding简介

DataBinding综述
DataBinding是Google Jetpack组件中的一员,该库使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。
使用该库,借助布局文件中的绑定组件,您可以移除 Activity 中的许多界面框架调用,使其维护起来更简单、方便。
还可以提高应用性能(绑定的时候遍历一遍View,而不是findViewById每次遍历查询),并且有助于防止内存泄漏以及避免发生 Null 指针异常
(该库进行了空指针判断)。
DataBinding相关的类
  1. DataBindingUtil.java类:类,作为Util在Activity中获取相关的Binding对象。
  2. BaseObservable.java类:类,Bean可以继承该抽象类,实现可观察的模式,在set属性的时候调用notifyPropertyChanged方法,唤起刷新操作,也可以调用notifyChange方法全部刷新。
  3. Observable .java接口:接口:Bean可以实现该接口,实现可观察的模式,在set属性的时候调用notifyPropertyChanged方法,唤起刷新操作,也可以调用notifyChange方法全部刷新。
  4. ObservableFloat.java类:类,这不是一个类,而是一类类的代表,如ObservableShort、ObservableParcelable等等,可观察的属性,通过get和set方法操作相关的值。
  5. BaseObservableField<>.java类:类,和上述类似,泛型可以传入String等类型,比上述定义的基类型更加自由。

DataBinding使用

简单使用
控件绑定
  1. 打开DataBinding开关,如下:
	android {
		  dataBinding{
       		 enabled = true
   		 }
	}

新版本需要在gradle.properties配置如下:

android.databinding.enableV2=true
  1. 改造xml布局,添加及标签
<?xml version="1.0" encoding="utf-8"?>
<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>
     
    </data>
    <LinearLayout
            android:orientation="vertical"
            android:gravity="center_horizontal"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".databinding.TestActivity">

        <TextView
                android:id="@+id/tv_name"
                android:layout_marginTop="10dp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>


        <TextView
                android:layout_marginTop="10dp"
                android:id="@+id/tv_age"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

    </LinearLayout>
</layout>
  1. 绑定后代码如下代码具体如下:
	class TestActivity : AppCompatActivity() {

    var binding: ActivityTestDataBindingBinding? = null
    var student: Student = Student("小李", 23)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(
            this,
            R.layout.activity_test_data_binding
        )
        binding?.tvName?.text = "小张"
        binding?.tvAge?.text = 22.toString()
    }

}

效果如图:
效果4

数据绑定
  1. 同上
  2. 改造xml布局,添加及标签,标签用于引入数据如下:
<?xml version="1.0" encoding="utf-8"?>
<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="student"
                  type="com.zgj.demojetpackapp.bean.Student"/>
    </data>
    <LinearLayout
            android:orientation="vertical"
            android:gravity="center_horizontal"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".databinding.TestActivity">


        <TextView
                android:id="@+id/tv_name"
                android:layout_marginTop="10dp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{student.name,default = Jack}"/>

        <TextView
                android:layout_marginTop="10dp"
                android:id="@+id/tv_age"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text='@{student.age+"",default = 16}'/>

    </LinearLayout>
</layout>

Student为一个简单的数据类,如下:

	data class Student (var name:String,var age:Int)
  1. Rebuild(Kotlin需要,Java可以直接使用)之后绑定布局文件,如下:
	class TestActivity : AppCompatActivity() {

    var binding: ActivityTestDataBindingBinding? = null
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this,  R.layout.activity_test_data_binding )
    }

}

  1. 设置数据到布局文件,如下:
class TestActivity : AppCompatActivity() {

    var binding: ActivityTestDataBindingBinding? = null
    var student: Student = Student("李大爷", 80)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(
            this,
            R.layout.activity_test_data_binding
        )
        binding?.student = student
    	//两个等效
    	//binding?.setVariable(BR.student,student)
    }

}

运行效果如图:
效果1
如果不设置数据或设置数据为null进去
则显示默认值(但不会报空指针)如图(左):,但不是default的值,default的值只在预览视图展示,方便调整布局及预览,如图(右)
效果2效果3

使用详解
  1. 表达式语言

可用

  • 算术运算符 + - / * %
  • 字符串连接运算符 +
  • 逻辑运算符 && ||
  • 二元运算符 & | ^
  • 一元运算符 + - ! ~
  • 移位运算符 >> >>> <<
  • 比较运算符 == > < >= <=(请注意,< 需要转义为 <)
  • instanceof
  • 分组运算符 ()
  • 字面量运算符 - 字符、字符串、数字、null
  • 类型转换
  • 方法调用
  • 字段访问
  • 数组访问 []
  • 三元运算符 ?:

不可用

  • this

  • super

  • new

  • 显式泛型调用

    如下代码演示(包含方法调用,字符串拼接、三目运算符、运算符%等,目前instanceof还没有调好):


        <TextView android:layout_width="wrap_content"
                  android:text="@{String.valueOf(10%3)}"
                  android:padding="10dp"
                  android:visibility="@{1>2?View.GONE:View.VISIBLE}"
                  android:layout_height="wrap_content"/>

        <TextView android:layout_width="wrap_content"
                  android:text='@{"新年"+"快乐"}'
                  android:padding="10dp"
                  android:layout_height="wrap_content"/>

        <TextView android:layout_width="wrap_content"
                  android:text='@{"123456".substring(0,2)}'
                  android:padding="10dp"
                  android:layout_height="wrap_content"/>

注意:使用View.GONE的时候要注意早标签内导入类如下,不然会报错:

	  <data>
     	...
        <import type="android.view.View"/>
    </data>

报错如下:
错误1

  1. 双目运算符??进行 Null 合并运算,如果左边的值为null,则取右边的值,右边也为null,那就是null了。
    代码如下(等效于三目运算符):
    <TextView android:layout_width="wrap_content"
              android:text='@{student.name??"Tom"}'
              android:padding="10dp"
              android:layout_height="wrap_content"/>
        <!--等效于-->
   <TextView android:layout_width="wrap_content"
             android:text='@{student.name != null?student.name:"Tom"}'
             android:padding="10dp"
             android:layout_height="wrap_content"/>
  1. 列表List的使用,代码如下:
  binding?.list = listOf("jack","tom","wang")
      <LinearLayout android:layout_width="match_parent"
                      android:orientation="horizontal"
                      android:layout_height="wrap_content">

            <TextView android:layout_width="wrap_content"
                      android:text="@{list[0]}"
                      android:padding="10dp"
                      android:layout_height="wrap_content"/>
            <TextView android:layout_width="wrap_content"
                      android:text="@{list[1]}"
                      android:padding="10dp"
                      android:layout_height="wrap_content"/>
            <TextView android:layout_width="wrap_content"
                      android:text="@{list[2]}"
                      android:padding="10dp"
                      android:layout_height="wrap_content"/>

        </LinearLayout>

执行效果如下(此处超出下标是不会报错的,但是也不会展示内容):
效果4

  1. map的使用
    kotlin代码(可以看到只给name1、name2和name4赋值):
  binding?.map  = mutableMapOf(Pair("name1","jack"), Pair("name2","tom"),Pair("name3","wang"))
        binding?.name1 = "name1"
        binding?.name2 = "name2"
        binding?.name33 = "name3"
        binding?.name4 = "name4"

xml代码:

    <data>
		...
        <variable name="map" type="Map&lt;String,String>"/>
        <variable name="name1" type="String"/>
        <variable name="name2" type="String"/>
        <variable name="name33" type="String"/>
        <variable name="name4" type="String"/>

    </data>

        <LinearLayout android:layout_width="match_parent"
                      android:orientation="horizontal"
                      android:layout_height="wrap_content">

            <TextView android:layout_width="wrap_content"
                      android:text="@{map[name1]}"
                      android:padding="10dp"
                      android:layout_height="wrap_content"/>
            <TextView android:layout_width="wrap_content"
                      android:text="@{map[name2]}"
                      android:padding="10dp"
                      android:layout_height="wrap_content"/>
            <TextView android:layout_width="wrap_content"
                      android:text="@{map.name33}"
                      android:padding="10dp"
                      android:layout_height="wrap_content"/>
            <TextView android:layout_width="wrap_content"
                      android:text="@{map[name4]}"
                      android:padding="10dp"
                      android:layout_height="wrap_content"/>
        </LinearLayout>

效果如下(可以看到第一个第二个展示出来了,但是第三个展示不出来,第四个因为没有对应的key-value所以没有展示):
注意: map.name33 直接将name33作为key来取值 map[name2]是将name2对应代码设置的值作为key来取值,两个是不一样的
效果5

  1. SpareArray使用
    kotlin代码
     val sparse:SparseArray<String> = SparseArray<String>()
        sparse.put(1,"jack")
        sparse.put(2,"tom")
        sparse.put(3,"wang")
        binding?.sparse = sparse

xml代码,效果:

 <data>
        <import type="android.util.SparseArray"/>
     
	    <variable name="sparse" type="SparseArray&lt;String>"/>
</data>     
     <LinearLayout android:layout_width="match_parent"
                      android:orientation="horizontal"
                      android:layout_height="wrap_content">

            <TextView android:layout_width="wrap_content"
                      android:text="@{sparse[1]}"
                      android:padding="10dp"
                      android:layout_height="wrap_content"/>
            <TextView android:layout_width="wrap_content"
                      android:text="@{sparse[2]}"
                      android:padding="10dp"
                      android:layout_height="wrap_content"/>
            <TextView android:layout_width="wrap_content"
                      android:text="@{sparse[3]}"
                      android:padding="10dp"
                      android:layout_height="wrap_content"/>
            <TextView android:layout_width="wrap_content"
                      android:text="@{sparse[4]}"
                      android:padding="10dp"
                      android:layout_height="wrap_content"/>
        </LinearLayout>

效果如下:
效果6

  1. 字符串字面量
    两种写法如下,效果是一样的,xml代码如下:
    <TextView
                android:layout_marginTop="10dp"
                android:id="@+id/tv_age"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text='@{student.age+""}'/>

        <TextView
                android:layout_marginTop="10dp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{student.age+`反单引号`}"/>

效果如下:
效果7

  1. 资源引用
    资源文件如下:
	<!--dimen尺寸资源-->
    <dimen name="dimen_10">10dp</dimen>
    <dimen name="dimen_30">30dp</dimen>
    <!--字符串资源-->
    <string name="str_demo1">字符串Demo:%s 结尾</string>
    <string name="str_demo2">字符串Demo:我有 %d 个香蕉</string>
    <string name="str_demo3">字符串Demo:我叫 %s ,今年 %d岁了</string>
    <string name="str_demo4">字符串Demo:我叫 %1$s ,今年 %2$d岁了</string>
    <string name="str_demo5">字符串Demo:我有 %d个香蕉 ,%d个苹果</string>

xml代码如下:

 		 <TextView android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:text="paddingResources"
                  android:padding="@{student.age > 50?@dimen/dimen_10:@dimen/dimen_30}"/>
        
        <TextView android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:text="@{@string/str_demo1(`测试字符串`)}"/>

        <TextView android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:text="@{@string/str_demo2(10)}"/>

        <TextView android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:text="@{@string/str_demo3(student.name,student.age)}"/>


        <TextView android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:text="@{@string/str_demo4(student.name,student.age)}"/>

        <TextView android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:text="@{@string/str_demo5(10,20)}"/>

效果如下:
效果8

  1. 可以视同include标签,使用binding来传入参数
    传入格式:bind:参数名(item中的参数名)=值
    item的xml如下:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="student" type="com.zgj.demojetpackapp.bean.Student"/>
        <variable name="person" type="com.zgj.demojetpackapp.bean.Person"/>
    </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:padding="@dimen/dimen_10"
                  android:text="@{`学生:`+student.name+`,`+student.age}"/>
        <TextView android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:padding="@dimen/dimen_10"
                  android:text="@{`人员:`+person.name+`,`+person.birthday}"/>

    </LinearLayout>
</layout>

主xml如下:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
    <data>
        <variable name="student" type="com.zgj.demojetpackapp.bean.Student"/>
        <variable name="person" type="com.zgj.demojetpackapp.bean.Person"/>

    </data>
    <LinearLayout
            android:orientation="vertical"
            android:gravity="center_horizontal"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".databinding.TestActivity">


        <TextView android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:text="DemoMain"/>

            <include layout="@layout/item_data_binding"
                     bind:student="@{student}"
                     bind:person="@{person}"/>

    </LinearLayout>
</layout>

kotlin代码如下:

class TestActivity2 : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding :ActivityTestDataBinding2Binding= DataBindingUtil.setContentView(this,R.layout.activity_test_data_binding2)
        binding.student = Student("小张",23)
        binding.person = Person("老王","1980-10-10")
    }

}

效果如下:
效果9

注意:databinding不支持<merge>标签(merge标签写的时候就会直接报红,因为)
写的时候注意格式 bind:参数名(item中的参数名)= 值,同时导入命名空间 xmlns:bind="http://schemas.android.com/apk/res-auto",不
然会报红,但是不影响运行,不写bind:也不影响运行,但是建议按规范写。
事件绑定
原有方法调用,如onTextChanged/onClick/onLongClick等
  1. xml添加事件,代码如下(此处方法调用可以是.也可以是::,推荐后者,防止混淆),效果见后图:
<?xml version="1.0" encoding="utf-8"?>
<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="student"
                  type="com.zgj.demojetpackapp.bean.Student"/>
        <variable name="listener"
                  type="com.zgj.demojetpackapp.databinding.TestActivity.EventListener"/>
    </data>
    <LinearLayout
            android:orientation="vertical"
            android:gravity="center_horizontal"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".databinding.TestActivity">

        <TextView
                android:id="@+id/tv_name"
                android:layout_marginTop="10dp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

        <TextView
                android:layout_marginTop="10dp"
                android:id="@+id/tv_age"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

        <TextView
                android:padding="10dp"
                android:text="点击事件1"
                android:onClick="@{listener::onClick}"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

    </LinearLayout>
</layout>

2、代用代码如下:

	class TestActivity : AppCompatActivity() {

    var binding: ActivityTestDataBindingBinding? = null
    var student: Student = Student("小李", 23)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(
            this,
            R.layout.activity_test_data_binding
        )
	   binding?.student = student
	   //重点  千万不要忘记
	   binding?.listener = EventListener()
    }


    inner class EventListener{
        fun onClick(view: View){
            Toast.makeText(this@TestActivity,"点击按钮",Toast.LENGTH_SHORT).show()
        }
    }

}

此处注意方法名和入参都必须与监听器一致,如点击事件必须入参View等,如onTextChanged必须如下写法:
这样设置之后名称根据EditText的内容变化,实现监听

	  fun onTextChanged(s:CharSequence,start:Int,before:Int,count:Int){
            Log.d(Common.TAG, s.toString())
            binding?.student?.name = s.toString()
            binding?.student = student
        }

xml代码如下:

        <EditText android:layout_width="match_parent"
                  android:text="@{student.name}"
                  android:onTextChanged="@{listener::onTextChanged}"
                  android:layout_height="wrap_content"/>	

可以看到效果如下:
效果3

监听绑定,

这个比较自由,方法名、参数都可以任意写,代码如下:

  fun customClickListener(student: Student) {
            Toast.makeText(this@TestActivity, student.name, Toast.LENGTH_SHORT).show()
        }

xml代码如下(传入的参数是lambda表达式方式,此处的调用是用.):

<TextView
                android:padding="10dp"
                android:text="点击事件2"
                android:onClick="@{()->listener.customClickListener(student)}"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

实现的效果如上图,按钮2的点击事件就是这种写法,只是此处的名字已经变了,所以弹出的是及时变化的名字。

说明

1. variable声明xml中用到的变量,import导入需要用到的类
2. 导入的时候如果类名冲突,则可以使用别名alias来处理
3. 标签的class属性可以自定义生成类的类名

总结

1、关于Jetpack中的databinding限于篇幅先介绍到这里,关于其他的内容后续第二篇的时候在介绍:
	剩余的内容包含:可观察的字段和类、绑定适配器(配合RecycleView)、双向绑定等。
2、关于上述使用databinding的代码,都是经过验证运行没有问题的,如果有如环境等导致的问题欢迎探讨。
3、databinding的使用感受:
	1)有一定的方便,数据绑定也较为安全,空指针等异常都得到了避免。
	2)效率也有了提升,不用频繁遍历view,而是将view作为变量维护了起来。
	3)对于事件的处理要建一个监听类导入进来,另外参数方面也有限制(方法调用),不是很方便。
	4)报错不够精确,xml中的错误有时候能检测到(如类没有导入等),有时候提示很模糊(表达式语言有的错误检测不出来),不知道哪里
	出错了,调试起来不是太方便。
	5)总体的感觉还是不错的,有效的减少了activity的代码量,xml使用起来也比较明确,整体还是建议大家尝试的,至于项目中是否使用,
	那就要在使用之后自己做整体的评估了。
3、自勉,多思考,多实践,不要只看网上的结论,不都是正确的,有时候由于各方面的条件限制得出的特定结论是不通用的。
4、关于文档,不要只停留在看的层面上,如上述的表达式语言的使用,官网上也就简单的几句demo,自己看的时候要学会举一反三,
同时也是查漏补缺的过程。
5、自勉,多接触一些自己没有用到的东西,一方面提升自己的技能,另外一方面也是对自己思维的一个开拓,这是很不错的选择。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章