Android DataBinding自定义数据双向绑定 笔记

在学习dataBinding的数据双向绑定时,针对自定义特性的双向数据绑定看的一头雾水,现在有了大致了解,记录一下

1、XML中使用方式:

单向数据绑定

    <CheckBox
        android:id="@+id/rememberMeCheckBox"
        android:checked="@{viewmodel.rememberMe}"
        android:onCheckedChanged="@{viewmodel.rememberMeChanged}"
    />

双向数据绑定,在单向上加上=

    <CheckBox
        android:id="@+id/rememberMeCheckBox"
        android:checked="@={viewmodel.rememberMe}"
    />

相信大家对于如何在xml总定义双向数据绑定还是很清楚,但是对于如何在代码中使用及操作就有点懵了,下面说下流程。

2、代码中使用方式(分2种情况)

情况一、数据绑定是使用在View的系统属性上,且view中有提供setter方法

    <CheckBox
        android:id="@+id/rememberMeCheckBox"
        android:checked="@={viewmodel.rememberMe}"//android:checked属性是CheckBox类中有的属性,且类中有提供setter方法
    />

情况二、view有相关属性,但是类中没有提供setter方法,或数据绑定是使用在View的自定义属性上

    <CheckBox
        android:id="@+id/rememberMeCheckBox"
        android:paddingLeft="@={viewmodel.paddingLeft}"//android:paddingLeft属性是CheckBox类中有的属性,但是没有提供setter方法
    />
    
    <EditText
        android:id="@+id/time"
        app:myText="@={viewmodel.myText}"// app:time属性在EditText中不存在
    />

2.1 情况一(数据是绑定在系统属性上的)

​ 针对这种使用的是view存在的属性且view中有提供setter方法,只需要在数据类ViewModel类上做操作就可以了,很简单

    public class ViewModel extends BaseObservable {
        private boolean rememberMe;
        private String text;

        @Bindable
        public Boolean getRememberMe() {
            return rememberMe;
        }

        public void setRememberMe(Boolean value) {
            // 避免死循环.
            if (rememberMe != value) {
                rememberMe = value;
                // 通知界面更新.
                notifyPropertyChanged(BR.remember_me);
            }
        }
        
        @Bindable
        public String getText() {
            return text;
        }

        public void setText(String text) {
            Log.d(TAG, "情况1:源数据更改,代码中调用这个setter方法\n情况2:用户改变界面的值,触发反向设置值步骤3,在onChange中调用@Bindable修饰过的属性的setter方法,设置最新值");
            if (TextUtils.equals(text, this.text)) {
                return;
            }
            this.text = text;
            notifyPropertyChanged(BR.text);
        }     
    }
  • ​ 首先让数据类继承BaseObservable,这样数据类就具有了在数据更新时,可以通知界面的方法notifyPropertyChanged(BR.xx)

  • ​ 针对要操作的绑定属性rememberMe,提供setter和getter方法;

  • ​ 然后在属性的getter方法上添加注解@Bindable,系统会在BR类中生成一个条目BR.remember_me,下次如果rememberMe的值更新时,调用notifyPropertyChanged(BR.remember_me)就可以使界面更新;

  • 当界面用户操作导致CheckBoxcheck变动时,系统会调用提供的setter方法。

2.2 情况二(数据是绑定在自定义属性上的)

​ 数据类ViewModel类的设置,和情况一的一样设置,记得参考上面情况一设置数据类。

情况二,还需要针对xml中自定义的属性,添加解释类和方法

2.2.1 先说几个个人理解,方便后续说明

(1)如果我们在xml中使用自定义属性,在当前view中不存在setter方法,例如:

    <EditText
        android:id="@+id/time"
        app:myText="@={viewmodel.text}"
    />

​ 很明显app:myText=这个属性,EditText中是不存在setMyText()方法的,出现这种情况的时候,编译器编译时就会报错,解决方法就是,告诉数据绑定模块,如果没有setter方法时,应该使用我们自己提供的方法。

(2)@BindingAdapter("属性名")

当给方法添加这个注解后,表示正向绑定时,设置的值应该通过这个方法操作,

    // 负责解析xml中app:myText的属性,并调用下面这个方法
    @BindingAdapter("app:myText")
    public static void setText(EditText view, String text) {
        if (TextUtils.equals(text,"1")) {
            view.setBackground(new ColorDrawable(0xffff0000));
        }else {
            view.setBackground(new ColorDrawable(0xff0000ff));
        }
    }

(3)@InverseBindingAdapter("属性名")

当给方法添加这个注解后,表示反向绑定时,会调用这个方法,

    @InverseBindingAdapter(attribute = "app:myText")
    public static String getText(EditText view) {
        return view.getText().toString();
    }

(4)当我们在xml中使用

    <EditText
        android:id="@+id/time"
        app:myText="@={viewmodel.text}"
    />

​ 时

app:myText="@={viewmodel.text}"

系统(编译器,后续统称为系统吧)其实为我们分解成了下面这两部分

app:myText="@{viewmodel.text}"
app:myTextAttrChanged="@{inverseBindingListener}"//inverseBindingListener是系统生成的,我们现在不用管它是哪儿来的

