banner
jzman

jzman

Coding、思考、自觉。
github

Android Jetpack组件之DataBinding篇

前面总结了 ViewModel、LiveData 及 Lifecycle 架构组件的使用,可先阅读下面文章详细了解:

本篇主要侧重 dataBinding 的基本使用,主要内容如下:

  1. dataBinding 支持
  2. 布局文件配置
  3. 绑定数据
  4. 特殊表达式
  5. 事件绑定
  6. 自定义绑定类
  7. Others

dataBinding 支持#

使用 dataBinding 需要在 app module 下面的 build.gradle 文件中进行配置,具体如下:

//设置支持dataBinding   
dataBinding {
    enabled = true
}

布局文件配置#

Data Binding Library 会自动生成将布局中的视图和数据对象绑定所需要的类,Data Binding Library 的布局文件中以 layout 标签为根标签,然后是具体的数据元素和视图元素,此视图元素是绑定布局文件的位置,布局文件参考如下:

<?xml version="1.0" encoding="utf-8"?>
<!--dataBinding必须以layout作为根标签-->
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <!--数据对象-->
    <data>
        <variable name="user" type="com.manu.databindsample.data.User"/>
    </data>
    <!--视图元素-->
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <!--dataBinding中具体属性值的配置在"@{}"中进行配置-->
        <TextView android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name,default=姓名}"/>
    </LinearLayout>
</layout>

数据实体#

在 "@{user.name}" 中的 name 属性最终映射调用数据对象的 getter 方法,也就是 getter 方法,当然,如果数据对象中有对应的 name 方法,在没有与之对应 getter 方法的时候会调用与之同名的方法,如果两者都存在,则会优先调用与之对应的 getter 方法,参考如下:

/**
 * 数据实体
 * Powered by jzman.
 * Created on 2018/11/28 0028.
 */
public class User {
    private String name;

    public User() {
    }

    public User(String name) {
        this.name = name;
    }
    
    //两者存在优先调用
    public String getName() {
        return name;
    }
    //getter方法不存在会调用
    public String name() {
        return "name";
    }

    //...
}

绑定数据#

dataBinding 会为内个布局文件生成对应的绑定类,默认情况下,类的名称基于布局文件的名称,如布局文件名为 activity_main,则该布局文件对应的绑定类是 ActivityMainBinding,该类包含数据对象到布局文件的所有绑定,那么如何绑定数据和视图呢,在 Activty 中绑定方式如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //生成绑定类
        ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
        //绑定视图与数据
        User user = new User("张三");
        binding.setUser(user);
    }
}

在 Fragment 中绑定方式如下:

//inflate方法
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    FragmentOneBinding oneBinding = DataBindingUtil.inflate(inflater,R.layout.fragment_one,container,false);
    User user = new User("小明");
    oneBinding.setUser(user);
    return oneBinding.getRoot();
}
//bind方法
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
    View view = inflater.inflate(R.layout.fragment_one,container,false);
    FragmentOneBinding oneBinding = FragmentOneBinding.bind(view);
    User user = new User("小明");
    oneBinding.setUser(user);
    return view;
}

其他布局的绑定方式基本是都是使用某个生成的绑定类的 inflate 方法和 bind 方法就可以完成。

特殊表达式#

  • 三目运算符简化
//完整写法
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
//简写
android:text="@{user.displayName ?? user.lastName}"
  • 空指针异常处理

生成的绑定类会自动检查 null 值以避免 NullPointerException,在表达式 @ {user.name} 中,如果 user 为 null,则为 user.name 分配其默认值 null。 如果引用 user.age,其中 age 的类型为 int,则数据绑定使用默认值 0。

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

    <data>

        <import type="java.util.Map" />

        <import type="java.util.List" />

        <!--Map-->
        <variable
            name="map"
            type="Map&lt;String,String>" />

        <variable
            name="key"
            type="String" />

        <!--List-->
        <variable
            name="list"
            type="List&lt;String>" />

        <variable
            name="index"
            type="int" />
    </data>
    <!--注意Map和List取值方式-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{map.key}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{list[0]}" />

    </LinearLayout>
</layout>

对于 Map 类型的数据可以在表达式 @{} 中使用 map.key 来获取 Map 集合中 key 对应的 value 值,List 类型的数据直接使用索引来取值,此外在 variable 标签中使用到的 < 要进行转义,及使用 < 来代替 <,否则报错如下:

> Error: 与元素类型 "variable" 相关联的 "type" 属性值不能包含 '<' 字符。
  • @{} 表达式中使用字符串

如何在 @{} 表达式中使用字符串而不是字符串变量呢,有两种方式,具体如下:

<!--使用单引号-->
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text='@{map["key"]}' />
<!--使用后引号-->
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{map[`key`]}" />
<!--在@{}中可以使用字符串资源-->
<TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{@string/app_name}"/>

