Jetpack架构组件DataBinding之标签,注解,表达式

文章目录

    • DataBinding 注解/标签/Observable
      • 布局标签介绍
        • layout
        • data
        • variable
        • class
        • alias
        • include
      • 数据绑定包装类
        • BaseObservable
        • ObservableField
        • ObservableCollection
      • 注解
        • @Bindable
        • @BindingAdapter
        • @BindingMethods
        • @BindingConversion
        • @InverseMethod
        • @InverseBindingAdapter
        • @InverseBindingMethods
        • 总结
          • Bindable
          • BindingAdapter
          • BindingMethod/BindingMethods
          • BindingConversion
          • InverseMethod
          • InverseBindingAdapter
          • InverseBindingMethod/InverseBindingMethods
      • 表达式
        • 常用的转义字符

DataBinding 注解/标签/Observable

布局标签介绍

layout

  • layout 布局根节点必须是 ,同时layout只能包含一个View标签。

data

  • 标签的内容即DataBinding的数据. data标签只能存在一个

variable

  • 通过标签可以指定类, 然后在控件的属性值中就可以使用,通过DataBind的setXX()方法可以给variable设置数据。以下2种写法都可以。
<variable
    name="userInfo"
    type="cn.iwcode.androidjetpack.databind1.UserBean" />
<import type="cn.iwcode.androidjetpack.databind1.UserBean" />
<variable
    name="userInfo"
    type="UserBean" />

class

可以自定义DataBinding生成的类名以及路径,不常用

alias

标签如果需要导入(import)两个同名的类时可以使用alias属性(别名属性)

<import type="cn.iwcode.androidjetpack.databind1.UserBean" alias="MyUserBean"/>
<variable
    name="userInfo"
    type="MyUserBean" />

include

在include其他布局的时候可能需要传递变量(variable)值过去

<include
 layout="@layout/include_view"
 bind:userName="@{userInfo}"/>

include_view.xml

<variable
  name="userInfo"
  type="cn.iwcode.androidjetpack.databind1.UserBean"/>

数据绑定包装类

主要是实现数据改变时,视图也随之改变。

实现数据变化自动驱动 UI 刷新的方式有三种:BaseObservable、ObservableField、ObservableCollection

  • BaseObservable 需要调用notify或notifyPropertyChanged刷新视图。
  • ObservableField 数据的包装类,set数据后自动更新视图。
  • ObservableCollection 主要是提供了list和map的包装类。

BaseObservable

BaseObservable有 notifyChange() 和 notifyPropertyChanged() 两个方法刷新数据,

  • notifyChange 刷新所有的字段。
  • notifyPropertyChanged只刷新BR的静态字段,使用@Bindable注解可以生成BR的静态字段。
public class UserBean extends BaseObservable {

    //如果是 public 修饰符,则可以直接在成员变量上方加上 @Bindable 注解
    //如果是 private 修饰符,则在成员变量的 get 方法上添加 @Bindable 注解
    @Bindable
    public String name;

    public String phoneNUmber;

    public int age;

    //更新所有的字段
    public void setAll(String name, String phoneNUmber, int age) {
        this.name = name;
        this.phoneNUmber = phoneNUmber;
        this.age = age;
        notifyChange();
    }
    
    // 只更新name字段
    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(BR.name);
    }

    public void setPhoneNUmber(String phoneNUmber) {
        this.phoneNUmber = phoneNUmber;
    }
}

在调用setName方法是只更新name字段,而setAll会更新所有的字段。

ObservableField

手动去调用notifyPropertyChanged太麻烦,官方提供了ObservableField包装类。

ObservableField是对 BaseObservable 中字段的注解和刷新等操作的封装,官方原生提供了对基本数据类型的封装,例如 ObservableBoolean、ObservableByte、ObservableChar、ObservableShort、ObservableInt、ObservableLong、ObservableFloat、ObservableDouble 以及 ObservableParcelable ,也可通过 ObservableField 泛型来申明其他类型。

public class UserBean extends BaseObservable {

    public ObservableField<String> name;

    public ObservableField<String> phoneNUmber;

    public ObservableInt age;

    public void setAll(String name, String phoneNUmber,int age) {
        this.name.set(name);
        this.phoneNUmber.set(phoneNUmber);
        this.age.set(age);
    }

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

ObservableCollection

这个主要是提供对list和map的包装,如ObservableArrayMap,ObservableArrayList。
需要注意的是在布局文件中,如果用泛型<>对应的是<>,如:

ObservableArrayMap<String, String> maps = new ObservableArrayMap<>();
maps.put("firstName", "Google");
maps.put("lastName", "Inc.");
viewDataBinding.setMaps(maps);
<import type="androidx.databinding.ObservableArrayMap" />
<variable
    name="maps"
    type="ObservableArrayMap<String,String>" />

注解

@Bindable

用于数据自动更新视图,会在BR中生成一个静态字段,用于更新部分数据,如下:

public class UserBean extends BaseObservable {

