Android设计模式之MVVM

来源:互联网 发布:守望先锋左上角6个数据 编辑:程序博客网 时间:2024/05/19 18:44

简介

在开发中可能你使用过MVP设计模式来对代码进行解耦,但是谷歌发布的DataBinding库更加简化了我们的代码,同时也催生了MVVM设计模式在Android中的使用。在MVP模式中我们需要ModelViewPresenter三者进行配合使用,而MVVM模式是由ModelViewViewModel进行配合的,其中的区别主要在于ViewModelDataBinding是一个实现数据和UI绑定的框架,是构建MVVM模式的一个关键的工具,其奇妙之处在于可以将XML文件与指定的JAVA类绑定,实现数据的自动更新效果。

MVVMMVP的演进版本,其核心是实现了双向绑定,主要依赖于Android提供的DataBinding兼容库实现。在MVVM中将MVPPresenter替换为ViewModel,而ViewModel相当于UIModel的桥梁,将ViewModel直接绑定并将相关的业务逻辑下移于Model层中进行处理。

mvp

mvvm

Model:负责数据实现和逻辑处理。
View:对应于Activityxml,负责View的绘制以及与用户交互。
ViewModel:创建关联,将ModelView绑定起来,如此之后Model更改后通过ViewModel反馈给ViewViewxml布局文件经过特定的编写及编译工具处理后,生成的代码会接收ViewModel的数据通知消息,自动刷新界面。

单向绑定中数据的流向是单方面的,只能从代码流向UI,而双向绑定的数据流向是双向的,当业务代码中的数据改变时,UI上的数据能够得到刷新,当用户通过UI交互编辑了数据时,数据的变化也能自动的更新到业务代码中的数据上。对于双向绑定可以使用DataBinding,它是一个实现数据和UI绑定的框架,是构建MVVM模式的一个关键的工具。

基本用法

1、环境要求

1、Android Studio版本在1.3以上;
2、gradle的版本要在1.5.0-alpha1以上;
3、需要在Android SDK Manager中下载Android Support Repository
4、在对应Modulebuild.gradle中添加:

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

2、创建实体类

public class User {    private String userName;    private String nickName;    public String getUserName() {        return userName;    }    public void setUserName(String userName) {        this.userName = userName;    }    public String getNickName() {        return nickName;    }    public void setNickName(String nickName) {        this.nickName = nickName;    }    @Override    public String toString() {        return "User{" +                "userName='" + userName + '\'' +                ", nickName='" + nickName + '\'' +                '}';    }}

3、XML布局

布局文件不再是以传统的某一个容器作为根节点,而是使用<layout></layout>作为根节点,在<layout>节点中我们可以通过<data>节点来引入我们要使用的数据源。

<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android">    <data>        <variable            name="user"            type="com.wiggins.mvvm.bean.User" />    </data>    <LinearLayout        android:layout_width="match_parent"        android:layout_height="match_parent"        android:background="@color/theme_bg"        android:orientation="vertical">    <TextView            android:layout_width="match_parent"            android:layout_height="0dp"            android:layout_weight="1"            android:gravity="center"            android:text="@{user.userName}"            android:textColor="@color/blue"            android:textSize="@dimen/font_normal" />        <TextView            android:layout_width="match_parent"            android:layout_height="0dp"            android:layout_weight="1"            android:gravity="center"            android:text="@{user.nickName}"            android:textColor="@color/blue"            android:textSize="@dimen/font_normal" />    </LinearLayout></layout>

4、定义variable

<data>节点中定义的variable节点,其中name属性表示变量的名称,type属性表示这个变量的类型,实例就是我们实体类的具体位置,当然data节点也支持import,所以上面的代码也可以换一种形式来写。

<data>    <import type="com.wiggins.mvvm.bean.User" />    <variable        name="user"        type="User" /></data>

使用import节点将User导入,然后直接使用即可。

