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显示效果,那应该怎么做呢?
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
项目地址 ☞ 传送门
相关文章推荐
- Android DataBinding库(MVVM设计模式)
- Android mvc,mvp , mvvm三种设计模式的选择实践
- 学习android的MVVM设计模式
- Android MVVM架构设计模式,从DataBinding开始
- Android DataBinding库(MVVM设计模式)
- android设计模式(MVC MVP MVVM)
- Android DataBinding库(MVVM设计模式)
- 【Android】DataBinding库(MVVM设计模式)
- Android DataBinding库(MVVM设计模式)
- android DataBinding库(MVVM设计模式)
- 设计模式笔记之三:Android DataBinding库(MVVM设计模式)
- Android架构设计---关于MVVM模式的探讨
- android设计模式MVVM
- android UI设计MVVM设计模式
- Android设计模式理解(mvc mvp mvvm)
- Android DataBinding(MVVM设计模式)
- Android架构设计---关于MVVM模式的探讨
- Android架构设计---关于MVVM模式的探讨
- 浅谈Android架构设计模式中MVC、MVP、MVVM
- 对Android中设计模式MVC,MVP,MVVM的简单理解