Android 基于MVC的MVVM模式开发
由skay整理 http://blog.csdn.net/sk719887916/article/details/50386144
什么是MVVM
我们一步步来,从MVC开始。
MVC 我们都知道,模型——视图——控制器。为了使得程序的各个部分分离降低耦合性,我们对代码的结构进行了划分。
他们的通信方式也如上图所示,即View层触发操作通知到业务层完成逻辑处理,业务层完成业务逻辑之后通知Model层更新数据,数据更新完之后通知View层展现。在实际运用中人们发现View和Model之间的依赖还是太强,希望他们可以绝对独立的存在,慢慢的就演化出了MVP。
Presenter 替换掉了Controller,不仅仅处理逻辑部分。而且还控制着View的刷新,监听Model层的数据变化。这样隔离掉View和Model的关系后使得View层变的非常的薄,没有任何的逻辑部分又不用主动监听数据,被称之为“被动视图”。
至于MVVM基本上和MVP一模一样,感觉只是名字替换了一下。他的关键技术就是今天的主题(Data Binding)。View的变化可以自动的反应在ViewModel,ViewModel的数据变化也会自动反应到View上。这样开发者就不用处理接收事件和View更新的工作,框架已经帮你做好了。
Data Binding Library
今年的Google IO 大会上,Android 团队发布了一个数据绑定框架(Data Binding Library)。以后可以直接在 layout 布局 xml 文件中绑定数据了,无需再 findViewById 然后手工设置数据了。其语法和使用方式和 JSP 中的 EL 表达式非常类似。
下面就来介绍怎么使用Data Binding Library。
配置环境
目前,最新版的Android Studio已经内置了该框架的支持,配置起来也很简单,只需要编辑app目录下的build.gradle文件,添加下面的内容就好了
android {
....
dataBinding {
enabled = true
}
}
Data Binding Layout文件
Data Binding layout文件有点不同的是:起始根标签是 layout,接下来一个 data 元素以及一个 view 的根元素。这个 view 元素就是你没有使用Data Binding的layout文件的根元素。举例说明如下:
<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"/>
</LinearLayout></layout>
上面定义了一个com.example.User
类型的变量user,然后接着android:text="@{user.firstName}"
把变量user的firstName属性的值和TextView的text属性绑定起来。
Data Object
我们来看下上面用到的com.example.User
对象。
public class User {
public final String firstName;
public final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
他有两个public的属性firstName,lastName,这和上面layout文件里面的@{user.firstName}
和@{user.lastName}
对应
或者下面这种形式的对象也是支持的。
public class User {
private final String firstName;
private final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// getXXX形式
public String getFirstName() {
return this.firstName;
}
// 或者属性名和方法名相同
public String lastName() {
return this.lastName;
}
}
绑定数据
添加完<data>
标签后,Android
Studio就会根据xml的文件名自动生成一个继承ViewDataBinding
的类。例如:activity_main.xml
就会生成ActivityMainBinding
,
然后我们在Activity里面添加如下代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
User user = new User("Test", "User");
binding.setUser(user);
}
绑定事件
就像你可以在xml文件里面使用属性android:onClick
绑定Activity
里面的一个方法一样,Data
Binding Library扩展了更多的事件可以用来绑定方法,比如View.OnLongClickListener
有个方法onLongClick()
,
你就可以使用android:onLongClick
属性来绑定一个方法,需要注意的是绑定的方法的签名必须和该属性原本对应的方法的签名完全一样,否则编译阶段会报错。
下面举例来说明具体怎么使用,先看用来绑定事件的类:
public class MyHandlers {
public void onClickButton(View view) { ... }
public void afterFirstNameChanged(Editable s) { ... }
}
然后就是layout文件:
<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.Handlers"/>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"
android:afterTextChanged="@{handlers.afterFirstNameChanged}"/>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{handlers.onClickButton}"/>
</LinearLayout></layout>
表达式语言(Expression Language)
你可以直接在layout文件里面使用常见的表达式:
数学表达式 + – / * %
字符串链接 +
逻辑操作符 && ||
二元操作符 & | ^
一元操作符 + – ! ~
Shift >> >>> <<
比较 == > < >= <=
instanceof
Grouping ()
Literals – character, String, numeric, null
Cast
函数调用
值域引用(Field access)
通过[]访问数组里面的对象
三元操作符 ?:
示例:android:text="@{String.valueOf(index + 1)}" android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}" android:transitionName='@{"image_" + id}'
更多语法可以参考官网文档:http://developer.android.com/tools/data-binding/guide.html#expression_language
更新界面
有些时候,代码会修改我们绑定的对象的某些属性,那么怎么通知界面刷新呢?下面就给出两种方案。
方案一
让你的绑定数据类继承BaseObservable,然后通过调用notifyPropertyChanged方法来通知界面属性改变,如下:
private static class User extends BaseObservable {
private String firstName;
private String lastName;
@Bindable
public String getFirstName() {
return this.firstName;
}
@Bindable
public String getLastName() {
return this.lastName;
}
public void setFirstName(String firstName){
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
public void setLastName(String lastName) { this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
}
在需要通知的属性的get方法上加上@Bindable,这样编译阶段会生成BR.[property name],然后使用这个调用方法notifyPropertyChanged就可以通知界面刷新了。如果你的数据绑定类不能继承BaseObservable,那你就只能自己实现Observable接口,可以参考BaseObservable的实现。
方案二
Data Binding Library提供了很便利的类ObservableField,还有ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, 和 ObservableParcelable,基本上涵盖了各种我们需要的类型。用法很简单,如下:
private static class User {
public final ObservableField<String> firstName = new ObservableField<>();
public final ObservableField<String> lastName = new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
然后使用下面的代码来访问:
user.firstName.set("Google");int age = user.age.get();
调用set方法时,Data Binding Library就会自动的帮我们通知界面刷新了。
绑定AdapterView
在一个实际的项目中,相信AdapterView是使用得很多的,使用官方提供给的API来进行AdapterView的绑定需要写很多代码,使用起来不方便,但是由于Data Binding Library提供丰富的扩展功能,所以出现了很多第三方的库来扩展它,下面就来介绍一个比较好用的库binding-collection-adapter,Github地址:https://github.com/evant/binding-collection-adapter
使用的时候在你的build.gradle
文件里面添加
compile
'me.tatarka:bindingcollectionadapter:0.16'
如果你要是用RecyclerView,还需要添加
compile
'me.tatarka:bindingcollectionadapter-recyclerview:0.16'
下面就是ViewModel的写法:
public class ViewModel {
public final ObservableList<String> items = new ObservableArrayList<>();
public final ItemView itemView = ItemView.of(BR.item, R.layout.item);
}
这里用到了ObservableList
,
他会在items变化的时候自动刷新界面
然后下面是layout文件:
<!-- layout.xml --><layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable name="viewModel" type="com.example.ViewModel"/>
<import type="me.tatarka.bindingcollectionadapter.LayoutManagers" />
</data>
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:items="@{viewModel.items}"
app:itemView="@{viewModel.itemView}"/>
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="@{LayoutManagers.linear()}"
app:items="@{viewModel.items}"
app:itemView="@{viewModel.itemView}"/>
<android.support.v4.view.ViewPager
android:layout_width="match_parent"
android:layout_height="match_parent"
app:items="@{viewModel.items}"
app:itemView="@{viewModel.itemView}"/>
<Spinner
android:layout_width="match_parent"
android:layout_height="match_parent"
app:items="@{viewModel.items}"
app:itemView="@{viewModel.itemView}"
app:dropDownItemView="@{viewModel.dropDownItemView}"/></layout>
然后是item layout:
<!-- item.xml --><layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable name="item" type="String"/>
</data>
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{item}"/></layout>
如果有多种样式的布局,那么就需要把ItemView
换成ItemViewSelector
,
如下:
public final ItemViewSelector<String> itemView = new BaseItemViewSelector<String>() {
@Override
public void select(ItemView itemView, int position, String item) {
itemView.set(BR.item, position == 0 ? R.layout.item_header : R.layout.item);
}
// This is only needed if you are using a BindingListViewAdapter
@Override
public int viewTypeCount() {
return 2;
}
};
自定义绑定
正常情况下,Data Binding Library会根据属性名去找对应的set方法,但是我们有时候需要自定义一些属性,Data Binding Library也提供了很便利的方法让我们来实现。
比如我们想在layout文件里面设置ListView的emptyView,以前这个是无法做到的,只能在代码里面通过调用setEmptyView来做;
但是现在借助Data Binding Library,我们可以很容易的实现这个功能了。先看layout文件:
<?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="viewModel"
type="com.example.databinding.viewmodel.ViewAlbumsViewModel"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:orientation="vertical">
<ListView
android:layout_width="fill_parent"
android:layout_height="0px"
android:layout_weight="1.0"
app:items="@{viewModel.albums}"
app:itemView="@{viewModel.itemView}"
app:emptyView="@{@id/empty_view}"
android:onItemClick="@{viewModel.viewAlbum}"
android:id="@+id/albumListView"/>
<TextView
android:id="@+id/empty_view"
android:layout_width="fill_parent"
android:layout_height="0px"
android:layout_weight="1.0"
android:gravity="center"
android:text="@string/albums_list_empty" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/create"
android:onClick="@{viewModel.createAlbum}"/>
</LinearLayout></layout>
app:emptyView="@{@id/empty_view}"
这个代码就用来指定emptyView,下面来看下实现的代码:
@BindingAdapter("emptyView")
public static <T> void setEmptyView(AdapterView adapterView, int viewId) {
View rootView = adapterView.getRootView();
View emptyView = rootView.findViewById(viewId);
if (emptyView != null) {
adapterView.setEmptyView(emptyView);
}
}
下面我们来分析上面的代码,@{@id/empty_view}
表示引用了@id/empty_view这个id,所以它的值就是int,再看上面的setEmptyView方法,第一个参数AdapterView
表示使用emptyView这个属性的控件,而第二个参数
adapterViewint
则是emptyView属性传进来的值,上面的layout可以看出来它就是
viewIdR.id.empty_view
,然后通过id找到控件,然后调用原始的setEmptyView来设置。
它基本上包含了开发一个app常用到的东西,大家有兴趣可以通过阅读原文去看看。
原文出自:胡笛同学
Android 基于MVC的MVVM模式开发的更多相关文章
- 浅析前端开发中的 MVC/MVP/MVVM 模式
MVC,MVP和MVVM都是常见的软件架构设计模式(Architectural Pattern),它通过分离关注点来改进代码的组织方式.不同于设计模式(Design Pattern),只是为了解决一类 ...
- 用MVVM模式开发中遇到的零散问题总结(4)——自制摄像头拍摄大头贴控件
原文:用MVVM模式开发中遇到的零散问题总结(4)--自制摄像头拍摄大头贴控件 一直有个疑问,为什么silverlight对摄像头支持这么好,WPF却一个库都没有....于是我各种苦恼啊,各种Code ...
- 用MVVM模式开发中遇到的零散问题总结(5)——将动态加载的可视元素保存为图片的控件,Binding刷新的时机
原文:用MVVM模式开发中遇到的零散问题总结(5)--将动态加载的可视元素保存为图片的控件,Binding刷新的时机 在项目开发中经常会遇到这样一种情况,就是需要将用户填写的信息排版到一张表单中,供打 ...
- 用MVVM模式开发中遇到的零散问题总结(2)
原文:用MVVM模式开发中遇到的零散问题总结(2) 本节目录: 1.解决动画属性被劫持问题 2.设置页面焦点默认所在对象 3.XAML模拟键盘按键 4.DataGrid数据源绑定到复杂格式(dynam ...
- 用MVVM模式开发中遇到的零散问题总结(3)——自制正则表达式万能绑定转换器
原文:用MVVM模式开发中遇到的零散问题总结(3)--自制正则表达式万能绑定转换器 前言 最近接受了3个项目的洗礼,出差近3个月,各种北京.广州.昆明来回奔波,好久没写博客了,之前我觉得我遇到的问题都 ...
- Android MVC,MVP,MVVM模式入门——重构登陆注册功能
一 MVC模式: M:model,业务逻辑 V:view,对应布局文件 C:Controllor,对应Activity 项目框架: 代码部分: layout文件(适用于MVC和MVP两个Demo): ...
- mvc和mvvm模式
一. Mvvm定义 MVVM是Model-View-ViewModel的简写.即模型-视图-视图模型.[模型]指的是后端传递的数据.[视图]指的是所看到的页面.[视图模型]mvvm模式的核心,它是连接 ...
- IOS的MVC和MVVM模式简明介绍
iOS中的MVC(Model-View-Controller)将软件系统分为Model.View.Controller三部分,结构图如下: Model: 你的应用本质上是什么(但不是它的展示方式) C ...
- 第四十六课:MVC和MVVM的开发区别
实现MVC的目的就是为了让M和V相分离.前端的MVC无法做到View和Model的相分离,而MVVM可以. 我们先来看一个用MVC模式开发的经典例子:(一定要深入了解这种开发的思想,而不是看懂代码) ...
随机推荐
- React 关于组件(界面)更新
在最近在学 React , 将组件的UI更新稍微整理了一下.根据业务要求,可能会出现如下的技术实现要求:1.更新自己2.更新子组件3.更新兄弟组件4.更新父组件5.父 call 子 function ...
- ignorable tips
枚举 索引从0开始 sort 默认升序排列 Array.Sort(intSort); //复制数组 Array.Copy(intSort,intNew,3); intsort 源数组 intnew ...
- 一口一口吃掉Hibernate(六)——多对多关联映射
今天来说说hibernate中的多对多关联映射,多对多关联映射涉及到单向映射和双向映射2种. 首先举个多对多关联例子:用户User和角色Role,一个用户可以属于多个角色,一个角色可以有多个用户.这就 ...
- ionic3-ng4学习见闻--(多环境方案)
搜了很久,很难找到一个详细入微,开箱即用的方案. 于是我 百折不挠的,搞出来一个,也不知道是不是最完美的方案,有什么可以优化的地方可以指出,谢谢. 首先, 1.项目目录下(与src平级),新增conf ...
- 基于PHP的快递查询免费开放平台案例-快宝开放平台
快递查询是快递业务中极其重要的业务,免费的快递查询开放平台:快宝开放平台. 快宝开放平台:http://open.kuaidihelp.com/home,已经对接100多家快递公司,实现快递物流信息实 ...
- 独立完成一个移动点餐wap后的小结
1.技术栈:vue vue-router vuex Mint-ui better-scroll; 2.实践总结: a.单页应用不重新渲染组件问题:组件在初次渲染后不会重新渲染,此时当从某个路径 ...
- sourceTree+gerrit管理代码
第一次接触gerrit,会对这种代码管理方式非常排斥,尤其是习惯了用sourceTree配合git进行代码管理的同学.不爽归不爽,代码还得写,我们的目标是让开发过程爽起来. 关于gerrit的知识,移 ...
- Go 语言结构体
Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型. 结构体是由一系列具有相同类型或不同类型的数据构成的数据集合. 结构体表示一项记录,比如保存图书馆的书籍记录,每 ...
- docker volume创建、备份、nfs存储
docker存储volume #环境 centos7.4 , Docker version 17.12.0-ce docker volume创建.备份.nfs存储 #docker volume 数据存 ...
- 项目分享:通过使用SSH框架的公司-学员关系管理系统(CRM)
----------------------------------------------------------------------------------------------[版权申明: ...