然后我们前面在build.gradle中添加的dataBinding会根据xml文件的名称Generate一个继承自ViewDataBinding的类。

例如:这里xml的文件名叫activity_main.xml,那么生成的类就是ActivityMainBinding

注意:

java.lang.*包中的类会被自动导入,可以直接使用,例如要定义一个String类型的变量:

<variable    name="name"    type="String" />

5、绑定variable

修改ActivityonCreate方法,用DataBindingUtil.setContentView()来替换掉以前的setContentView(),然后使用之前创建的User对象,通过binding.setUser(user)variable进行绑定。

@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);    User user = new User();    user.setUserName("小明");    user.setNickName("一花一世界");    binding.setUser(user);}

其中的ActivityMainBinding类是DataBinding框架为我们自动生成的,它与你的XML文件名字相关。比如我的XML文件名字是activity_main,那么生成的类就会取消下划线并且在最后加上Binding就得到了ActivityMainBinding。这个类的实例可以通过DataBindingUtil.setContentView()来得到,同时此类里面有我们XML文件里所有的控件信息,因此也不需要去findViewById了,界面上的管理基本可以全部转移到绑定的ViewModel中了,可以参考以下方式对相应的控件进行操作。

binding.tvContent.getText().toString().trim();

注意:

ActivityMainBinding类是自动生成的,所有的set方法也是根据variable名称生成的。例如我们定义了以下两个变量:

<data>    <variable name="userName" type="String" />    <variable name="nickName" type="String" /></data>

那么就会生成对应的两个set方法。

setUserName(String userName);setNickName(String nickName);

6、使用variable

数据与variable绑定之后,xmlUI元素就可以直接使用了。

<TextView    android:id="@+id/tv_content"    android:layout_width="match_parent"    android:layout_height="0dp"    android:layout_weight="1"    android:gravity="center"    android:text="@{user.nickName}"    android:textColor="@color/blue"    android:textSize="@dimen/font_normal" />

在布局文件中,TextViewtext属性设置成了@{user.nickName},这样该TextView就会直接将User实体类的nickName属性值显示出来了。

基本运算

1、三目运算

<TextView    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:text="@{user.userName??user.nickName}"    android:textColor="@color/blue"    android:textSize="@dimen/font_normal" />

两个??表示如果userNamenull则显示nickName,否则显示userName

2、字符拼接

<TextView    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:text="@{`userName is : `+user.userName}"    android:textColor="@color/blue"    android:textSize="@dimen/font_normal" />

这里的字符拼接不是用单引号,而是ESC按键下面的那个按键按出来的,目前DataBinding中的字符拼接还不支持中文。

3、根据数据来决定显示样式

<TextView    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:background="@{user.age &lt; 30 ? 0xFFEA5450:0xFFFA7C20}"    android:text="@{String.valueOf(user.age)}"    android:textColor="@color/blue"    android:textSize="@dimen/font_normal" />

在这里给TextView设置背景的时候做了一个简单的判断,如果用户的年龄小于30时背景就显示为红色,否则背景就显示为橘黄色。DataBinding里支持大于号但是不支持小于号,因此我们在使用大于小于号时可以都用转义字符来表示。另外DataBinding对于基本的四则运算、逻辑与、逻辑或、取反、位移等都是支持的,大家如有需要可自行使用。

绑定ImageView

如何来绑定图片呢?我们先了解一下关于DataBinding自定义属性的问题。事实上在我们使用DataBinding的时候可以给一个控件自定义一个属性,假如现在想要通过DataBindingPicasso显示一张网络图片该怎么做呢?我们可以使用@BindingAdapter注解来创建一个自定义属性,同时还要有一个统一的注解方法。当我们在布局文件中使用这个自定义属性的时候,就会触发这个被我们注解的方法。下面我们在原来的User实体类中添加了用户头像参数,来看看是如何使用的。