看到上面这2个属性,大家应该就明白了,类似我们开头数据单向绑定,

app:myText="@{viewmodel.text}"是用来做数据正向绑定的,app:myTextAttrChanged="@{inverseBindingListener}"是用来做数据反向绑定的,一个回调对象

2.2.2 步骤及解释

​ 通过2.2.1第4点的说明,我们看到系统帮我们生成的app:myTextapp:myTextAttrChanged在EditText中是不存在的属性,肯定也没有setter方法,所以根据2.2.1第1点,此时就需要我们自定义方法,来让系统使用我们提供的方法

​ a.先解决数据正向绑定的问题,用2.2.1第2点的注解便可:

    // 负责解析xml中app:myText的属性,并调用下面这个方法
    @BindingAdapter("app:myText")
    public static void setText(EditText view, String text) {
        if (TextUtils.equals(text,"1")) {
            view.setBackground(new ColorDrawable(0xffff0000));
        }else {
            view.setBackground(new ColorDrawable(0xff0000ff));
        }
    }

    // 负责解析xml中app:myTextAttrChanged的属性,并调用下面这个方法
    @BindingAdapter("app:myTextAttrChanged")
    public static void setListener(EditText view, final InverseBindingListener listener)    {
        //内容需要在反向绑定数据时,再写。
    }
    

​ 通过上面这两个方法,算是完成了数据的正向绑定,

app:myText的方法,其实就是判断给定的text是否等于1,然后给EditText设置不同的背景色;

app:myTextAttrChanged的方法,就是告诉系统什么时候,触发回调;

​ b.数据反向绑定:

​ 反向绑定其实,就是把用户在界面的改变,设置回我们自己的数据类上,本例中,就是把用户输入的文本,设置回viewmodeltext属性上

​ 由于我们已经给数据类ViewModel中的text属性添加了@Bindable注解,并且上面提到的系统自动生成的InverseBindingListener中的onChange()方法是会帮我们调用text属性的setter方法,所以我们只需要告诉数据绑定系统在什么时候设置和需要设置什么值便可。

​ ① 在什么时候设置,由于是当用户输入时,我们就要同步获取输入内容,所以我们肯定是要在EditText上添加一个文本变化监听器,用户只要一输入,我们通过监听器就能马上知道,所以我们可以在我们自定义的app:myTextAttrChanged方法中这样写:

    // 负责解析xml中app:myTextAttrChanged的属性,并调用下面这个方法
    @BindingAdapter("app:myTextAttrChanged")
    public static void setListener(EditText view, final InverseBindingListener listener) {
        if (listener != null) {
            view.addTextChangedListener(new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

                }

                @Override
                public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                    Log.d(TAG, "用户改变界面的值,触发反向设置值步骤1,准备调用自动生成的onChange");
                    listener.onChange();
                }

                @Override
                public void afterTextChanged(Editable editable) {

                }
            });
        }
    }

​ 当EditText内容有变化时,就会调用系统生成的InverseBindingListeneronChange()方法,然后系统在onChange()方法中就会自动把值设置给数据类对象ViewModel,至于值从哪儿来,我们看下面的②;

​ ② 需要设置什么值,其实在InverseBindingListeneronChange()方法中,系统还会调用@InverseBindingAdapter("属性名")修饰的方法,即2.2.1第3点的方法,所以我们只需要把我们要设置的值,通过这个方法返回便可:

    @InverseBindingAdapter(attribute = "app:myText")
    public static String getText(EditText view) {
        return view.getText().toString();
    }

​ 至此便完成了,基本的数据双向绑定。

3、流程梳理

前提:

数据对象:

public class MyViewModel extends BaseObservable {
    private static final String TAG = "MyObservable";
    private String text;

    @Bindable
    public String getText() {
        return text;
    }

    public void setText(String text) {
        if (TextUtils.equals(text, this.text)) {
            return;
        }
        this.text = text;
        notifyPropertyChanged(BR.text);
    }
}

xml文件:

    <EditText
        android:id="@+id/time"
        app:myText="@={viewmodel.text}"
    />

数据正向绑定流程:MyObservable.text属性变化,调用属性的setter方法,触发notifyPropertyChanged(BR.text),根据xml中使用,会调用@BindingAdapter("app:myText")注解的方法;

数据反向绑定流程:EditText内容变化时,触发InverseBindingListener中的onChange()方法,onChange()中会调用获取值的@InverseBindingAdapter(attribute = "app:myText")注解的方法,然后onChange()中会调用MyObservable.text属性的setter方法,触发notifyPropertyChanged(BR.text),根据xml中使用,会调用@BindingAdapter("app:myText")注解的方法;

4、后话

​ 其实数据绑定系统,也有自己实现的类,大家可以参考下:androidx.databinding.adapters.ViewBindingAdapter

​ 当然还有@BindingMethods@InverseBindingMethods,以及更高深的用法,我现在还在学习中,后续有时间会继续更新

由于掺杂很多个人的理解的,文中描述不一定完全正确,欢迎留言指正和讨论。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335