秒懂Android开发之DataBinding详解 (part 2)

【版权申明】非商业目的注明出处可自由转载
博文地址:https://blog.csdn.net/ShuSheng0007/article/details/106308380
出自:shusheng007

系列:
秒懂Android开发之DataBinding详解 (part 1)

秒懂Android开发之DataBinding详解 (part 2)

概述

秒懂Android开发之DataBinding详解 (part 1)一文中我们已经了解并上手了DataBinding,意味着你可以干活了。 接下来就该看看其绑定规则及 BindingAdapter了,学习了DataBinding而不理解绑定原理及BindingAdapter的话,只能算是学到了皮毛。

后面行文中以DB作为DataBinding的缩写, BA作为BindingAdapter的缩写

DataBinding的绑定规则

你是否想过我们在layout文件中对TextView使用了绑定表达式后,系统是如何为其赋值的呢?以及我们可以修改TextView的哪些特性呢?例如字体颜色可以使用表达式吗?字体大小呢?背景可以吗?文字超出Textview宽度后的结束方式可以吗?等等这些问题都应该搞清楚,正是因为这些原理性的知识将程序员分为了庸俗和优秀两类。不理解原理,一旦遇到特殊需求或者异常情况就会显得束手无策。

绑定原理

DataBinding通过自动生成代码的方式帮助程序员省去了赋值操作,整体生成的代码比较多,最核心的是那个绑定类的具体实现类。例如我的layout文件生成绑定类为ActivityDataBindingBinding,那么核心类为其子类ActivityDataBindingBindingImpl

有兴趣的应该研究一下这个生成代码的实现,由于是插件生成的,可读性稍差,但仍然是可以阅读的。

绑定规则

绑定规则分3种情况

  • 由类库按规则自动选择绑定方法

此方式系统会尝试调用那个属性的setter方法,例如我们为Textview的透明度赋值 android:alpha=“@{xxx}” 那么类库就会寻找Textview里面的setAlpha(arg)。如果TextView里面有好几个setAlpha的重载,由表达式xxx的返回类型确定调用哪一个,所以这个表达式返回值的类值型很重要。

只要是Android Framwork 提供了的属性都可以直接使用data binding 的赋值表达式,这里说的属性就是你可以在layout文件中使用的那些。

data binding甚至可以绑定View中那些不存在对应属性的setter方法。 例如DrawerLayout 中存在 setScrimColor(int) 这样一个set方法,但是不存在可以在layout中使用的属性,所以没有DB的时候我们只能从代码中调用,不能在layout文件中设置。但是使用DB就可以在layout文件中进行绑定了,如下代码所示。

    <androidx.drawerlayout.widget.DrawerLayout
		...            
          app:scrimColor="@{xxx}"/>
  • 自定义绑定方法名称

从第一条我们了解到,系统会去找属性对应的setter方法, 那如果属性和对应的setter方法没有遵循传统格式怎么办呢?例如 Textview的一个属性叫 autoLink 但是其对应的设置方法叫setAutoLinkMask() 而不是叫setAutoLink() ,那系统就找不到对应的设置方法了,那怎么办呢?

使用@BindingMethod注解来对其重命名, 这个注解可以放在任何类上面,一般是放在你正在处理的类上面。下面的代码将绑定方法重命名了,通过这个注解系统就知道给autoLink赋值时调用setAutoLinkMask() 而不是setAutoLink()方法

@BindingMethods({
   @BindingMethod(type = TextView.class, attribute = "android:autoLink", method = "setAutoLinkMask"),
		...
})
public class TextViewBindingAdapter {}

对于Android Framwork的属性,即以android:开头的属性都不用管,DB已经帮忙做好了。但是如果你自己写了一个扩展Textview的自定义View, 自定义属性发生了这种问题,你就需要按照上面的方法处理了。所以你就不要找不自在了,老老实实的按照setter的语法书写!

  • 自定义赋值方法名称和逻辑

终于到了BindingAdapter出场的时候了。如果一个View的属性没有Setter方法怎么办呢?或者你想为一个View新加一个自定义属性怎么办呢?BindingAdapter 为此而生。

