简介
在开发中可能你使用过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中一并处理。
项目地址 ☞ 传送门
本文详细介绍了Android中的MVVM设计模式,包括基本用法、数据绑定、ImageView和ListView的绑定、点击事件处理、数据更新处理以及高级用法。重点讲解了DataBinding库的使用,如ViewModel、LiveData和Repository的角色,强调了MVVM如何实现视图和模型的解耦,以及其在大型项目中可能面临的调试困难和内存消耗问题。
1609

被折叠的 条评论
为什么被折叠?