事件绑定#

使用 databinding 时可以采用方法引用或监听绑定的方式来设置事件监听,这两者的区别是前者的事件监听器是在数据绑定时创建的,而后者是在事件触发时绑定。

  • 方法引用

事件可以直接绑定在事件处理方法上,与普通的 android 属性相比较,这种配置方式会在编译时进行相关处理,如果该方法不存在或该方法签名不正确,则会收到编译时错误。首先创建一个事件处理方法如下:

/**
 * Powered by jzman.
 * Created on 2018/11/30 0030.
 */
public class MyHandler {
    /**
     * @param view
     */
    public void onClickEvent(View view){
        Toast.makeText(view.getContext(), "click me", Toast.LENGTH_SHORT).show();
    }
}

然后,在对应的布局文件中配置具体的 onClick 等事件,这里以 onClick 事件为例,具体如下:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="handler"
            type="com.manu.databindsample.handler.MyHandler"/>
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <!--第一种方式-->
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="click me"
            android:onClick="@{handler::onClickEvent}"/>
        <!--第二种方式-->
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="click me"
            android:onClick="@{handler.onClickEvent}"/>
    </LinearLayout>
</layout>

最后,在对应的 Activity 中设置数据对象 handler 即可,具体参考如下:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ActivityEventHandlerBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_event_handler);
    binding.setHandler(new MyHandler());
}

这样通过方法引用事件就成功绑定了。

  • 监听绑定

这种方式是在事件发生时创建事件监听器,相较方法引用可以传递自定义参数在事件回调中,首先,创建一个事件回调方法如下:

/**
 * 监听绑定
 * Powered by jzman.
 * Created on 2018/12/3 0003.
 */
public class MyPresenter {
    private Context mContext;

    public MyPresenter(Context mContext) {
        this.mContext = mContext;
    }

    public void onClickEvent(User user) {
        Toast.makeText(mContext, user.getName(), Toast.LENGTH_SHORT).show();
    }
}

然后,在对应的布局文件中配置具体的 onClick 等事件,这里以 onClick 事件为例,具体如下:

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

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

        <variable
            name="presenter"
            type="com.manu.databindsample.handler.MyPresenter" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{() -> presenter.onClickEvent(user)}"
            android:text="click me 3" />
    </LinearLayout>
</layout>

最后,在对应的 Activity 中设置数据对象 presenter 即可,具体参考如下:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ActivityEventHandlerBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_event_handler);
    binding.setUser(new User("android"));
    binding.setPresenter(new MyPresenter(this));
}

这样通过事件监听事件就成功绑定了,在上面 xml 中调用事件方法时,可以在配置当前 View,具体如下:

<Button
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:onClick="@{(view) -> presenter.onClickEvent(user)}"
    android:text="click me 3" />

则对应的事件回调方法如下:

public class MyPresenter {
    public void onClickEvent(View view, User user){}
}

此外,也可以在事件绑定时使用三目运算符,此时可将 void 作为操作符使用,使用方式参考如下:

android:onClick="@{(v) -> v.isVisible() ? presenter.doSomething() : void}"

自定义绑定类#

从前面可知,默认状态下绑定类名称是由布局文件名称决定,那么如何自定义绑定类呢,在布局文件 data 标签上使用 class 属性指定自定义的绑定类名即可,当然也可以在自定义类名前面添加完成的包路径,参考如下:

<!--自定义绑定类-->
<data class="com.manu.test.CustomBinding">
    <variable name="user" type="com.manu.databindsample.data.User"/>
</data>

Others#

在 databinding 中使用 import 关键字导入相关的类,java.lang.* 下面的相关类默认自动导入,如果有相同名字的 View 可以使用使用 alias 来区分,参考如下:

<import type="android.view.View"/>
<import type="com.manu.View"
        alias="MView"/>

使用 variable 关键字定义要在 xml 布局中使用的变量,如果使用了 include 布局,则要使用 bind 绑定 include 包含的布局与主布局使用同样的变量,创建一个 include 包含的布局 test_layout.xml 文件,具体如下:

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

    <data>
        <variable
            name="userTest"
            type="com.manu.databindsample.data.User" />
    </data>

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

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{`this is include content...`+userTest.getName(),default=user}" />
    </LinearLayout>
</layout>

然后,在主布局中引用这个布局,具体如下:

<?xml version="1.0" encoding="utf-8"?>

<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.manu.databindsample.data.User" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
         <!--bind绑定变量-->
        <include
            layout="@layout/test_layout"
            bind:userTest="@{user}" />
    </LinearLayout>
</layout>

这样通过 bind 就绑定了两个布局中使用到的 User 类型的变量,使得两个布局中使用的变量是同一个变量,此外,databinding 不支持 merge 标签,下篇继续 Binding adapters 的介绍。

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。