MVVM dataBinding 学习心得(2018-09-14)

此篇为转载文章,如有侵权,定会删除
掘金作者:

下位子

原文链接
https://juejin.im/post/5a3bb1096fb9a0451c3a92b1

MVVM dataBinding 学习心得

目录

MVVM DataBinding 介绍

MVVM框架类似于早期的MVC和最热的MVP,但是比起这两个更为强势。MV-VM相比于MVP,其实就是将Presenter层替换成了ViewModel层,我们都知道,MVP的好处就是将逻辑代码从View层抽离出来,做到与UI层的低耦合,但是无形中会创造出许多的接口,有些接口很是冗余,不仅如此,当后期修改数据或者添加新的功能还需要修改或是添加接口,很是麻烦。

这个时候MV-VM的优势就体现出来了,ViewModel层所需要做的完全就是跟逻辑相关的代码,完全不会涉及到UI。当数据变化,直接驱动UI的改变,中间省去了冗余的接口。同时,在ViewModel层编写代码中,要求开发者需要将每个方法尽可能的做的功能单一,不与外部有任何的引用或者是联系,无形中提高了代码的健壮性,方便了后期的单元测试。

DataBinding其实就是谷歌出台的工具,是实现UI和数据绑定的框架,ViewViewModel通过DataBinding实现单向绑定或双向绑定,做到UI和数据的相互监听,同时开发者的任务分配也就很明确了,负责ViewModel的小伙伴完全不用考虑UI如何实现,很大程度上提高了代码的开发效率和后期出问题跟踪的准确性,针对这些好处,采用MVVM进行代码开发还是非常有必要的。

初步使用

1. modulebuild.gradle文件加上一行配置代码

android {
    ...
    dataBinding {
        enabled = true
    }
}
复制代码

2. 创建布局文件

只需要在之前布局的基础上,外层嵌套 <layout></layout>即可。

<layout
    xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="student"
            type="com.xiaweizi.bean.Student"/>
        <!-- 这里 type 必须传完整路径,或者用 import 方式也是可以的 -->
        <!--
            <import type="com.xiaweizi.bean.Student"/>
            <variable
                name="student"
                type="Student"/>
        -->
    </data>

    <!-- 对应之前的XML文件 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_horizontal"
        android:orientation="vertical">

    </LinearLayout>
</layout>
复制代码

因为XML是不支持自定义导包的,所以通过import先导包,如果类名相同的话可以通过alias进行区分:

<import type="android.view.View"/>
<import type="com.xiaweizi.View"
        alias="MyView"/>

<variable
    name="view1"
    type="View"/>

<variable
    name="view2"
    type="MyView"/>
复制代码

这个时候会在app\build\generated\source\debug\包名路径下生成对应的binding类,命名方式,举个例子最为直接:

原XML名:activity_main  ----> 生成对应的binding名: ActivityMainBinding
复制代码

3. Activity中替换原来的setContentView()代码

ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
复制代码

4. 接下来就是关键的ViewModel

a. 单向绑定

咱们先从简单的开始,DataBinding有个很大的好处就是摒弃原生findViewById频繁的遍历视图层和ButterKnife的反射,采用的是数组记录每个view

final Object[] bindings = mapBindings(bindingComponent, root, 8, sIncludes, sViewsWithIds);
复制代码

XML创建一个TextView

<TextView
    android:id="@+id/tv_content"
    android:text="@{student.name}"
    android:layout_width="match_parent"
    android:layout_height="50dp"/>
复制代码

在代码中通过binding直接可以获取到这个TextView

mBinding.tvContent
复制代码

那么如何实现单向绑定呢?

Student student = new Student("xiaweizi", 12);
mBinding.setStudent(student);
复制代码

这样就可以直接改变TextView的值。

ViewModel就是简单的数据

public class Student {
    public String name;
    public int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
复制代码

b. 双向绑定

之前说的单向绑定,即当数据变化,通过mBinding.setStudent(student)方式驱动UI的改变 而双向绑定,无论View还是ViewModel谁改变,都会驱动另一方的改变,实现双向绑定有两种方式:继承BaseObservable和使用ObservableField创建成员变量。

代码实现: 第一种继承BaseObservable

public class Student extends BaseObservable{

    // 如果是 public 则在成员变量上方加上 @Bindable 注解
    @Bindable
    public String sex;

    public void setSex(String sex) {
        this.sex = sex;
        notifyPropertyChanged(BR.sex);
    }

    /*************************** 我是分割线 ***************************/
    // 如果是 private 则在成员变量的 get 方法中添加 @Bindable 注解
    private String name;
    @Bindable
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(BR.name);
    }

