相信不少同学已经开始使用
- MVVM
作为自己 Android 开发架构了,但实际上,我在使用过程中查阅资料发现,网上有关 MVVM 的资料并不是很多,这主要是因为 MVVM 还是有一定使用门槛的,并且 MVVM 不一定会帮助你提高开发效率,可能你需要写的代码更多了,或者说为了你为了让代码保持 Databinding 的双向绑定特性,而需要考虑很多业务以外的设计逻辑。我们使用一个架构或者设计模式,当然是为了更好的开发体验嘛,所以我将给大家介绍几个实用的第三方库和工具,来帮助大家解决这些问题。
「MVVMLight」这个第三方库实际上是对 Databinding 工具库的一些扩展,并且通过
和
- ReplyCommand
来对所有的 View 的事件进行统一封装,这是我认为 MVVMLight 最大的用处
- ResponseCommand
MVVMLight的官方介绍博客
MVVMLight的源码地址
我们来看一下
怎么用。我们用常见的下拉刷新控件
- ReplyCommand
来举例子。
- PullToRefreshLayout
我们知道如果你想自定义一个控件的事件,你需要使用
注解,比如
- @BindingAdapter
通过
- ImageView
属性直接根据地址下载图片并显示可以这样写:
- URL
- @BindingAdapter("bind:urlImage")
- public
- static
- void
- getInternetImage
- (ImageView iv, String userface)
- {
- Picasso.with(iv.getContext()).load(userface).into(iv);
- }
- <ImageView
- android:id="@+id/iv"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:urlImage="@{user.urlImage}"/>
这种情况往往是比较简单的,因为只是操作一个属性,但我们要自定义某一个事件该怎么办呢,比如我们要自定义
事件,那可能就得写接口了:
- onClick
- @BindingAdapter("setImageOnClick")
- public
- static
- void
- setImageOnClick
- (ImageView imageView, final ImageOnClickListener listener)
- {
- if (listener != null) {
- imageView.setOnClickListener(new View.OnClickListener() {
- @Override
- public
- void
- onClick
- (View v)
- {
- listener.onClick(v);
- }
- });
- }
- }
- interface ImageOnClickListener{
- void onClick(View v);
- }
使用的时候呢,你得在 VM 中定义一个
的成员变量
- ImageOnClickListener
,在里面写具体的
- listener
实现方法,然后在 xml 中通过
- onClick
来绑定这个事件。
- app:setImageOnClick="viewModel.listener"
当然,你可以直接通过
来进行绑定,这里只是实例。
- android:onClick
看起来好像也不是很麻烦,但是你可能每一个这样的事件,就得定义一个特殊的接口,我们能不能封装一下呢?
这就是 MVVMLight 中 ReplyCommand 和 ResponseCommand 做的事了。通过这两个类封装了各种请求参数数量和返回值参数数量的回调方法,在使用的时候,只要在泛型里具体指名请求参数和返回值的类型即可,可以说很方便了。
实例,PullToRefreshLayout 是一个刷新列表控件,我们通过使用
监听下拉刷新和上拉加载的监听器是这样写的:
- ReplyCommand
- @BindView(R.id.refresh_listview)
- PullToRefreshLayout pullToRefreshLayout;
- ...
- @BindingAdapter (value = {"onRefreshCommand", "onLoadCommand"}, requireAll = false)
- public static void onRefreshLoadCommand(
- final PullToRefreshLayout pullToRefreshLayout, final ReplyCommand onRefreshCommand, final ReplyCommand onLoadCommand) {
- pullToRefreshLayout.setOnRefreshListener(new PullToRefreshLayout.OnRefreshListener() {
- @Override
- public
- void
- onRefresh
- (PullToRefreshLayout pullToRefreshLayout)
- {
- if (onRefreshCommand != null) {
- onRefreshCommand.execute();
- }
- }
- @Override
- public
- void
- onLoadMore
- (PullToRefreshLayout pullToRefreshLayout)
- {
- if (onLoadCommand != null) {
- onLoadCommand.execute();
- }
- }
- });
- }
我们使用统一的ReplyCommand来处理控件的各种事件,这里使用的是无参无返回值的最简单的情况,我们在 ViewModel 和 xml 中的写法是和之前的接口差不多的:
- public final ReplyCommand onRefreshCommand = new ReplyCommand(() - >getPostData(true));
- public final ReplyCommand onLoadCommand = new ReplyCommand(() - >getPostData(true));
- <com.weapon.joker.lib.view.pullrefreshload.PullToRefreshLayout
- android:id="@+id/pull_refresh_layout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- app:onRefreshCommand="@{viewModel.onRefreshCommand}"
- app:onLoadCommand="@{viewModel.onLoadCommand}"/>
这样,我们所有事件的接口就统一了。ResponseCommand 和 ReplyCommand 的区别主要在,ResponseCommand 是用来定义那种有返回值的参数的,而 ReplyCommand 是没有返回值的,具体的使用方法,大家可以参考上面的链接,作者自己讲的最详细。
「binding-collection-adapter」对所有需要
的控件进行了封装,比如一些常用的:
- adapter
、
- ListView
、
- RecyclerView
等,通过使用这个库,我们就不需要再写 adapter 了,通过 databinding 的方式,在 xml 绑定一些属性,并在 ViewModel 中对这些属性进行处理即可完成这些控件的处理,逻辑清晰,代码简单。
- ViewPager
GitHub 地址
下面举一个 RecyclerView 的例子。我们现在 xml 中定义一个 RecyclerView 控件。
- <android.support.v7.widget.RecyclerView
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- app:layoutManager="@{LayoutManagers.linear()}"
- app:items="@{viewModel.items}"
- app:itemBinding="@{viewModel.itemBinding}"/>
我们看到有三个特殊的属性:
、
- layoutManager
、
- items
,这里的
- itemBinding
大家都比较熟悉了,参数是在开头的
- layoutManager
导入的,传入相关的类名即可。
- import
- <data>
- <variable
- name="viewModel"
- type="com.weaponzhi.test.ViewModel"/>
- <import type="com.weaponzhi.test.LayoutManagers"/>
- </data>
我们先来看一下
是干什么用的,我们知道有时候列表项是可能多布局的,那么这个
- itemBinding
就是用来处理每种布局和对应 item 的 ViewModel 的绑定关系的。上述代码的 ViewModel 中,定义了该
- itemBinding
- itemBinding
- public final OnItemBindClass<Object> itemBinding =
- new OnItemBindClass<>
- .map(NoDataViewModel.class,BR.noData,R.layout.listitem_no_data)
- .map(ItemViewModel.class,BR.itemVM,R.layout.listitem_page);
方法中有三个参数,第一个参数是这个布局的 ViewModel,第三个参数是这个布局的 xml 文件,第二个参数这个 xml 中引入的 ViewModel 的 BR 文件 id。这样我们就绑定好了这个列表控件的多布局逻辑了。一个空数据时候的布局,一个正常返回数据时候的布局。
- map
那么我们的数据是如何刷新的呢,这就要用到上面的
这个属性了,在我们这个例子里,它是这样定义的:
- items
- public final ObservableList < Object > viewModels = new ObservableArrayList < >();
当我们网络请求返回的时候,我们在数据回调里,通过对数据类型的处理,进行
的构造,最后只需要将构造好的对象一个个添加到这个
- ItemViewModel
数据结构中去,界面的刷新工作都在对应的
- ObservableList
里中进行处理,我们刚刚设置的
- ItemViewModel
在这时候就起作用了,当新增数据的时候,它会先判断这个更新数据的
- itemBinding
的数据类型,
- ItemViewModel
类型的,那么就使用
- NoDataViewModel.class
,
- R.layout.listitem_no_data
类型的,就使用
- ItemViewModel.class
。当然,其他的数据更新和删除操作,也会因为双向绑定而同步刷新。
- R.layout.listitem_page
我们完全从 Adapter 的繁琐中解放出来了!
这是一个 Android Studio 插件,我们写 xml 中的一些 Databind 代码比如
、
- <layout>
、
- <data>
、
- <variable>
等标签的使用还是比较多的,而且写起来也比较繁琐,这个插件就是可以帮助你解放双手,只需要在适当的地方按
- <import>
(Windows 是 Alt+Enter)即可,从官网盗几张 Gif 图给大家感受一下吧。
- ⌥+⏎
- <layout></layout>
tag
- <data>
- @{}
- @={}
and
- @{}
- @={}
- <import>
- <variable>
MVVM 和 MVP 这种架构并不一定会让我们代码量减少,每一个界面可能都要以一种固定的模式创建很多类,那我们为什么不通过一种自动代码生成工具来通过简单的配置就完成这些类的创建呢,Java 完全就可以实现这些功能。网上有很多用 Java 实现的自动生成代码的方式,但每个人实现的 MVP 和 MVVM 架构方式都不同,所以自动化代码也会不同,我来展示下我这边使用的过程吧。
我使用的 MVVM 代码生成工具的主要思路是比较简单粗暴的,通过一个 xml 文件配置一些属性,比如起一个名字,设置一下文件输出的路径,然后在 Java 里用字符串拼接和文件流读取的方式来生成模板代码。
我现在维护的一个项目中,使用了 MVVMLight 和 binding-collection-adapter 大家可以参考下。
GitHub 链接
期待您关注我的公众号:WeaponZhi
来源: https://juejin.im/post/59ffde2b6fb9a044fe45c03e