    //如果是 public 修饰符,则可以直接在成员变量上方加上 @Bindable 注解
    //如果是 private 修饰符,则在成员变量的 get 方法上添加 @Bindable 注解
    @Bindable
    public String name;

    // 只更新name字段
    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(BR.name);
    }
}

@BindingAdapter

创建一个XML属性和函数, 然后在属性中进行设置数据操作会进入该函数.

图片加载时,比较方便使用这个注解。如:

<ImageView
    android:id="@+id/imageView"
    android:layout_width="80dp"
    android:layout_height="80dp"
    app:imageUrl="@{userBean.headUrl}"
    tools:srcCompat="@tools:sample/avatars[0]" />
public class ImageBindingAdapter {

    @BindingAdapter(value={"imageUrl"},requireAll = false)
    public static void bindImageUrl(ImageView view, String imageUrl){
    
        RequestOptions options = new RequestOptions()
                                .centerCrop()
                                .dontAnimate();
                                
        Glide.with(view).load(imageUrl).apply(options).into(view);
    }
}
  • 修饰的方法必须是public static。
  • 第一个参数必须是该控件。
  • 名字可以所以写,如imageUrl,imageLoad等。
  • requireAll=true,所以的参数都要填写,如果为false,没有填写的属性将为null。

@BindingMethods

androidx中的view大部分,databinding已经都给我们做了。如果是自定义的view,可能会有需要。

@BindingMethods是和@BindingMethod配合使用的,BindingMethod就是引导DataBinding使用控件自身的函数,XML属性和View中的函数关联,而BindingAdapter是提供一个新的类函数给控件使用。

看一下官网上的这个例子:android:tint属性实际对应的方法为setImageTintList(),而不是setTint(),如果databinding不做任何处理就会报错,因为在ImageView中找不到对应的setTint方法,android:tint属性对应的应该是setImageTintList(),BindingMethods就是为了能够寻找指定的setXX方法。

@BindingMethods({
   @BindingMethod(type = "android.widget.ImageView",
                  attribute = "android:tint",
                  method = "setImageTintList"),
})
  • type 控件类
  • attribute xml属性
  • method xml属性对应在控件类中的函数名

@BindingConversion

属性值进行类型转换,拿官网的例子来说:

android:background接受的属性是一个Drawable,但是给的参数确实一个int的color值,如果不转换的话,肯定是会报错的。所以需要把int的color值转换ColorDrawable。

<View
   android:background="@{isError ? @color/red : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
    return new ColorDrawable(color);
}

  • 被该注解标记的方法,被视为dataBinding的转换方法。
  • 只能修饰public static方法,且只能有1个参数。
  • 任意位置任意方法名都不限制。

@InverseMethod

  • 用于双向绑定
  • 自身参数必须和另一个函数的返回值对应

它的作用就是为某个方法指定一个相反的方法,正方法的最终参数的类型与反方法的返回值必须相同,正方法的返回值类型必须与反方法的最终参数类型相同。

比如,这里写个例子,王者段位和int互转,大家就能明白。

public class Converter {

    @InverseMethod("stringToInt")
    public static String intToString(int value){
        String valueStr;
        switch (value){
            case 1:
                valueStr = "青铜";
                break;
            case 2:
                valueStr = "白银";
                break;
            case 3:
                valueStr = "黄金";
                break;
            case 4:
                valueStr = "铂金";
                break;
            case 5:
                valueStr = "钻石";
                break;
            case 6:
                valueStr = "星耀";
                break;
            case 7:
                valueStr = "王者";
                break;
            default:
                valueStr = "";
                break;
        }
        return valueStr;
    }

    public static int stringToInt(String value){
        if (TextUtils.isEmpty(value)) return 0;

        int valueInt ;

        switch (value){
            case "青铜":
                valueInt = 1;
                break;
            case "白银":
                valueInt = 2;
                break;
            case "黄金":
                valueInt = 3;
                break;
            case "铂金":
                valueInt = 4;
                break;
            case "钻石":
                valueInt = 5;
                break;
            case "星耀":
                valueInt = 6;
                break;
            case "王者":
                valueInt = 7;
                break;
            default:
                valueInt = 0;
                break;
        }
        return valueInt;
    }
}

xml中使用

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="30dp"
    android:layout_marginStart="10dp"
    android:orientation="vertical">

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

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@{String.valueOf(userInfo.level)}"/>

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@={Converter.intToString(userInfo.level)}"/>

LinearLayout>

@InverseBindingAdapter

  • 用于双向绑定
  • @BindingAdapter配合使用
  • 方法的第一个参数必须为View类型
  • attribute 属性值(必填)
  • event 非必填, 默认值等于 AttrChanged

双向数据绑定需要二个函数

  • set (数据到视图, BindingAdapter)
  • get (视图到数据, InverseBindingAdapter)
  • onChange更新视图

看一下androidx的部分源码,在androidx.databinding.adapters.TextViewBindingAdapter :

