完全掌握Android Data Binding(MVVM)-入门篇

  • 阅读本文大概需要6min
    关键字
    ArchitectureMVVMData Binding

目录

  • MVVM风流史
  • 一个优秀的男人需要的气质
    1. 数据驱动
    2. 低耦合度
    3. 更新UI
    4. 可复用性
  • 如何成为一个优秀的男人
    1. 如何把数据和View进行绑定
      i. 添加一个POJO类
      ii. 定义与使用Variable
      iii. 绑定Variable
    2. 如何更新数据
      i. Observable Objects
      ii. ObservableFields
      iii. Observable Collections
    3. 事件处理(Event Handling)
      i. 方法引用(Method Reference)
      ii. 监听绑定(Listener Bingings)
    4. RecyclerView(列表)
    5. 自定义属性
    6. 其他
  • 填坑
  • 特殊通道
  • 参考

MVVM风流史


  • 出生时间不详,Android Studio 1.3时需要通过依赖库添加com.android.databinding
  • 于Android Studio 2.0被内置,直接配置
android {
    ....
    dataBinding {
        enabled = true
    }
}
  • 于Android Studio2.1 Preview 3开始支持双向绑定,从此人生走入正轨
  • 于2017年下半年 伴随着 Android Architecture Components 1.0 stable发布,人生从此进入了巅峰

PS: MVVM 是一种思想,数据的双向绑定,并不是Android特有的,其他的比如AngularJS和IOS的MVVM
最初我还以为是我们大Google的专利[尴尬脸]😌

一个优秀的男人需要的气质


数据驱动

在我们平常的开发,当数据变化需要更新UI的时候,需要先获取UI控件的引用(findViewById),然后去更新UI,获取控件的属性也是需要通过UI控件的引用。在MVVM中,这些都是通过数据驱动来自动完成的,数据变化后会自动更新UI,UI的改变也能自动反馈到数据层,数据成为主导因素。这样MVVM层在业务逻辑处理中只要关心数据,不需要直接和UI打交道,在业务处理过程中简单方便很多。

低耦合度

MVVM模式中,数据是独立于UI的。

数据和业务逻辑处于一个独立的ViewModel中,ViewModel只需要关注数据和业务逻辑,不需要和UI或者控件打交道。UI想怎么处理数据都由UI自己决定,ViewModel不涉及任何和UI相关的事,也不持有UI控件的引用。即便是控件改变了(比如:TextView换成EditText),ViewModel也几乎不需要更改任何代码。它非常完美的解耦了View层和ViewModel

更新UI

在MVVM中,数据发生变化后,我们在工作线程直接修改(在数据是线程安全的情况下)ViewModel的数据即可,不用再考虑要切到主线程更新UI了,这些事情相关框架都帮我们做了。

可复用性

一个ViewModel可以复用到多个View中。同样的一份数据,可以提供给不同的UI去做展示。对于版本迭代中频繁的UI改动,更新或新增一套View即可。如果想在UI上做A/B Testing,那MVVM是你不二选择。

PS: 本部分优势参考这篇文章

如何成为一个优秀的男人


如何把数据和View进行绑定

把大象装入箱子需要三部

  1. 添加一个POJO类
    public class User {
        private final String firstName;
        private final String lastName;
        private String displayName;
        private int age;
     
        public User(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }
     
        public User(String firstName, String lastName, int age) {
            this(firstName, lastName);
            this.age = age;
        }
     
        public int getAge() {
            return age;
        }
     
        public String getFirstName() {
            return firstName;
        }
     
        public String getLastName() {
            return lastName;
        }
    }
  1. 定义与使用Variable
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
<data>
    <import type="android.graphics.drawable.Drawable"/>
    <variable name="user"  type="com.example.User"/>
    <variable name="image" type="Drawable"/>
    <variable name="note"  type="String"/>
<variable
    name="viewModel"
    type="com.demo.tangminglong.mvvmprojecttest.viewmodel.MainViewModel"/>
 </data>
    ...
<Button
            android:padding="10dp"
            android:layout_gravity="center_horizontal"
            android:gravity="center"
            android:layout_width="200dp"
            android:layout_height="50dp"
            android:text="@{user.firstName}"
         />
 </layou>

一个根据layout.xml将自动生成一个以Binding结尾的类文件,例如main_activity.xml将生成MainActivityBinding,生成的代码位置如下图所示

847D2E67-524A-451D-97D6-2CE117B4B674.png

  1. 绑定Variable
    Binding类根据layout.xml定义的变量(variable),将会自动生成相应的set方法,同时我们通常的setContentView(int)也被替换,如下代码
ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
        mainViewModel = new MainViewModel(this,this);
        binding.setViewModel(mainViewModel);

如何更新数据

一个POJO值改变并不能够自动更新UI,dataBing的强大之处在于值改变能够自动同步更新UI(单向绑定),实现的方式有如下三种

  1. Observable Objects
    用一个类来实现Observable为了方便,Android 原生提供了已经封装好的一个类 - BaseObservable,并且实现了监听器的注册机制。
    我们可以直接继承BaseObservable
private static class User extends BaseObservable {
   private String firstName;
   private String lastName;
   @Bindable
   public String getFirstName() {
       return this.firstName;
   }
   @Bindable
   public String getLastName() {
       return this.lastName;
   }
   public void setFirstName(String firstName) {
       this.firstName = firstName;
       notifyPropertyChanged(BR.firstName);
   }
   public void setLastName(String lastName) {
       this.lastName = lastName;
       notifyPropertyChanged(BR.lastName);
   }
}