public class User {    private String userName;    private String nickName;    private String userAvatar;    @BindingAdapter("bind:userAvatar")    public static void getAvatarImage(ImageView iv, String userAvatar) {        Picasso.with(iv.getContext())                .load(userAvatar)                .into(iv);    }    public String getUserName() {        return userName;    }    public void setUserName(String userName) {        this.userName = userName;    }    public String getNickName() {        return nickName;    }    public void setNickName(String nickName) {        this.nickName = nickName;    }    public String getUserAvatar() {        return userAvatar;    }    public void setUserAvatar(String userAvatar) {        this.userAvatar = userAvatar;    }    @Override    public String toString() {        return "User{" +                "userName='" + userName + '\'' +                ", nickName='" + nickName + '\'' +                ", userAvatar='" + userAvatar + '\'' +                '}';    }}

新实体类里边新增了用户头像,用户头像中存储的是一个网络图片地址,类中除了基本的get/set方法之外还多了一个叫getAvatarImage的方法,此方法有一个@BindingAdapter("bind:userAvatar")注解,该注解表示当用户在ImageView中使用自定义属性userAvatar的时候会触发这个方法,我在这个方法中为此ImageView加载一张图片,这里有一点需要注意,就是该方法必须为静态方法。下面再来看看这次的布局文件:

<?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">    <data>        <variable            name="user"            type="com.wiggins.mvvm.bean.User" />    </data>    <LinearLayout        android:layout_width="match_parent"        android:layout_height="match_parent"        android:background="@color/theme_bg"        android:orientation="vertical">        <ImageView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            app:userAvatar="@{user.userAvatar}" />    </LinearLayout></layout>

注意:在ImageView控件中使用userAvatar属性的时候,使用的前缀不是android而是app。再来看看Activity中的代码:

@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);    User user = new User();    user.setUserName("小明");    user.setNickName("一花一世界");    user.setUserAvatar("http://pic.sc.chinaz.com/files/pic/pic9/201412/apic8065.jpg");    binding.setUser(user);}

在配置文件中加上网络权限就可以运行显示图片了。

绑定ListView

ListView中实现左边显示图片、右边显示文本这样一个效果,下面是主布局:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="@color/theme_bg"    android:orientation="vertical">    <ListView        android:id="@+id/lv_users"        android:layout_width="match_parent"        android:layout_height="match_parent" /></LinearLayout>

再来看看ListViewitem布局:

<?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">    <data>        <import type="com.wiggins.mvvm.bean.User" />        <variable            name="user"            type="User" />    </data>    <LinearLayout        android:layout_width="match_parent"        android:layout_height="@dimen/item_large"        android:background="@color/white"        android:gravity="center_vertical"        android:orientation="horizontal"        android:paddingLeft="@dimen/padding_normal"        android:paddingRight="@dimen/padding_normal">        <ImageView            android:layout_width="@dimen/icon_normal"            android:layout_height="@dimen/icon_normal"            app:userAvatar="@{user.userAvatar}" />        <TextView            android:id="@+id/tv_content"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:layout_marginLeft="@dimen/margin_small"            android:text="@{user.nickName}"            android:textColor="@color/blue"            android:textSize="@dimen/font_normal" />    </LinearLayout></layout>

实体类我们还是使用之前的User类。

public class User {    private String userName;    private String nickName;    private String userAvatar;    @BindingAdapter("bind:userAvatar")    public static void getAvatarImage(ImageView iv, String userAvatar) {        Picasso.with(iv.getContext())                .load(userAvatar)                .into(iv);    }    public String getUserName() {        return userName;    }    public void setUserName(String userName) {        this.userName = userName;    }    public String getNickName() {        return nickName;    }    public void setNickName(String nickName) {        this.nickName = nickName;    }    public String getUserAvatar() {        return userAvatar;    }    public void setUserAvatar(String userAvatar) {        this.userAvatar = userAvatar;    }    @Override    public String toString() {        return "User{" +                "userName='" + userName + '\'' +                ", nickName='" + nickName + '\'' +                ", userAvatar='" + userAvatar + '\'' +                '}';    }}