@BindingAdapter({"android:text"})
public static void setText(TextView view, CharSequence text) {
    CharSequence oldText = view.getText();
    if (text != oldText) {
        if (text != null || oldText.length() != 0) {
            if (text instanceof Spanned) {
                if (text.equals(oldText)) {
                    return;
                }
            } else if (!haveContentsChanged(text, oldText)) {
                return;
            }
            view.setText(text);
        }
    }
}

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


 @BindingAdapter(requireAll = false, value = {"android:beforeTextChanged", "android:onTextChanged", "android:afterTextChanged", "android:textAttrChanged"})
public static void setTextWatcher(TextView view, final BeforeTextChanged before, final OnTextChanged on, final AfterTextChanged after, final InverseBindingListener textAttrChanged) {
    TextWatcher newValue;
    if (before == null && after == null && on == null && textAttrChanged == null) {
        newValue = null;
    } else {
        newValue = new TextWatcher() {
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                BeforeTextChanged beforeTextChanged = before;
                if (beforeTextChanged != null) {
                    beforeTextChanged.beforeTextChanged(s, start, count, after);
                }
            }

            public void onTextChanged(CharSequence s, int start, int before, int count) {
                OnTextChanged onTextChanged = on;
                if (onTextChanged != null) {
                    onTextChanged.onTextChanged(s, start, before, count);
                }
                // 设置的双向绑定,textAttrChanged就不是null。
                InverseBindingListener inverseBindingListener = textAttrChanged;
                if (inverseBindingListener != null) {
                    inverseBindingListener.onChange();
                }
            }

            public void afterTextChanged(Editable s) {
                AfterTextChanged afterTextChanged = after;
                if (afterTextChanged != null) {
                    afterTextChanged.afterTextChanged(s);
                }
            }
        };
    }
    TextWatcher oldValue = (TextWatcher) ListenerUtil.trackListener(view, newValue, R.id.textWatcher);
    if (oldValue != null) {
        view.removeTextChangedListener(oldValue);
    }
    if (newValue != null) {
        view.addTextChangedListener(newValue);
    }
}

我们再来看一下我们项目生成类中的部分代码,在包名.databinding中:

...

this.mboundView2androidTextAttrChanged = new InverseBindingListener() {
    // 监听回调
    public void onChange() {
        // 对应@InverseBindingAdapter的get方法
        String callbackArg_0 = TextViewBindingAdapter.getTextString(ActivityDataBind3BindingImpl.this.mboundView2);
        UserBean userInfo = ActivityDataBind3BindingImpl.this.mUserInfo;
        boolean z = true;
        if (userInfo != null) {
            ObservableField userInfoName = userInfo.name;
            if (userInfoName == null) {
                z = false;
            }
            if (z) {
                userInfoName.set(callbackArg_0);
            }
        }
    }
};

...
// 设置本文的监听。
TextViewBindingAdapter.setTextWatcher(activityDataBind3BindingImpl.mboundView2, beforeTextChanged, onTextChanged, afterTextChanged, activityDataBind3BindingImpl.mboundView2androidTextAttrChanged);
...

@InverseBindingMethods

@BindingMethods相似,不是很常用。

BindingMethods是寻找view属性对应的view的set方法。

InverseBindingMethods 是寻找view属性对应的view的get方法。

总结

以上就是DataBinding在我们日常开发中都有可能用到的注解,还有2个是@BindingBuildInfo@Untaggable是hide,自动生成Java类使用的。

是不是看完有点头晕了,其实在我们开发中最常用的也就是BindableBindingAdapter。我们在回顾一下,所有的注解:

Bindable

自动生成BR的ID,设置数据刷新视图。

BindingAdapter

设置自定义属性,与方法绑定。

BindingMethod/BindingMethods

关联view属性到view原有的setXX方法。

BindingConversion

view属性值和数据类型转换。

InverseMethod

提供一个正反方法,负责实现视图和数据之间的转换。

InverseBindingAdapter

与BindingAdapter配合使用,获取视图上的数据

InverseBindingMethod/InverseBindingMethods

关联view属性到view原有的getXX方法。

表达式

DataBinding 支持在布局文件中使用以下运算符、表达式和关键字

  • 算术 + - / * %
  • 字符串合并 +
  • 逻辑 && ||
  • 二元 & | ^
  • 一元 + - ! ~
  • 移位 >> >>> <<
  • 比较 == > < >= <=
  • Instanceof
  • Grouping ()
  • character, String, numeric, null
  • Cast
  • 方法调用
  • Field 访问
  • Array 访问 []
  • 三元 ?:

不支持

  • this
  • super
  • new

常用的转义字符

空格	 ;		 ;
<	小于号	<;		<;
>	大于号	>;		>;
&	与号	&;		&;
"	引号	";		";
‘	撇号	&apos;		';
×	乘号	×;	×;
÷	除号	÷;	÷;

参考前辈的链接

https://juejin.im/post/5a55ecb6f265da3e4d7298e9#heading-30

https://www.jianshu.com/p/bd9016418af2

你可能感兴趣的:(Android,android)