Jetpack架构组件库-DataBinding真香

前言

Data Binding是一种支持库,借助该库,您可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。

布局通常是使用调用界面框架方法的代码在 Activity 中定义的。例如,以下代码调用 findViewById() 来查找 TextView 控件并将其绑定到 viewModel 变量的 userName 属性:

    TextView textView = findViewById(R.id.sample_text);
    textView.setText(viewModel.getUserName());

以下示例展示了如何在布局文件中使用Data Binding 将文本直接分配到TextView。这样就无需调用上述任何 Java 代码。请注意赋值表达式中 @{} 语法的使用:

<TextView
        android:text="@{viewmodel.userName}" />
    

注意

如果您使用Data Biding的主要目的是取代 findViewById() 调用,请考虑改用ViewBinding。

使用过ButterKnife的都知道,目前ButterKnife作者建议切换至ViewBindng使用;在许多情况下,ViewBinding可简化实现,提高性能,提供与DataBinding相同的好处。

dataBinding 的优势

双向数据绑定

数据发生改变后,dataBinding 会自动通知 UI 刷新页面,不再需要人工绑定最新数据到 View 上。UI 改变后也能同步给数据。

减少模板代码

有了 dataBinding,从此不用再写 findViewById,setOnClickListener 等枯燥生硬的代码,大大提高工作效率。从此 Butterknife 靠边站。

释放 Activity/Fragment

以前,我们在 Activity , Fragment 或 Presenter 中计算数据再绑定到 View 组件上,导致 View 层很臃肿,现在这部分工作我们可以直接在 xml 布局文件中完成。Activity , Fragment 让它更加只关注核心业务。

数据绑定空安全

在 xml 中绑定数据它是空安全的,因为 dataBinding 在数据绑定上会自动装箱和空判断,所以大大减少了数据绑定带来的 NullpointException 问题。

在使用 dataBinding 的时候,很多同学会误以为不方便调试;
  • 现在的 databinding 在编译阶段也会有丰富的错误提示,在运行阶段,我们可以根据布局文件找到实现类,跟进去断点排查问题。

  • 如fragment_layout_my.xml布局,在编译时会生成 FragmentLayoutMyImpl.java 实现类,我们可以搜索这种类 debug 跟进解决问题。

Data Binding使用场景:

Data Binding使用前需要先引入

在app的build.gradle中加上以下代码即可,不用引用其他的依赖

android {
    ...
    dataBinding {
        enabled = true
    }
}
布局和绑定表达式

数据绑定的布局以根标记 layout 开头,后跟 data 元素和 view 根元素。如下:

注意:布局表达式应保持精简,因为它们无法进行单元测试,并且拥有的 IDE 支持也有限。为了简化布局表达式,可以使用自定义绑定适配器。

<?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
        
        <!--data 中的 user 变量描述了可在此布局中使用的属性。    -->
       <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>
    

==系统会为每个布局文件生成一个绑定类==。

1、默认情况下,类名称基于布局文件的名称,它会转换为驼峰形式并在末尾添加 Binding 后缀。

2、以上布局文件名为 activity_main.xml,因此生成的对应类为 ActivityMainBinding,==且都是ViewDataBinding的子类,所有布局对应的生成的绑定类都可以是ViewDataBinding类==

3、此类包含从布局属性(例如,user 变量)到布局视图的所有绑定,并且知道如何为绑定表达式指定值。

4、建议的绑定创建方法是在扩充布局时创建,如以下示例所示:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       //此时可以通过DataBindingUtil来设置Activity的页面布局。
       //此时会返回一个ActivityMainBinding对象。
       //这个是编译时根据xml布局文件中的数据绑定自动生成的实现类。
       ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
       User user = new User("Test", "User");
       //完成数据绑定
       binding.setUser(user);
    }

    

a、Activity 数据绑定 ( DataBinding ) :

1、DataBindingUtil类方法:

ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

2、生成的布局绑定类的inflate()方法:

ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());

    @Override
    protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       //DataBindingUtil类方法
       ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
       //生成的布局绑定类的inflate()方法
       //ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
       User user = new User("Test", "User");
       binding.setUser(user);
    }

    

b、 Fragment、ListView 或 RecyclerView 适配器中使用数据绑定 ( DataBinding )

DataBindingUtil 或 生成的布局绑定类deinflate() 方法,如以下代码示例所示:

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

    
Data Binding绑定表达式:

xml里支持使用以下表达式:

  • 算术运算符 + - / * %

  • 字符串连接运算符 +

  • 逻辑运算符 && ||

  • 二元运算符 & | ^

  • 一元运算符 + - ! ~

  • 移位运算符 >> >>> <<

  • 比较运算符 == > < >= <=(请注意,< 需要转义为 <)

  • instanceof

  • 分组运算符 ()

  • 字面量运算符 - 字符、字符串、数字、null

  • 类型转换

  • 方法调用

  • 字段访问

  • 数组访问 []

  • 三元运算符 ?:

不支持以下表达式:

  • this

  • super

  • new

  • 显式泛型调用

a、变量

1、生成的数据绑定代码会自动检查有没有 null 值并避免出现 Null 指针异常。

2、例如,在表达式 @{user.name} 中,如果 user 为 Null,则为 user.name 分配默认值 null。