BR 是编译阶段生成的一个类,功能与 R.java 类似,用 @Bindable 标记过 getter 方法会在 BR 中生成一个 entry,当我们

通过代码可以看出,当数据发生变化时还是需要手动发出通知。 通过调用notifyPropertyChanged(BR.firstName)来通知系统 BR.firstName 这个 entry 的数据已经发生变化,需要更新 UI。

  1. ObservableFields
    还有一种更细粒度的绑定方式,可以具体到成员变量,这种方式无需继承 BaseObservable,一个简单的 POJO 就可以实现。
private static class User {
   public final ObservableField<String> firstName =
       new ObservableField<>();
   public final ObservableField<String> lastName =
       new ObservableField<>();
   public final ObservableInt age = new ObservableInt();
}

系统为我们提供了所有的 primitive type 所对应的 Observable类,例如 ObservableInt、ObservableFloat、ObservableBoolean 等等,还有一个 ObservableField 对应着 reference type。

  1. Observable Collections
    对应ObservableFields的集合,用法和ObservableFields差不多

PS: 双向绑定表达式对应@={setting.cacheEnable},单向绑定对应@{setting.cacheEnable},具体可参照GitHub的Demo

事件处理(Event Handling)

Data Binding 可以通过表达式处理View的事件(eg.onClick),事件处理有两种方式处理,一个是方法引用(Method Reference),一个是监听绑定(Listener Bingings)

  • 方法引用(Method Reference)
    方法引用的方式android:onClcik对应方法Vuew#onClick,因为属性是在编译期进行,所以一旦有错误会立即报错,引用的方式如下
public class MainViewModel {
    private Context context;

    public MainViewModel(Context context) {
        this.context = context;
    }

    public void onClickOne(View view){
        context.startActivity(new Intent(context, SingleMainActivity.class));
    }

    public void onClickTwo(View view){
        context.startActivity(new Intent(context, DoubleMainActivity.class));
    }
}
<Button
            android:padding="10dp"
            android:layout_gravity="center_horizontal"
            android:gravity="center"
            android:layout_width="200dp"
            android:layout_height="50dp"
            android:text="@string/singleBundle"
            android:onClick="@{viewModel::onClickOne}"/>
        <Button
            android:padding="10dp"
            android:layout_gravity="center_horizontal"
            android:gravity="center"
            android:layout_width="200dp"
            android:layout_height="50dp"
            android:text="@string/doubleBundle"
            android:onClick="@{viewModel.onClickTwo}"/>

调用方式为variable.method或者variable::method
注意:方法里面需到参数view

  • 监听绑定(Listener Bingings)
    Listener Bingings的优点是可以使用复杂的参数,比如下面接口
public interface ItemEventHandler {
    void clickTitle(View view,Item item);
}

布局文件如下

<TextView
    android:id="@+id/textView"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="1"
    android:onClick="@{(view)->handler.clickTitle(view,item)}"
    />

两者的区别主要是Method Reference会在绑定数据时创建好View的onClickListener,而Listener Bindings在事件发生时才会创建。

PS :Method Reference在xml不会自动提示

RecyclerView(列表)

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

自定义属性

<de.hdodenhof.circleimageview.CircleImageView
                    android:id="@+id/image_owner"
                    android:layout_width="65dp"
                    android:layout_height="65dp"
                    app:imageUrl="@{viewModel.ownerAvatarUrl}"/>

这里面有自定义属性imageUrl,这并不是定义在自定义CircleImageView里面,而是通过代码动态添加自定义属性,关键代码如下

 @BindingAdapter({"imageUrl"})
    public static void loadImage(ImageView view, String imageUrl) {
        Picasso.with(view.getContext())
                .load(imageUrl)
                .placeholder(R.drawable.placeholder)
                .into(view);
    }

属性imageUrl传入地址就可以实现自动加载图片到ImageView
自定义属性Attribute默认是找setAttribute方法,对xml传入的值进行设置,eg android:text 对应 setText(String),与属性相关的还有@BindingMethods可以重命名

PS: 我们在编写xml时候有时候调用import的变量Android Studio不能自动提示,原因是对于自定义属性Android Studio对值设置不支持自动提示功能

其他

其他一些需要注意的,比如语法表达,布局的incluedViewStubs 以及converters等更多使用,参照官方文档

填坑

  • 如果出现这样的错误信息cannot resolve symbol 'ActivityMainBinding'以为这数据绑定自动生成没有创建成功,试试下面几个方法
    1. 确认Module添加了dataBinding.enabled = true
    2. 确认XML的根布局是<layout>
    3. 检查layout的名称是否正确拼写 eg.activity_main.xml 对应ActivityMainBinding.java
    4. Run File => Invalidate Caches / Restart to clear the caches.
    5. Run Project => Clean and Project => Re-Build to regenerate the class file.
    6. 重启Android Studio.
  • 如果出现错误信息像**.**.databinding does not exist很可能是因为在数据绑定到XML时候有错误,检查那些错误(比如忘记import a java class)
  • dentifiers must have user defined types from the XML file. View is missing it这通常是因为你忘记import statement或者变量拼写错误

特殊通道👉

如果想深入了解,可以参看我的GitHub MVVMDemo

参考

http://xuyushi.github.io/2016/03/05/Android%20MVVM/
https://tech.meituan.com/android_mvvm.html
https://guides.codepath.com/android/Applying-Data-Binding-for-Views
https://developer.android.google.cn/topic/libraries/data-binding/index.html
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0603/2992.html

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

推荐阅读更多精彩内容