接下来再看看我们的Adapter类:

public class MyBaseAdapter<T> extends BaseAdapter {    private LayoutInflater inflater;    private int layoutId;    private int variableId;    private List<T> list;    public MyBaseAdapter(Context context, int layoutId, int variableId, List<T> list) {        this.layoutId = layoutId;        this.variableId = variableId;        this.list = list;        inflater = LayoutInflater.from(context);    }    @Override    public int getCount() {        return list.size();    }    @Override    public Object getItem(int position) {        return list.get(position);    }    @Override    public long getItemId(int position) {        return position;    }    @Override    public View getView(int position, View convertView, ViewGroup parent) {        ViewDataBinding dataBinding;        if (convertView == null) {            dataBinding = DataBindingUtil.inflate(inflater, layoutId, parent, false);        } else {            dataBinding = DataBindingUtil.getBinding(convertView);        }        dataBinding.setVariable(variableId, list.get(position));        return dataBinding.getRoot();    }}

以上算是Adapter的通用写法了,如果按照此种方式来写Adapter适配器,那么如果没有非常奇葩的需求,在App中可能就只需这一个给ListView使用的Adapter了。为什么这么说呢?因为这个Adapter中没有一个变量和我们的ListView关联。里面的几个变量含义:layoutId这个表示item布局的资源idvariableId是系统自动生成的,可根据实体类直接从外部传入即可。最后再来看看Activity中的写法:

public class UsersActivity extends BaseActivity {    private UsersActivity mActivity;    private TitleView titleView;    private ListView mLvUsers;    private List<User> users;    private MyBaseAdapter<User> adapter;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_users);        mActivity = this;        initView();        initData();    }    private void initView() {        titleView = (TitleView) findViewById(R.id.titleView);        titleView.setAppTitle(UIUtils.getString(R.string.user_data));        titleView.setLeftImgOnClickListener();        mLvUsers = (ListView) findViewById(R.id.lv_users);    }    private void initData() {        if (users == null) {            users = new ArrayList<>();        }        for (int i = 0; i < 30; i++) {            users.add(new User("小明", "一花一世界", "http://pic.sc.chinaz.com/files/pic/pic9/201412/apic8065.jpg"));        }        if (adapter == null) {            adapter = new MyBaseAdapter<>(mActivity, R.layout.item_users, BR.user, users);            mLvUsers.setAdapter(adapter);        } else {            adapter.notifyDataSetChanged();        }    }}

在构造MyBaseAdapter的时候传入的variableId参数是BR中的,这个BR和我们项目中的R文件类似,都是系统自动生成的。至此,我们使用DataBinding方式给ListView加载数据就算完成了。

点击事件处理

如果你使用DataBinding,那么我们的点击事件也会有新的处理方式。在这里我们以ListView为例来说说如何绑定点击事件,在item_users布局文件中item的根节点添加如下代码:

<?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">    <data>        <import type="com.wiggins.mvvm.bean.User" />        <variable            name="user"            type="User" />    </data>    <LinearLayout        android:layout_width="match_parent"        android:layout_height="@dimen/item_large"        android:background="@color/white"        android:gravity="center_vertical"        android:onClick="@{user.onItemClick}"        android:orientation="horizontal"        android:paddingLeft="@dimen/padding_normal"        android:paddingRight="@dimen/padding_normal">        ......    </LinearLayout></layout>

LinearLayout容器添了onClick属性,其属性值为user.onItemClick,那么这个onItemClick到底是什么呢?其实就是在实体类User中定义的一个方法,如下:

public void onItemClick(View view) {    Toast.makeText(view.getContext(), getNickName(), Toast.LENGTH_SHORT).show();}

点击item获取当前position的数据提示,获取方式非常简单,直接调用get方法获取即可,比传统ListView的点击事件通过position来获取数据方便多了。如果想为昵称这个TextView添加点击事件也很简单,与上面使用方式一样。

数据更新处理

单纯的更新User对象并不能改变ListViewUI显示效果,那应该怎么做呢?Google给我们提供了三种解决方案,分别如下:

1、让实体类继承BaseObservable

让实体类继承BaseObservable,然后给需要改变字段的get方法添加上@Bindable注解,给需要改变字段的set方法加上notifyPropertyChanged(BR.userName);即可。比如我想在点击item的时候把nickName字段的数据改为”我爱西红柿”,可以修改User类为下面的样子:

public class User extends BaseObservable {    private String userName;    private String nickName;    private String userAvatar;    public User(String userName, String nickName, String userAvatar) {        this.userName = userName;        this.nickName = nickName;        this.userAvatar = userAvatar;    }    @BindingAdapter("bind:userAvatar")    public static void getAvatarImage(ImageView iv, String userAvatar) {        Picasso.with(iv.getContext())                .load(userAvatar)                .into(iv);    }    public void onItemClick(View view) {        setNickName("我爱西红柿");    }    public String getUserName() {        return userName;    }    public void setUserName(String userName) {        this.userName = userName;    }    @Bindable    public String getNickName() {        return nickName;    }    public void setNickName(String nickName) {        this.nickName = nickName;        notifyPropertyChanged(BR.nickName);    }    public String getUserAvatar() {        return userAvatar;    }    public void setUserAvatar(String userAvatar) {        this.userAvatar = userAvatar;    }    @Override    public String toString() {        return "User{" +                "userName='" + userName + '\'' +                ", nickName='" + nickName + '\'' +                ", userAvatar='" + userAvatar + '\'' +                '}';    }}

这是第一种解决方案,也是比较简单常用的一种。

2、使用DataBinding提供的ObservableField来创建实体类

这种方式使用起来略微麻烦,除了继承BaseObservable之外,创建属性的方式也变成了下面这种形式:

private ObservableField<String> userName = new ObservableField<>();

属性的读写方式也改变了,读取方式如下:

userName.get();

写入方式如下:

this.userName.set(userName);

依据上面规则定义实体类如下:

public class User extends BaseObservable {    private ObservableField<String> userName = new ObservableField<>();    private ObservableField<String> nickName = new ObservableField<>();    private ObservableField<String> userAvatar = new ObservableField<>();    public User(String userName, String nickName, String userAvatar) {        this.userName.set(userName);        this.nickName.set(nickName);        this.userAvatar.set(userAvatar);    }    @BindingAdapter("bind:userAvatar")    public static void getAvatarImage(ImageView iv, String userAvatar) {        Picasso.with(iv.getContext())                .load(userAvatar)                .into(iv);    }    public void onItemClick(View view) {        Toast.makeText(view.getContext(), getNickName(), Toast.LENGTH_SHORT).show();    }    public String getUserName() {        return userName.get();    }    public void setUserName(String userName) {        this.userName.set(userName);    }    public String getNickName() {        return nickName.get();    }    public void setNickName(String nickName) {        this.nickName.set(nickName);    }    public String getUserAvatar() {        return userAvatar.get();    }    public void setUserAvatar(String userAvatar) {        this.userAvatar.set(userAvatar);    }    @Override    public String toString() {        return "User{" +                "userName='" + userName + '\'' +                ", nickName='" + nickName + '\'' +                ", userAvatar='" + userAvatar + '\'' +                '}';    }}

这种方式实现的功能和第一个实体类实现的功能一模一样。

3、使用DataBinding中提供的集合来存储数据

DataBinding中给我们提供了一些现成的集合用来存储数据,比如:ObservableArrayListObservableArrayMap等,因为使用的较少,这里就不做介绍了。

高级用法

1、使用类方法

首先为类添加一个静态方法:

public class StringUtil {    public static boolean isEmpty(String value) {        if (value != null && !"".equalsIgnoreCase(value.trim()) && !"null".equalsIgnoreCase(value.trim())) {            return false;        } else {            return true;        }    }}

然后在xmldata节点中导入:

<import type="com.wiggins.mvvm.utils.StringUtil" />

使用方法与Java语法一样:

<TextView    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:gravity="center"    android:text="@{!StringUtil.isEmpty(`小明`)?`小明`:`小花`}"    android:textColor="@color/blue"    android:textSize="@dimen/font_normal" />

2、类型别名

如果我们在data节点导入了两个同名的类怎么办?

<data>    <import type="com.wiggins.mvvm.bean.User" />    <import type="com.wiggins.mvvm.data.User" />    <variable        name="user"        type="User" /></data>

这样一来出现了两个User类,那么user变量到底要使用哪一个呢?不用担心,在import中还有一个alias属性,此属性表示可以给该类取一个别名,比如可以给User这个实体类取一个别名叫做Lenve,这样就可以在variable节点中直接写Lenve了。

<data>    <import type="com.wiggins.mvvm.bean.User" />    <import type="com.wiggins.mvvm.data.User" alias="Lenve" />    <variable        name="user"        type="User" />    <variable        name="lenve"        type="Lenve" /></data>

3、Null Coalescing运算符

android:text="@{user.userName ?? user.nickName}"

等价于:

android:text="@{user.userName != null ? user.userName : user.nickName}"

4、属性值

通过@{}可以直接把Java中定义的属性值赋值给xml属性。

<TextView    android:layout_width="match_parent"    android:layout_height="@dimen/item_normal"    android:text="@{user.userName}"    android:textColor="@color/white"    android:textSize="@dimen/font_normal"    android:visibility="@{user.isShow ? View.VISIBLE : View.GONE}" />

5、使用资源数据

5.1、布局文件

<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android">    <data class="ResourceBinding">        <variable            name="large"            type="boolean" />        <variable            name="firstName"            type="String" />        <variable            name="lastName"            type="String" />        <variable            name="bananaCount"            type="int" />        <variable            name="orangeCount"            type="int" />    </data>    <LinearLayout        android:layout_width="match_parent"        android:layout_height="match_parent"        android:background="@color/theme_bg"        android:orientation="vertical">        <com.wiggins.mvvm.widget.TitleView            android:id="@+id/titleView"            android:layout_width="match_parent"            android:layout_height="wrap_content" />        <TextView            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:gravity="center"            android:padding="@{large ? (int)@dimen/largePadding : (int)@dimen/smallPadding}"            android:text="@string/title"            android:textColor="@color/blue"            android:textSize="@dimen/font_normal" />        <TextView            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:gravity="center"            android:padding="@dimen/padding_normal"            android:text="@{@string/nameFormat(firstName, lastName)}"            android:textColor="@color/blue"            android:textSize="@dimen/font_normal" />        <TextView            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:gravity="center"            android:padding="@dimen/padding_normal"            android:text="@{@plurals/banana(bananaCount)}"            android:textColor="@color/blue"            android:textSize="@dimen/font_normal" />        <TextView            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:gravity="center"            android:padding="@dimen/padding_normal"            android:text="@{@plurals/orange(orangeCount, orangeCount)}"            android:textColor="@color/blue"            android:textSize="@dimen/font_normal" />    </LinearLayout></layout>

largePaddingsmallPadding都是定义在dimens.xml文件中的资源数据。

5.2、dimens.xml

<dimen name="largePadding">15dp</dimen><dimen name="smallPadding">5dp</dimen>

5.3、strings.xml

<string name="nameFormat">Full Name : %1$s : %2$s</string><plurals name="banana">    <item quantity="zero">zero bananas</item>    <item quantity="one">one banana</item>    <item quantity="two">two bananas</item>    <item quantity="few">few bananas</item>    <item quantity="many">many bananas</item>    <item quantity="other">other bananas</item></plurals><plurals name="orange">    <item quantity="one">Have an orange</item>    <item quantity="other">Have %d oranges</item></plurals>

5.4、绑定variable

@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    ResourceBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_resource);    binding.setLarge(false);    binding.setFirstName("小明");    binding.setLastName("小花");    binding.setBananaCount(2);    binding.setOrangeCount(10);}

6、消除空指针

自动生成的DataBinding代码会检查null,避免出现NullPointerException。例如在表达式中@{user.userName}如果usernull,那么会为user.userName设置默认值null,而不会导致程序崩溃(基本类型将赋予默认值如int赋值为0,引用类型赋值null)。

7、自定义DataBinding名

如果不喜欢自动生成的DataBinding名,我们可以自己来定义:

<data class="ResourceBinding">    ......</data>

class对应的就是生成的DataBinding名称。

8、导包

Java中的用法相似,布局文件中支持import的使用,原来的代码是这样:

<data>    <variable        name="user"        type="com.wiggins.mvvm.bean.User" /></data>

使用import后可以写成这样:

<data>    <import type="com.wiggins.mvvm.bean.User" />    <variable        name="user"        type="User" /></data>

当需要用到一些包时需要使用import导入这些包后才能使用。如需要用到View的时候:

<data>    <import type="android.view.View" /></data><TextView    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:visibility="@{user.isShow ? View.VISIBLE : View.GONE}" />

注意:只要是在Java中需要导入包的类,在这里都需要导入,如:MapArrayList等,不过java.lang包里的类是可以不用导包的。

9、表达式

 <TextView    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:text='@{user.isShow ? "小明" : "小花"}' />

注意:需要用到双引号的时候,外层的双引号改成单引号。

10、调用类中的变量

例如在MainActivity中定义userName

public static String userName = "小明";

布局中:

<data>    <variable        name="mainActivity"        type="com.wiggins.mvvm.view.MainActivity" /></data><TextView    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:text="@{mainActivity.userName}" />

注意:这个变量必须是public static类型。

缺点

1、数据绑定使得Bug很难被调试。比如你看到界面异常了,有可能是你View的代码有Bug,也可能是Model的代码有问题。数据绑定使得一个位置的Bug被快速传递到别的位置,要定位原始出问题的地方就变得不那么容易了。
2、对于过大的项目,数据绑定需要花费更多的内存。

总结

Model层的职责就是获取数据的,网络请求的逻辑写在这里面。因此ViewModel层可以持有一个Model的引用,通知Model获取数据,同时Model在获取到数据之后,回调通知ViewModel进行数据更改,进而使UI得到更新。

View层做的是和UI相关的工作,我们只在XMLActivityFragmentView层的代码,View层不做和业务相关的事,也就是我们在Activity不写业务逻辑和业务数据相关的代码,更新UI通过数据绑定实现,尽量在ViewModel里面做。

ViewModel专注于业务的逻辑处理,只做和业务逻辑、业务数据相关的事,UI相关的事情不要写在这里面,ViewModel层不会持有任何控件的引用,更不会在ViewModel中通过UI控件的引用去做更新UI的事情。但是ViewModel可能会改变数据,由于数据和UI已经绑定在一起了,所以相应的控件会自动去更新UI

综上所述:View层的Activity通过DataBinding生成Binding实例,同时将这个实例传递给ViewModelViewModel层持有Model的引用获取数据并通过把自身与Binding实例绑定,从而实现ViewlayoutViewModel的双向绑定。如果不引入ViewModel这一层会有一个缺点:一个xml中可能会涉及到多个数据对象,那么只有把多个数据对象都引入进来,可能会导致xml布局的清晰程度下降。但是通过这种方法,我们layout文件中data标签里只需要引入ViewModel就可以了,其它的数据对象统一在ViewModel中一并处理。

Realm - Data Bindings

项目地址 ☞ 传送门

原创粉丝点击