DataBinding_1

18 篇文章 0 订阅
5 篇文章 0 订阅

1、依赖

在app的build.gradle文件中(模块build)中添加如下代码,即可

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

但是如果 使用了kotlin进行编码 顶部要依赖kotlin插件,否则编译失败

apply plugin: "kotlin-kapt"

2、数据绑定

数据绑定数据源驱动View 所以其布局文件是和普通的布局文件是不同的,其根元素 是固定的layout 标签 其内部包含两个标签 从上而下是 1,数据标签 2,根布局(就是之前的整个普通布局),示例如下:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
	//数据标签 包含驱动视图所需要的所有数据
   <data>
       <variable name="user" type="com.example.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>

假如不使用数据绑定 则布局文件是:

	<?xml version="1.0" encoding="utf-8"?>
   <LinearLayout
	   xmlns:android="http://schemas.android.com/apk/res/android"
       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>

2.1 data标签

data标签是引入 驱动VIew所需要的所有 数据
比如:

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

上面是定义一个变量(variable标签定义一个变量), 这个变量的类型(type)是 com.example.User

variable是定义一个变量, type 是声明这个变量的类型 variable可以定义你使用的几乎所有的类型 而且variable可以定义多个如下所示:

<data>
    <import type="android.graphics.drawable.Drawable"/>
    <variable name="user" type="com.example.User"/>
    <variable name="image" type="Drawable"/>//引用数据类型
    <variable name="note" type="String"/>//引用数据类型
 	<variable name="age" type="int" />//基础数据类型
</data>

import功能:

type是定义一个变量的类型,这个类型可以是类型全地址,比如com.example.User 但是也是可以写成 type =“User”,但是这时我们需要先将User类导入到data标签内,就像普通的类导入一样:如下

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

如果我们在导入的过程发现类的名称冲突时 比如:

<import type="android.view.View"/>
<import type="com.example.real.estate.View"/>
<variable name="view" type="View"/>

这时我们根本无法清楚的知道 变量view到底是那个类型 ,这时我们可以使用重命名(alias)将导入的某个类改名为另外一个名称

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

这时我们就可以知道清楚的知道,变量view的类型是Vista也就是com.example.real.estate.View

总结:

  1. improt可以导入设置View所需要的所有类型
  2. 如果所导入的类存在类名相同(仅仅是类名而不是全地址),这时我们可以使用 alias属性进行重命名区分

Variables&type 功能:

(1) Variables标签是定义一个变量,而且可以定义多个 比如:

<data>
    <import type="android.graphics.drawable.Drawable"/>
    <variable name="user" type="com.example.User"/>
    <variable name="image" type="Drawable"/>
    <variable name="note" type="String"/>
	<variable name="index" type="int"/>
</data>

(2)上面仅仅是定义了 基础数据类型和引用类型但是没有定义集合 因为集合的定义方式有些特殊,因为集合的泛型会使用到<>,这个和标签<>会造成冲突,为了解决这种冲突 集合的< 要使用转义字符 &lt; 代替 ,比如:

<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List&lt;String>"/>
    <variable name="sparse" type="SparseArray&lt;String>"/>
    <variable name="map" type="Map&lt;String, String>"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
</data>

常见的转义字符

    "		   &quot;
    &		   &amp;
    <		   &lt;
    >		   &gt;
    不断开空格  &nbsp;

(3) databinding为variable声明的每个变量都声明了set和get方法 而且get都有默认值 引用类型为null, int为0,布尔值为false,等等,
而且 如果调用者为null,则直接返回null不在进行下一步调用,比如user.name 如果user为null则直接返回null,所以使用DataBinding 避免了空指针

(4)variable 会默认的生成一个context 变量 这个变量来源于rootView的getContext() 而且这个默认生成的变量context会覆盖data中声明的context变量(如果你声明了的话),换言之,1,我们不能再data中定义context变量名 因为其会被覆盖,2.我们可以在布局文件中使用context来获取一些系统的方法 比如:

android:text="@{context.getApplicationInfo().toString()}"

2.2 绑定数据

前面已经说明了如何声明所需要的数据变量,现在就需要再布局文件中使用这些变量达到绑定数据的目的

只要布局文件中有属性可以设置的都可以通过databinding来实现

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.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}"/> //设置text属性
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"/>//这里设置text属性
   </LinearLayout>
</layout>

布局文件设置属性都是:属性="属性值"的形式 但是databinding是通过:属性="@{表达式}"的方式实现的,这里的表达式的最终值就是属性值,比如:

//设置可见性
 android:visibility="@{user.gender==1 ? View.VISIBLE : View.GONE}"
//设置背景
 android:background="@{@color/colorImg }"
//设置文本
 android:text="@{String.valueOf(user.age)}"