    public void setSexName(String name, String sex){
        this.name = name;
        this.sex = sex;
        notifyChange();
    }
}
复制代码

这个时候当调用setName()方法,不仅数据改变,UI中的TextView内容也会随之改变。

我们可以发现有两个方法:notifyPropertyChanged()notifyChange,一个是更新指定的变量,第二个是更新所有该ViewModel中的对象。

notifyPropertyChanged(int fieldId)里面传的参数,即上面通过@Bindable注解创建对应的变量id

第二种:使用ObservableField

public class Student extends BaseObservable{

    public ObservableField<String> name = new ObservableField<>();

    private ObservableInt age = new ObservableInt();
    public void setAge(int age) {
        this.age.set(age);
    }
    public int getAge() {
        return age.get();
    }
}
复制代码

通过使用ObservableField创建的对象作用相当于第一种的方案,支持ObservableIntObservableBoolean或者是ObservableField<T>指定的类型、ObservableArrayMap<String, Object>ObservableArrayList<Object>等。

ObservableField内部已经封装了getset方法,如果成员变量是public属性,直接通过

mStudent.name.set("shabi");
String name = mStudent.name.get();
复制代码

设置和获取对应的成员变量的值。

如果是private,可以自己封装getset方法,效果一样。

其他使用

学会了上面基本的用户还是远远不够的,像按钮的点击事件或是EditText内容的监听,这些也是非常重要的,不过学会了一种,其他的举一反三就会容易的多了。

1. 事件处理

dataBinding需要你通过一些表达式来处理view的分发事件,除了少数例子外,事件元素的名称是由监听器中的方法所控制。比如View.OnLongClickListener内部有onLongClick()方法,所以XML定义的事件就为android:onLongClick.

可以直接在Activity内部定义一个类,用于处理事件的监听

public class Presenter {
    public void onClickExample(View view) {
        Toast.makeText(SimpleActivity.this, "点到了", Toast.LENGTH_SHORT).show();
    }

    public void onTextChanged(CharSequence s, int start, int before, int count) {
        mStudent.name.set(s.toString());
    }

    public void onClickListenerBinding(Student student) {
        Toast.makeText(SimpleActivity.this, student.name.get(),Toast.LENGTH_SHORT).show();
    }
}
复制代码

XML中:

<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="输入name"
    android:onTextChanged="@{presenter::onTextChanged}"/>

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:onClick="@{presenter.onClickExample}"
    android:text='@{"年龄:" + student.age}'/>

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginLeft="5dp"
    android:onClick="@{() -> presenter.onClickListenerBinding(student)}"
    android:text='@{"姓名:" + student.name}'/>
复制代码

首先从点击事件开始分析,android:onClick="@{presenter.onClickExample}" 里面对应的方法自然是要与Presenter定义的方法名一致,名字可以不为onClickExample,但是参数必须是View,参数要对应于setOnClickListener(onClickListener listener)对应的onClickListener要实现的接口,即public void onClick(View)

同理,监听EditText文本的变化,一般只要注意onTextChanged(CharSequence s, int start, int before, int count)方法即可,那么我们可以创建与之对应的方法,在XML文件中引用:android:onTextChanged="@{presenter::onTextChanged}"

最后再来看从UI中获取数据,也就是数据的回调,即DataBinding的精髓支出,ViewViewModel双向绑定。android:onClick="@{() -> presenter.onClickListenerBinding(student)}这里用到了lamda表达式,这样就可以不遵循默认的方法签名,将student对象直接传回点击方法中。来看一下实现效果:

<figure style="display: block; margin: 22px auto; text-align: center;">[图片上传中...(image-3bc70a-1536886348041-1)]

<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>

</figure>

一目了然,我就不赘述了,我们可以发现一点,一开始我们并没有给Student对象设置值,所以显示的是null,并没有报空指针异常,这也是DataBinding的有点之一。

其实dataBinding自带对数据监听的方法:

<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@={student.name}"/>
复制代码

代码中:

student.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
        @Override
        public void onPropertyChanged(Observable observable, int i) {
            // i 为 BR 文件中对应的 int 值
            Log.i("xwz--->", student.getName());
            Log.i("xwz--->", student.getAge());
        }
});
复制代码

这个对数据的监听建立在,使用@Bindable作为双向绑定为条件,当数据变化,便会出发onPropertyChanged方法。需要注意的是android:text="@={student.name}",@后面多了一个=

2. ViewStubinclude

dataBinding同样是支持ViewStub的,使用起来也很简单,直接贴代码了。

<ViewStub
    android:id="@+id/view_stub"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout="@layout/viewstub"/>
复制代码

代码中:

View inflate = binding.viewStub.getViewStub().inflate();
复制代码

