Android MVVM一步一步来实现

通过本文可以了解到的内容:
1.如何使用dataBinding;
2.设置点击事件(含参数的);
3.数据与UI同步更新;
4.基本的dataBinding语法;
5.自定义注解。

按照官网的文档一步一步来学习
官方文档传送门

简单开始

第一步,在module的build.gradle文件中添加如下代码,注意是android节点下添加:

android {
    ...
    dataBinding {
        enabled = true
    }
}

第二步,增加一个数据实体类。

public class UserBean{
    private String firstName;

    public UserBean(String firstName) {
        this.firstName = firstName;
    }
}

第三步,修改layout文件,以layout节点开始,data节点包含数据实体类,其他就和以前布局文件没有啥区别。
给某个textview绑定数据:android:text="@{user.firstName}",user就是某个实体类在这个文件中的引用名,firstName为这个实体类的某个属性。

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

    <data>

        <variable
            // 以下类在本xml文件中的引用名称
            name="user"
            // 数据实体类全名
            type="com.hotelgg.android.hotelggreview.data.UserBean" />
        
        <variable
            name="person"
            type="com.hotelgg.android.hotelggreview.data.PersonBean"/>

        <variable
            name="onclick"
            type="com.hotelgg.android.hotelggreview.handler.ClickHandler"/>
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical">

        <TextView
            android:id="@+id/main_tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@color/red_2"
            android:padding="10dp"
            android:text="@{user.firstName}"
            android:onClick="@{onclick.onClick}"
            android:textColor="@color/white_1" />

        <TextView
            android:id="@+id/person_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@color/red_2"
            android:layout_marginTop="20dp"
            android:padding="10dp"
            android:text="@{person.name}"
            android:onClick="@{onclick.onClick}"
            android:textColor="@color/white_1" />


    </LinearLayout>

</layout>

第四步,在activity中的使用,ActivityMainBinding的命名规则是和其绑定的xml文件的命名规则相关的,例如,activity_main为xml文件的名称,那么就在其后面增加一个binding,首字母大写即可。

ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
UserBean userBean = new UserBean("shuai bu shuai");
binding.setUser(userBean);
坑Num.1

注意ActivityMainBinding这个类,编译器不会自动编译出来,需要build一下项目这个类才能够被引用到。

坑Num.2
From the perspective of data binding, these two classes are equivalent. 

这是官网的一段话,我来断章取义一下,第一句话不知道怎么翻译,什么鬼从data binding的透明(还能穿透明装呢)?申明来看?(知道的朋友指点一下)第二句,这两个类是等同的。然后我就跟着官方文档一样就写了一个数据model的构造器。

public class UserBean{
    private String firstName;

    public UserBean(String firstName) {
        this.firstName = firstName;
    }
}

编译,啪!不出意外,你已经得到了ide的一个异常

Error:(31, 29) Could not find accessor com.hotelgg.android.hotelggreview
.data.UserBean.firstName 

应该添加一个get方法的入口,所以不知道官方文档说的这两者是等同的是嘛意思!

public class UserBean{
    private String firstName;

    public UserBean(String firstName) {
        this.firstName = firstName;
    }

    public String getFirstName() {
        return firstName;
    }

}

添加点击事件

第一步,添加一个ClickHandler类,名字随便取。

public class ClickHandler {
    private BaseActivity context;
    private ActivityMainBinding mDataBinding;

    public ClickHandler(BaseActivity context, ActivityMainBinding mDataBinding) {
        this.context = context;
        this.mDataBinding = mDataBinding;
    }

    public void onClick(View view) {
        if (R.id.main_tv == view.getId()) {
            UserBean bean = mDataBinding.getUser();
            bean.setFirstName("perry");
        } else if (R.id.person_name == view.getId()) {
            //  这里先不用理会
            PersonBean bean = mDataBinding.getPerson();
            bean.name.set("mary");
        }

    }

}

第二步,在activity中添加绑定

binding.setOnclick(new ClickHandler(this, binding));

第三步,对应的xml中添加引用,上面已经给出完整代码。

<data>

        <variable
            name="onclick"
            type="com.hotelgg.android.hotelggreview.handler.ClickHandler"/>
</data>

// textview添加点击事件
android:onClick="@{onclick.onClick}"

然后,就没有然后了,可以运行开干了。

点击事件中传递参数

这种场景主要应用于:一个recyclerview的item在点击时需要取出当前item的数据,比如一个当前item的id或者url要传递到下一个页面中去。这个时候从绑定的数据中取出这个参数也有相应的办法。

<data>

        <import type="com.hotelgg.android.hotelggreview.adapter.UserAdapter.UserAdapterItemClick" />

        <variable
            name="hotelInfo"
            type="com.hotelgg.android.hotelggreview.data.HotelInfoBean" />

        <variable
            name="itemClick"
            type="UserAdapterItemClick" />

    </data>