//设置文本
android:text="@{user.firstName}"/> //设置text属性

这里的表达式 包括:

1,资源内容

我们可以在表达式中直接调用res下的资源 比如color.xml string.xml dimen.xml 如下

 android:text="@{@string/age}"

 android:background="@{@color/colorImg}"

如果我们在引用资源的时候需要写入参数 这时需要:

android:text="@{@string/info(age,gender)}"

完整的xml

string.xml(string资源文件)
...
 <string name="info">%1$d岁,性别%2$s</string>
...

activity_test.xml(布局文件)

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
                name="age"
                type="int" />
        <variable
                name="gender"
                type="String" />
    </data>
    <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        <androidx.appcompat.widget.AppCompatTextView
                android:layout_width="wrap_content"
                android:text="@{@string/info(age,gender)}"
                android:layout_height="wrap_content"/>
    </LinearLayout>
</layout>

但是还有一些类型需要特殊的表示 如下图:
特殊资源引用表示

2,表达式特性

databinding表达式支持很多表达式特性比如:(以下的text均是指:TextView的text属性)

(1),算数运算:+ - / * % 与括号()

 android:text="@{String.valueOf((8+5-7%3)*3/2)}"

(2),字符串拼接

 android:text='@{"hello"+" world"}'

(3),逻辑运算 &&(需要转换成转义字符&amp;&amp;) ||

&&运算 需要转换成转义字符
android:text="@{true &amp;&amp;false ? String.valueOf(3) :String.valueOf(5)}"
结果是5

|| 运算
android:text="@{true || false ? String.valueOf(3) :String.valueOf(5)}"
结果是3

(4),二进制运算 &(需要转换成转义字符&amp;) | ^

 android:text="@{String.valueOf(2&amp;3)}"
 结果 2

android:text="@{String.valueOf(2|3)}"
结果 3

android:text="@{String.valueOf(2^3)}"
结果 1

(5) 一元运算 + - ! ~

正号 +
android:text="@{String.valueOf(+3)}"
结果 3
负号 -
android:text="@{String.valueOf(-3)}"
结果-3
android:text="@{String.valueOf(!3)}"
非!
android:text="@{String.valueOf(!true)}"
结果false
取反 ~
android:text="@{String.valueOf(~5)}"
结果-6

(6) 位移运算 >> >>> <<

(7) 比较运算 == > < >= <= 不过 < 需要使用转义字符&lt;代替

**(8) 三元运算符 ? : **

  表达式? result1  : result2 如果表达式为true 则整个表达式的结果是 result1  否则是result2
 android:text="@{true ? String.valueOf(5) : String.valueOf(3)}"
 结果 5

(9) 判断是否为空的表达式 ??

result1 ?? result2 如果result1为null 则取result2值  否则取result1的值

android:text="@{null??String.valueOf(3)}"
结果为:3

(10) 可以使用 instanceof 判断是否是某个类型 可以使用 cast 进行类型转换

(11) 可以调用某个类的方法

(12) 可以访问类的成员变量

**(13) 可以使用[] **

(14)可以使用 字符 字符串 null等

(15),但是需要注意的是 表达式中不能使用 this super new 以及显示通用调用

3 特殊用法

(1) 前面我们说好 可以导入集合以及使用集合,但是我们需要注意 导入集合时<需要使用转义字符&lt;,而且在调用集合的值的时候 我们需要使用[] 而不是get,比如:

<data>
<import type="android.util.SparseArray"/>
<import type="java.util.Map"/>
<import type="java.util.List"/>
<variable name="list" type="List&lt;String>"/>
<variable name="sparse" type="SparseArray&lt;String>"/>
<variable name="map" type="Map&lt;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]}"

(2) TextView的一些注意事项

在布局文件中 TextView不能直接设置bean类的int属性 否则报错

`android.content.res.Resources$NotFoundException: String resource ID #0x14`

正确做法是:先将int转换为string类型

  android:text="@{Integer.toString(user.age)}"
  android:text="@{String.valueOf(user.age)}"

Binding 拼接字符串 不支持中文硬编码拼接 应该在string.xml中定义

官方提供了一种方式 
android:transitionName='@{"image_" + id}' //运行没问题 (使用英文拼接字符串)
android:transitionName='@{"图片" + id}' //运行报错(使用中文拼接字符串) 错误信息如下:
错误信息:
Caused by: com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException: 3 字节的 UTF-8 序列的字节 3 无效

解决方式:在string.xml中定义:
 <string name="age">岁</string>
 android:text='@{user.age+@string/age}'
或者:
  <string name="ageDes">%1$d岁</string>
 android:text='@{@string/ageDes(user.age)}'

4,事件处理