3、如果您引用 user.age,其中 age 的类型为 int,则数据绑定使用默认值 0。

<!--变量给控件赋值-->
android:text="@{user.name}"

<!--控件给变量赋值(双向绑定)-->
android:text="@={user.name}"
b、Null 合并运算符(空运算符)

如果左边运算数不是 null,则 Null 合并运算符 (??) 选择左边运算数,如果左边运算数为 null,则选择右边运算数。

android:text="@{user.displayName ?? user.lastName}"
//等效于如下三目表达式
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
c、视图引用

1、表达式可以通过以下语法按 ID 引用布局中的其他视图:

2、绑定类将 ID 转换为驼峰式大小写。

3、在以下示例中,TextView 视图引用同一布局中的 EditText 视图:android:text="@{exampleText.text}"

<EditText
    android:id="@+id/example_text"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"/>
    
<TextView
    android:id="@+id/example_output"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{exampleText.text}"/>
    
d、显示隐藏控制

1、首先在 xml 的 data 节点中引用View

2、然后设置visibility

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

android:visibility="@{student.boy ? View.VISIBLE : View.INVISIBLE}"
e、事件处理

方法引用:

==android:onClick="@{handlers::onClickFriend}"==

绑定表达式可将视图的点击监听器分配给MyHandlers 类的 onClickFriend() 方法,如下所示:

    public class MyHandlers {
        public void onClickFriend(View view) { ... }
    }

    
<?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
       <data>
           <variable name="handlers" type="com.example.MyHandlers"/>
           <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}"
               android:onClick="@{handlers::onClickFriend}"/>
       </LinearLayout>
    </layout>
    

注意:

1、在表达式中,您可以引用符合监听器方法签名的方法。

2、当表达式求值结果为方法引用时,数据绑定会将方法引用和所有者对象封装到监听器中,并在目标视图上设置该监听器。

3、如果表达式的求值结果为 null,则数据绑定不会创建监听器,而是设置 null 监听器。

4、表达式中的方法签名必须与监听器对象中的方法签名完全一致。

监听器绑定:

==android:onClick="@{() -> presenter.onSaveClick(task)}"==

绑定表达式可将视图的点击事件绑定打给Presenter 类的 onSaveClick(Task task) 方法,如下所示:

    public class Presenter {
        public void onSaveClick(Task task){}
    }

    
<?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
        <data>
            <variable name="task" type="com.android.example.Task" />
            <variable name="presenter" type="com.android.example.Presenter" />
        </data>
        <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
            <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
            android:onClick="@{() -> presenter.onSaveClick(task)}" />
        </LinearLayout>
    </layout>
    

以上,我们尚未定义传递给 onClick(View) 的 view 参数。

监听器绑定提供两个监听器参数选项:您可以忽略方法的所有参数,也可以命名所有参数。

如果您想命名参数,则可以在表达式中使用这些参数。

例如,上面的表达式可以写成如下形式:

android:onClick="@{(view) -> presenter.onSaveClick(task)}"
    

或者,如果您想在表达式中使用参数,则采用如下形式:

    public class Presenter {
        public void onSaveClick(View view, Task task){}
    }

    
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"
    

监听长按事件,表达式应返回一个布尔值。

    public class Presenter {
        public boolean onLongClick(View view, Task task) { }
    }

    
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
    

注意:

1、监听器绑定这些是在事件发生时进行求值的 lambda 表达式。

2、数据绑定始终会创建一个要在视图上设置的监听器。

3、事件被分派后,监听器会对 lambda 表达式进行求值。

dataBinding 可以拓展 View 属性

以前想要给 ImageView 增加几个属性,必须要写个自定义的 ImageView 在构造函数中一顿解析。

那看看使用 dataBinding 如何拓展 View 属性。

public class CustomImageView extends ImageView{

   //需要使用BindingAdapter注解并标记在public static方法上。
    //value中的字段随意添加和方法参数一一对应即可。
   @BindingAdapter(value = {"image_url", "isCircle"})
    public static void setImageUrl(PPImageView view, String imageUrl, boolean isCircle) {
        view.setImageUrl(view, imageUrl, isCircle, 0);
    }
    //requireAll = false代表是否以下三个属性在xml中同时使用才会调用到该方法
    //为false的话,只要有一个属性被使用就能调用到该方法
    @BindingAdapter(value = {"image_url", "isCircle", "radius"}, requireAll = false)
    public static void setImageUrl(PPImageView view, String imageUrl, boolean isCircle, int radius) {
       ......
      }
   }

//在布局文件中如下使用,便能实现图片圆角和资源Url绑定的功能
 <CustomImageView
            .......
            app:image_url ="@{user.avatar}"
            app:radius="@{50}">
</CustomImageView>
BindingAdapter :

绑定适配器,是 Jetpack DataBinding 中用来扩展布局 xml 属性行为的注解;

  • 允许你针对布局 xml 中的一个或多个属性进行绑定行为扩展;

  • 这个属性可以是自定义属性,也可以是原生属性。

  • 这个扩展行为可以是简单的ViewModel属性与控件赋值绑定,也可以是关联某个控件属性的额外操作,

  • 例如在设置属性之前进行值域检查,或类型转换,或者统一处理一些事情。

< END >【Android进化之路】
【Android进化之路】
微信扫描二维码,关注我的公众号。 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值