您的位置:首页 > 移动开发 > Android开发

Android设计模式之MVVM

2017-08-24 11:06 330 查看

简介

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

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





Model
:负责数据实现和逻辑处理。

View
:对应于
Activity
xml
,负责
View
的绘制以及与用户交互。

ViewModel
:创建关联,将
Model
View
绑定起来,如此之后
Model
更改后通过
ViewModel
反馈给
View
View
xml
布局文件经过特定的编写及编译工具处理后,生成的代码会接收
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、在对应
Module
build.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

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

@Override
protected 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
绑定之后,
xml
UI
元素就可以直接使用了。

<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" />


在布局文件中,
TextView
text
属性设置成了
@{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" />


两个
??
表示如果
userName
null
则显示
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 < 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
的时候可以给一个控件自定义一个属性,假如现在想要通过
DataBinding
Picasso
显示一张网络图片该怎么做呢?我们可以使用
@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
中的代码:

@Override
protected 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>


再来看看
ListView
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: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
布局的资源
id
variableId
是系统自动生成的,可根据实体类直接从外部传入即可。最后再来看看
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
对象并不能改变
ListView
UI
显示效果,那应该怎么做呢?
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
中给我们提供了一些现成的集合用来存储数据,比如:
ObservableArrayList
ObservableArrayMap
等,因为使用的较少,这里就不做介绍了。

高级用法

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;
}
}
}


然后在
xml
data
节点中导入:

<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>


largePadding
smallPadding
都是定义在
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

@Override
protected 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}
如果
user
null
,那么会为
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
中需要导入包的类,在这里都需要导入,如:
Map
ArrayList
等,不过
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
相关的工作,我们只在
XML
Activity
Fragment
View
层的代码,
View
层不做和业务相关的事,也就是我们在
Activity
不写业务逻辑和业务数据相关的代码,更新
UI
通过数据绑定实现,尽量在
ViewModel
里面做。

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


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

Realm - Data Bindings

项目地址 ☞ 传送门
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: