Android设计模式之MVVM
来源:互联网 发布:守望先锋左上角6个数据 编辑:程序博客网 时间:2024/05/19 18:44
简介
在开发中可能你使用过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
进行绑定。
@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
绑定之后,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
中的代码:
@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>
再来看看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
@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}
如果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设计模式之MVVM
- 设计模式之 MVVM
- android设计模式MVVM
- Android DataBinding(MVVM设计模式)
- android中mvvm设计模式
- Android设计模式MVVM之DataBinding简单使用
- UI设计模式之:MVVM模式经典
- android UI设计MVVM设计模式
- iOS开发之MVVM设计模式
- Android DataBinding库(MVVM设计模式)
- 【Android】DataBinding库(MVVM设计模式)
- Android DataBinding库(MVVM设计模式)
- 学习android的MVVM设计模式
- Android DataBinding库(MVVM设计模式)
- Android DataBinding库(MVVM设计模式)
- android设计模式(MVC MVP MVVM)
- 《Android源码设计模式》读书笔记 (25) 第25章 Android架构之MVC,MVP,MVVM
- Android开发模式MVVM之DataBinding
- Unexpected exception parsing XML document from class path resource
- Groovy语法之类
- POI各Jar包的作用
- 字符串
- android AIDL
- Android设计模式之MVVM
- 工厂模式
- 中缀表达式转后缀表达式--Java
- 只有一行VNC server running on ’::1:5900' 没有其他输出
- 成功在MP4封装的H264视频中提取能播放的裸流
- 廖雪峰《python3 基础教程》读书笔记——第二十章 访问数据库
- javascript-遍历
- 设计模式之代理模式
- HalconMFC混合编程