dataBinding 不仅仅可以设置属性值 还可以处理事件 而处理事件分为两种方式

  1. 方法引用

  2. 监听器绑定

     如下是对View的onClick事件进行绑定处理的写法
    
     TestActivity.kt
     ...
       fun viewClick(view: View) {
         Log.d("===","===viewClick=")
     }
     ...
     
     <data>
     ...
     <variable
                 name="activity"
                 type="com.wkkun.jetpack.TestActivity" />
     </data>
     //方法引用		
     <androidx.appcompat.widget.AppCompatTextView
                 android:layout_width="wrap_content"
                 android:layout_height="30dp"
                 android:onClick="@{activity::viewClick}" //方法引用
                 android:text="@{@string/info(age,gender)}" />
     //监听器绑定
        <androidx.appcompat.widget.AppCompatTextView
             android:layout_width="wrap_content"
             android:minWidth="30dp"
             android:layout_height="30dp"
             android:background="#f00"
             android:onClick="@{(view)->activity.viewClick(view)}" //监听器绑定
             android:text="@{null??String.valueOf(3)}" />
    

4.1方法引用

方法引用实现方式是:当我们设置一个方法引用时,在编译期间 dataBinding 会为我们直接生成一个 继承自OnClickListener的OnClickListenerImpl类,如下:

   // Listener Stub Implementations
public static class OnClickListenerImpl implements android.view.View.OnClickListener{
    private com.wkkun.jetpack.TestActivity value;
    public OnClickListenerImpl setValue(com.wkkun.jetpack.TestActivity value) {
        this.value = value;
        return value == null ? null : this;
    }
    @Override
    public void onClick(android.view.View arg0) {
        this.value.viewClick(arg0); //1
    }
}

当我们在绑定数据的时候,执行如下两个操作

1,生成OnClickListenerImpl实例,注意这里dataBinding自动做了防空判断 如果变量为null 最后view设置事件时会设置为null 如:view.setOnClickListener(null)

OnClickListenerImpl activityViewClickAndroidViewViewOnClickListener =null
...
 if (activity != null) {
       // read activity::viewClick
        activityViewClickAndroidViewViewOnClickListener = (((mActivityViewClickAndroidViewViewOnClickListener == null) ? (mActivityViewClickAndroidViewViewOnClickListener = new OnClickListenerImpl()) : mActivityViewClickAndroidViewViewOnClickListener).setValue(activity));
    }
2,给View设置onClick事件
 this.mboundView1.setOnClickListener(activityViewClickAndroidViewViewOnClickListener);

总结:方法绑定 是在编译期间为我们创建了监听事件 并且如果我们传入的方法的签名和 监听器的签名不一致 会直接报错(上述的代码标记1处)

4.2监听器绑定

监听器绑定是在 事件触发的时候,生成绑定监听器 并触发 dataBinding 中我们写入的逻辑表达式

如下监听器绑定:
<androidx.appcompat.widget.AppCompatTextView
            android:layout_width="wrap_content"
            android:minWidth="30dp"
            android:layout_height="30dp"
            android:background="#f00"
            android:onClick="@{(view)->activity.viewClick(view)}" //监听器绑定
            android:text="@{null??String.valueOf(3)}" />
dataBinding 最终生成的编译代码是:
1首先生成 OnClickListener的实现类 该类会最终设置给View 
	public final class OnClickListener implements android.view.View.OnClickListener {
    final Listener mListener;
    final int mSourceId;
    public OnClickListener(Listener listener, int sourceId) {
        mListener = listener;
        mSourceId = sourceId;
    }
    @Override
    public void onClick(android.view.View callbackArg_0) {
        mListener._internalCallbackOnClick(mSourceId , callbackArg_0);
    }
    public interface Listener {
        void _internalCallbackOnClick(int sourceId , android.view.View callbackArg_0);
    }
}

2,生成监听类 生成的DataBinding(该类后面会说到)类继承 Listener 并实现_internalCallbackOnClick方法
 mCallback2 = new com.wkkun.jetpack.generated.callback.OnClickListener(this, 1);
实现的_internalCallbackOnClick方法如下:
    // callback impls
public final void _internalCallbackOnClick(int sourceId , android.view.View callbackArg_0) {
    // localize variables for thread safety
    // activity != null
    boolean activityJavaLangObjectNull = false;
    // activity
    com.wkkun.jetpack.TestActivity activity = mActivity;
    activityJavaLangObjectNull = (activity) != (null);
    if (activityJavaLangObjectNull) {
		//如果变量不为空 则只需变量的方法 
        activity.viewClick(callbackArg_0);
    }
}

3,给View设置监听类
this.mboundView2.setOnClickListener(mCallback2);

上面的逻辑 总的来说是 生成2个监听器 一个是监听View的分发事件A 另一个B是包装我们填入的方法 当A触发事件时 再触发B的事件.
也就是说 监听器绑定是在事件触发的时候绑定 而不是在编译器期间绑定,而且监听器绑定必定会创建2个监听类对象