例如 android:paddingLeft属性就不存在相应的setter方法,那我们就可以使用BA使其可以在layout文件中绑定。Android Framework 已经为我们实现了这个绑定适配器,如下代码所示:

@BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, padding: Int) {
    view.setPadding(padding,
                view.getPaddingTop(),
                view.getPaddingRight(),
                view.getPaddingBottom())
}
   

明确了上面的绑定规则,写起代码来也就能做到心中有数了。 接下来就让我们近距离观察一下BindingAdatper吧。

BindingAdatper

通过前面的阅读,相应你已经清楚BindingAdatper就是DataBiding库用来为layout中View设置值的,包括属性和事件,的一种补充机制, 其最有魅力之处在于可以自定义名称和逻辑。

例如下面代码中对 android:text 值的绑定就是使用了DataBinding预先实现好的BindingAdapter。这里需要提一下的是如果自定义BA与前的绑定规则冲突,那么后者胜!

<TextView
    android:id="@+id/tv_girl"
	...
    android:text="@{viewModel.targetGirl.name}"
    app:wrapWithSymbol='@{viewModel.symbol}'/>

DataBinding预先为很多View实现了各种BindingAdapter,这些你可以从你projectExternal Libaries里面的
androidx.databinding:databinding-adapters:xxx找到,如下图所示:
在这里插入图片描述
我们简单来看一下TextViewBindingAdapter,这个是DataBinding 为TextView实现的BindingAdapter

    @BindingAdapter("android:text")
    public static void setText(TextView view, CharSequence text) {
        ...
        ew.setText(text);
    }

    @InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
    public static String getTextString(TextView view) {
        return view.getText().toString();
    }

可以看到对应Textview的text属性有两个BindingAdapter,一个正向的,一个反向的,反向的用于双向绑定,这个后面再说。layout文件中TextView 的text属性的绑定表达式就是由上面那个正向BindingAdapter负责赋值的。

通过BindingAdapter,我们可以为某个View使用自定义的属性,例如前面例子中的 app:wrapWithSymbol='@{viewModel.symbol}'就是我给Textview自定义的一个属性,这个绑定适配器的功能给人的感觉就和Kotlin里面的扩展函数似的,只是其扩展的对象是各种View.

如何写一个BindingAdapter

又到了躬行的时候了,不然终觉浅,让我们动手写一个简单的BA吧。其实很简单,可以分为3步:

  1. 写一个public static方法。
  2. 确定方法入参,第一个入参为要绑定的View类型,例如Textview。第二个参数为要绑定的属性的值
  3. 使用@BindingAdapter注解标记,其参数为自定义属性名称。名称可以是任意字符串,不要带命名空间,例如app:wrapWithSymbol 这种方式是不提倡的,因为在查找BA的时候命名空间是会被忽略的,还一堆警告。

完成以上3步后一个BindingAdapter就写好了,当然了,你要在方法体里面写上逻辑。举个例子?sure!

先上一个Java版本吧,比较容易理解

public class MyDataBindingAdapters {
   @BindingAdapter("wrapWithSymbol")
	public static wrapWithSymbol(TextView view, String symbol) {
        view.text = symbol+view.getText()+symbol;
    }
}

对应的kotlin版本如下,稍微复杂一点,其中@JvmStatic就是为了让kotlin编译器产生静态函数的。

object MyDataBindingAdapters {
    @JvmStatic
    @BindingAdapter("wrapWithSymbol")
    fun wrapWithSymbol(view: TextView, symbol: String) {
        view.text = "$symbol${view.text}$symbol"
    }
}

BindingAdapter 里的参数wrapWithSymbol 就是我们在layout文件中要使用的属性名称,可以任意定义。 方法的入参很重要,DB通过入参的类型去匹配BindingAdapter 。

多属性BindingAdapter

自定义BindingAdapter本来就不是太常用,用到的话大多也是单属性的,然而查看@BindingAdapter注解可以发现,它是允许传入多个值的。

@Target(ElementType.METHOD)
public @interface BindingAdapter {
    String[] value();
    boolean requireAll() default true;
}

第一个方法类型为数组,第二个为bool,表示数组中传入的属性是否都必须赋值。

例如我们可以定义如下一个包含两个属性的BindingAdapter

    @JvmStatic
    @BindingAdapter(value=["imageUrl", "error"],requireAll = true)
    fun loadImage(view: ImageView, url: String, error: Drawable) {
        Picasso.get().load(url).error(error).into(view)
    }

可以在layout文件中按照如下方式调用

<ImageView 
	...
    app:imageUrl="@{viewModel.imageUrl}" 
    app:error="@{@drawable/venueError}" />

因为我们设置了requireAll = true,所以在layout中这两个属性都必须赋值.

至此本文应该结束了,但是我们稍微扩展一下事件的绑定,如果没有兴趣的就可以散场了。

事件绑定

前面我们一直在谈论属性的赋值,没有具体谈论对事件的绑定,DB绑定事件时有两种方式:

  • Method references

这种方式要求我们的业务逻辑类里的方法签名必须与对应的Listener里面的方法在返回值和入参上保持签名一致。例如View的OnClickListener源码如下

 public interface OnClickListener {
      void onClick(View v);
  }

那么我们的handler方法签名必须与Listener里的onCLick方法一致,即返回类型为void,入参为1个View类型

class MyHandlers {
    fun onClickFriend(view: View) { ... }
}

这样我们就可以在layout文件中以这种方式绑定事件了,如下代码所示

<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="handlers" type="com.example.MyHandlers"/>
   </data>
   <LinearLayout
       ...
       >
       <TextView 
       	   ...
           android:onClick="@{handlers::onClickFriend}"/>
   </LinearLayout>
</layout>

这种方式在事件进行绑定的时候系统已经为我们生成了相应Listener的实现了。

  • Listener bindings

这种方式就比较灵活了,相应Listener的实现类在事件触发的时候才生成。

只要求处理类的方法返回值类型与对应Listener一致即可,例如还是绑定View 的Click事件,我们的处理类中的方法可以传入任意个数和类型的参数了

class MyHandlers {
     fun onClickFriend(girl: Girl) { ... }
}

我们可以使用如下绑定

 <data>
     <variable name="girl" type="com.android.example.Girl" />
     <variable name="handlers" type="com.example.MyHandlers"/>
 </data>

 <Button 
     ...
      android:onClick="@{() -> handlers.onClickFriend(girl)}" />

OnClickListener 里onClick(View v);的参数v可以省略,也可以不省略,但是要求:要不全传,要不全省

 <Button 
     ...
      android:onClick="@{(v) -> handlers.onClickFriend(girl)}" />

之所以讲上面两种事件绑定方式,主要是想谈论一下如何绑定非函数式接口的监听,换句话说,Listener里面不止一个方法。例如View 中有这么一个接口,它有两个方法,那么就不能直接绑定,怎么办呢?

    /**
     * Interface definition for a callback to be invoked when this view is attached
     * or detached from its window.
     */
    public interface OnAttachStateChangeListener {
        public void onViewAttachedToWindow(View v);
        public void onViewDetachedFromWindow(View v);
    }

答案是使用BindingAdapter,但即使使用BA也是不容易的,需要按照如下方式处理:

  1. 将原来的接口拆成多个接口,每个方法对应一个。
	@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
	public interface OnViewDetachedFromWindow {
	     void onViewDetachedFromWindow(View v);
	}
	
	@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
	public interface OnViewAttachedToWindow {
	     void onViewAttachedToWindow(View v);
	}
  1. 使用BindingAdapter多属性方式处理
@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"}, requireAll=false)
public static void setListener(View view, OnViewDetachedFromWindow detach, OnViewAttachedToWindow attach) {
	...
}

  1. 绑定
<TextView
    ...
    android:onViewDetachedFromWindow="@{() -> handlers.onClickFriend()}"/>

总结

不知不觉嘚嘚了这么多,我将自己认为正确打开DataBinding的那些事都尽量说清楚了,但正所谓师父领进门修行在个人,就像俺高中时候一个老师教育俺们的:“考清华北大的学生根本就不是教出来的,而是自己学出来的”。其实他说的很对,因为他自己只是山西临汾师范学院毕业的。

so,加油吧,少年!

下一篇就是收宫之篇了,我们谈一下双向绑定。

本文源码:AndroidDevMemo

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ShuSheng007

亲爱的猿猿,难道你又要白嫖?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值