inflate即为替代ViewStubView.

至于include更简单,用法跟以前是差不多,唯一不同的是可以将ViewModel传到下一个XML中:

<include layout="@layout/layout_include" bind:student="@{student}"/>
复制代码

layout_include中同样可以共享student这个对象。

3. BindingAdapter的使用

我们之前用的都是Android自带的监听或是属性,比如textonClick,但是如果项目中需要动态改变ImageView的内容,那我们应该怎么办呢?dataBinding给我们提供了BindingAdapter这个注解,方便我们定义自定义的属性。 假如我们有个需求,点击按钮更换图片,这个时候我们需要定义静态的方法:

@BindingAdapter({"url", "name"})
public static void loadImageView(ImageView view, String url, String name) {
    Log.i("xwz--->", url + "\t" + name);
    Glide.with(view.getContext())
         .load(url)
         .into(view);
}
复制代码

XML中使用

<ImageView
    android:layout_width="160dp"
    android:layout_height="160dp"
    bind:name="@{student.name}"
    bind:url="@{student.imgUrl}"/>
复制代码

这里有必要解释一下,静态方法loadImageView里第一个参数为作用的View,这里是ImageView;后面的参数即分别对应于@BindingAdapter里面的参数。那这里是怎么跟View联系在一块呢?我们发现XML中有这样一行代码bind:name="@{student.name}这里的name对应的的@BindingAdapter注解里的参数name,并映射于ViewModel中的student.name。当student.name值改变,就会触发loadImageView方法,从而执行里面的方法。

bind名称是任意的定义的,不过要定义对应的命名空间xmlns:bind="http://schemas.android.com/apk/res-auto"

实现的效果就很简单了:

<figure style="display: block; margin: 22px auto; text-align: center;">[图片上传中...(image-c234c0-1536886348040-0)]

<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>

</figure>

更强大的在于可以覆盖Android原生的元素设置属性,比如android:text最常见不过了

@BindingAdapter ("android:text")
public static void setText(TextView view, String text) {
    view.setText(text + "xiaweizi");
    Log.i("xwz--->", text);
}
复制代码

XML:

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text='@{"测试"}'/>
复制代码

这个时候所有设置text的地方后缀全部加上了xiaweizi.

4. @BindingConversion

dataBinding还支持对数据的转换,或者是类型的转换

@BindingConversion
public static String addString(String text){
    Log.i("xwz--->", "DemoBindingAdapter:  " + "addString: " + text);
    return text + "xiaweizi";
}
复制代码

这个时候会将项目中所有以@{String}方式用到的String后缀全部加上xiaweizi.

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color){
   return new ColorDrawable(color);
}
复制代码

XML:

<View
   android:background="@{isError ? @color/red : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
复制代码

这段代码的作用在于将int类型的color值,转换成了ColorDrawable类型.

一些细节

databinding支持一些java的表达式

  • + - * / %
  • 字符串的连接"a"+"b"
  • 逻辑和位运算&& || & |
  • 一元运算+ - ! ~
  • 移位 >> >>> <<
  • 比较 == > < >= <=
  • instance of
  • 支持数据类型:character,String,numeric,null
  • 强转cast
  • 方法的调用
  • 成员变量的访问
  • 数组访问
  • 三元表达式? :

简单例子:

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
复制代码

dataBinding不支持的Java特性

  • this
  • super
  • new
  • 泛型

dataBinding判空处理

使用??来进行判空操作

android:text="@{user.displayName ?? user.lastName}"
复制代码

如果不为空则选择左侧值,否则选择右侧值,类似于:

android:text="@{user.displayName != null ? user.displayName : user.lastName}"
复制代码

支持数组,集合,map

<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List&lt;String&gt;"/>
    <variable name="sparse" type="SparseArray&lt;String&gt;"/>
    <variable name="map" type="Map&lt;String, String&gt;"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"
复制代码

资源的访问

dataBinding支持一般语法对资源的访问:

android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
复制代码

小结

dataBinding主要的作用就在于减少ActivityFragment层的代码,不再使用findViewById,让XML从之前只用于显示视图,到现在可以做一些操作。在性能上更是有很大的提高,内部采用0反射,使用位标记检测需要更新的view,每次数据的改变是在下一帧开始改变等等。

当然也有一些不足之处,Android StudioIDE支持还不是那么完善,在XML中一些方法不能智能生成和跳转,还有就是报错的错误信息,有的时候并不能定位到准确的位置。不过总体上来说dataBinding带来的好处远远的超过这些不足,所以还没有尝试的小伙伴,不妨试一试,相信你会爱上他的。

感谢dataBinding视频 markzhai

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