说明: 在监听器绑定中 android:onClick="@{(view)->activity.viewClick(view)}" //监听器绑定 activity的viewClick的方法签名可以是任意的 比如:

	android:onClick="@{(view)->activity.viewClick()}
	//假设task是传入的其他变量
	android:onClick="@{(view)->activity.viewClick(task)}
	//假设A B C ...是传入的其他变量
	android:onClick="@{(view)->activity.viewClick(A,B,C,..)}
	而且(view)->{} 中的变量名任意 也可以不写  这个是监听的VIew事件传递的参数 可以接受也可以不接受

总结:

  1. 方法绑定是在编译期间完成,方法签名要和事件的方法签名完成,而监听器绑定是在事件触发的时候完成,监听器绑定中的方法的方法签名不做限制
  2. 方法绑定最多只会创建一个监听类,而监听器绑定 每次必定创建2个监听类
  3. 方法绑定,和监听器绑定都做了变量的防空判断

3,生成DataBinding

dataBinding为每一个布局文件生成一个绑定类,该类的命名规则是 首先将布局文件的名称取消下划线 再按照驼峰命名规则命名 最后后面拼接Binding,比如布局文件的名称是activity_main.xml,因此生成的相应类是ActivityMainBinding,该类包含从布局属性(例如,用户变量)到布局视图的所有绑定,并且知道如何为绑定表达式赋值,上面我们说到监听器绑定默认实现监听器的类就是这个DataBinding类

生成DataBinding类的方式有两种:

方式一,使用 DataBindingUtil

activity.kt 

 override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val userInfoBinding =
        DataBindingUtil.setContentView<ActivityUserInfoBinding>(this,R.layout.activity_user_info)
    userInfoBinding.user = UserBean("我是用户名",20,"男","https://www.baidu.com")
}

方式二 Binding生成类 然后调用 inflate方法

activity.kt 

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val userInfoBinding =ActivityUserInfoBinding.inflate(layoutInflater)
    setContentView(userInfoBinding.root)
    userInfoBinding.user = UserBean("我是用户名",20,"男","https://www.baidu.com")
}
userInfoBinding.root//是指Binding生成类 绑定的根布局

上面是生成Activity的DataBinding类, 在生成Fragment或者是List RecyclerView的DataBinding类如下:

val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false)
// or
val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)

如果我们想要为DataBinding类的变量赋值 则直接 DataBinding.变量名 = … 即可比如:

 activityUserInfoBinding?.user = user 
 activityUserInfoBinding?.age = 10086

DataBinding类会绑定所有有ID的视图,所有如果我们想要引用某个视图可以直接引用

<androidx.appcompat.widget.AppCompatTextView
            android:id="@+id/tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            />

activityUserInfoBinding.tv

如果我们想要引用根布局 则直接 activityUserInfoBinding.root 即可,根布局不需要ID也可以引用

现在我们已经知道如何生成DataBinding,如何编写 布局文件 如何为控件赋值 则简单的写个实例:

TestActivity.kt

	class TestActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val activityTestBinding =
            DataBindingUtil.setContentView<ActivityTestBinding>(this, R.layout.activity_test)
        activityTestBinding.activity = this
        activityTestBinding.age = 10086
        activityTestBinding.data= Data("小明")
        activityTestBinding.gender="男"
    }
    fun viewClick(view: View) {
        Log.d("==","点击了我")
    }
}

布局文件

	<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
                name="age"
                type="int" />
        <variable
                name="gender"
                type="String" />
        <variable
                name="data"
                type="com.wkkun.jetpack.bean.Data" />
        <variable
                name="activity"
                type="com.wkkun.jetpack.TestActivity" />
    </data>
    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
        <androidx.appcompat.widget.AppCompatButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:onClick="viewClick"
                android:text="@{String.valueOf(age)}" />

        <androidx.appcompat.widget.AppCompatButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:onClick="@{(view)->activity.viewClick(view)}"
                android:text="@{null??String.valueOf(3)}" />

        <androidx.appcompat.widget.AppCompatButton
                android:text="@{data.name}"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                />
    </LinearLayout>
</layout>

Bean类

public class Data {
public Data(String name){
    this.name = name;
}
private String name;

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

}

显示结果是:不截图了

10086
3
小明

这里是简单实现了 从数据到View的显示,但是我们并没有实现绑定,绑定是一个变化,另一个也跟着变化,但是上面的代码显然无法实现 数据变化 View也跟着变化 View变化数据也跟着变化的,

如果要实现上面的绑定 则需要使用Observable 实现单项绑定和双向绑定 请看下篇Databinding2
Databinding2-传送门

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值