// 某个控件的点击方法,其实就是lambda表达式的形式
android:onClick="@{()->itemClick.onClick(hotelInfo)}"

然后在adapter中进行相应的注册,这里涉及到在recyclerview当中使用,具体的代码会在后头贴出。这里只是简单的演示。

((ItemSingleTextBinding) binding).setItemClick(new UserAdapterItemClick());

public static class UserAdapterItemClick {
    public void onClick(HotelInfoBean bean) {
        LogUtil.e(bean.title.get());
        bean.title.set("change big hotel");
    }
}

这里有一点小小的观点需要表达一下:我们可以把这里的UI这样理解,它有自身的数据源model,有自身的操作行为handler,而同一个页面,我更加倾向于让其使用一个handler,这样复用性高一些,至于每个控件的数据源,我们可以通过传递进来的ActivityMainBinding对象进行获取和改变,UI层面就不需要做除了初始化之外的其他任何工作。所有的逻辑操作全由handler进行。

虽然点击事件和数据绑定都已经完成,但是数据绑定后,改变数据源是不是就能够直接让UI发生相应的更新变化呢?答案是否定的。

数据源与UI同步更新

如果想要改变数据源后同步更新UI界面,就需要对数据源进行一定的修改,我们还是以UserBean为例。

第一种方法
继承BaseObservable,然后在get方法上加上@Bindable注解,在set方法中加入notifyPropertyChanged(BR.firstName);进行数据更新。这个BR有点类似于资源文件引用中的R,也是包名.BR

public class UserBean extends BaseObservable{
    private String firstName;
    private int num;

    public UserBean(String firstName, int num) {
        this.firstName = firstName;
        this.num = num;
    }

    @Bindable
    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
        notifyPropertyChanged(BR.num);
    }

    @Bindable
    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
        notifyPropertyChanged(BR.firstName);
    }
}

第二种方法
用Google给我们提供的Observable...类,支持所有数据类型。

public class PersonBean {
    public ObservableField<String> name = new ObservableField<>();
    public ObservableField<String> lastName = new ObservableField<>();
}

// 更新方式
personBean.lastName.set("White");

一些基本的语法

增加默认值
除了指定相应的绑定数据对象外,还可以增加默认值,如下:

android:text="@{user.firstName,default=test}"

数据类型转换
类型的转换,把int型转化成string类型并进行存储,其中int值还可以进行相应的计算。

android:text="@{String.valueOf(user.num + 1)}"

使用资源文件属性
还可以指定资源文件中的属性,并且直接使用占位符的形式来填充数据(这个功能强大)。

android:text="@{@string/name_format(person.name,person.lastName)}"
<resources>
    <string name="name_format">第一个:%s,第二个:%s</string>
</resources>

使用三目运算符

android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

不过注意,在导入集合类型时,咱们一般都会使用泛型类,以下泛型类为string类型,因为是基本数据类型,所以不用导入。使用泛型类的格式如下:

<data>
    <import type="java.util.List"/>
    <variable name="list" type="List&lt;String&gt;"/>
</data>

自定义注解

这个功能相当之强大,废话不多说,直接上代码。先做一个工具类,名字随便取,如我的AppBindingUtils,具体啥意思,注释当中已经说的很清楚。

public class AppBindingUtils {
    // BindingAdapter注解,自定义注解,可以让某些控件增加自定义属性,并且,只要在xml中出现该属性,
    // 就会调用该注解注释的方法。
    // 其中 bind_data 为xml中自定义属性的属性名,setDataTest这个名字随便取。
    // 不让recyclerView每次都设置adapter,这样会刷新多余数据
    @BindingAdapter("bind_data")
    public static void setDataTest(RecyclerView recyclerView, ObservableArrayList<MultipleListBean> data) {
        if (recyclerView != null && recyclerView.getAdapter() == null) {
            recyclerView.setLayoutManager(new LinearLayoutManager(recyclerView.getContext(), LinearLayoutManager.VERTICAL, false));
            recyclerView.setAdapter(new UserAdapter(data));
        }
    }
}

xml布局文件中如下,这样,当编译器发现了xml中有一项自定义注解编注的属性时,就会带着这个属性去找有@BindingAdapter注解编注过并且属性名为bind_data的方法,并在初始化该控件的时候,调用这个被注解标注过的方法。

<variable
            name="data"
            type="ObservableArrayList&lt;MultipleListBean&gt;" />

<android.support.v7.widget.RecyclerView
            android:id="@+id/main_recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            bind_data="@{data}" />

在activity当中设置这个数据源。

ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
beans = new ObservableArrayList<>();
beans.add(new MultipleListBean(1, bean));
beans.add(new MultipleListBean(1, bean1));
binding.setData(beans);

以上介绍差不多就是dataBinding的一些基础内容的介绍了。欢迎留言讨论。

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

推荐阅读更多精彩内容