高效开发 MVVM 和 databinding 你需要使用的工具

阅读本篇大概需要 11 分钟。

相信不少同学已经开始使用 MVVM 作为自己 Android 开发架构了,但实际上,我在使用过程中查阅资料发现,网上有关 MVVM 的资料并不是很多,这主要是因为 MVVM 还是有一定使用门槛的,并且 MVVM 不一定会帮助你提高开发效率,可能你需要写的代码更多了,或者说为了你为了让代码保持 Databinding 的双向绑定特性,而需要考虑很多业务以外的设计逻辑。我们使用一个架构或者设计模式,当然是为了更好的开发体验嘛,所以我将给大家介绍几个实用的第三方库和工具,来帮助大家解决这些问题。

1. MVVMLight

「MVVMLight」这个第三方库实际上是对 Databinding 工具库的一些扩展,并且通过 ReplyCommand 和 ResponseCommand 来对所有的 View 的事件进行统一封装,这是我认为 MVVMLight 最大的用处。

博客 : http://www.jianshu.com/p/43ea7a531700

源码 : https://github.com/kelin-Hong/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((v)->{
           listener.onClick(v);
       });
   }
}

interface ImageOnClickListener{    
    void onClick(View v);
}

使用的时候呢,你得在 VM 中定义一个 ImageOnClickListener的成员变量 listener,在里面写具体的 onClick 实现方法,然后在 xml 中通过 app:setImageOnClick="viewModel.listener" 来绑定这个事件。

当然,你可以直接通过 android:onClick 来进行绑定一个 VM 方法,这里只是实例。

看起来好像也不是很麻烦,但是你可能每一个这样的事件,就得定义一个特殊的接口,我们能不能封装一下呢?

这就是这两个 Command 做的事了。通过这两个类封装了各种请求参数数量和返回值参数数量的回调方法,在使用的时候,只要在泛型里具体指名请求参数和返回值的类型即可,可以说很方便了。

实例,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 是没有返回值的,具体的使用方法,大家可以参考上面的链接,作者自己讲的最详细。

2. binding-collection-adapter

「binding-collection-adapter」对所有需要adapter的控件进行了封装,比如一些常用的:ListView、RecyclerView、ViewPager 等,通过使用这个库,我们就不需要再写 adapter 了,通过 databinding 的方式,在 xml 绑定一些属性,并在 ViewModel 中对这些属性进行处理即可完成这些控件的处理,逻辑清晰,代码简单。

GitHub : https://github.com/evant/binding-collection-adapter

下面举一个 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 是干什么用的,我们知道有时候列表项是可能多布局的,那么这个 itemBinding 就是用来处理每种布局和对应 item 的 ViewModel 的绑定关系的。上述代码的 ViewModel 中,定义了该 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);

map 方法中有三个参数,第一个参数是这个布局的 ViewModel,第三个参数是这个布局的 xml 文件,第二个参数这个 xml 中引入的 ViewModel 的 BR 文件 id。这样我们就绑定好了这个列表控件的多布局逻辑了。一个空数据时候的布局,一个正常返回数据时候的布局。

那么我们的数据是如何刷新的呢,这就要用到上面的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 的繁琐中解放出来了!

3. Databinding support

这是一个 Android Studio 插件,我们写 xml 中的一些 Databind 代码比如 <layout>、<data>、<variable>、<import> 等标签的使用还是比较多的,而且写起来也比较繁琐,这个插件就是可以帮助你解放双手,只需要在适当的地方按 ⌥+⏎ (Windows 是 Alt+Enter) 即可,从官网盗几张 Gif 图给大家感受一下吧。

Wrap with <layout></layout>

Add <data> tag

Wrap with @{}

Wrap with @={}

Switch @{} and @={}

Add <import>

Add <variable>

4. MVVM 自动代码生成

MVVM 和 MVP 这种架构并不一定会让我们代码量减少,每一个界面可能都要以一种固定的模式创建很多类,那我们为什么不通过一种自动代码生成工具来通过简单的配置就完成这些类的创建呢,Java 完全就可以实现这些功能。网上有很多用 Java 实现的自动生成代码的方式,但每个人实现的 MVP 和 MVVM 架构方式都不同,所以自动化代码也会不同,我来展示下我这边使用的过程吧。

我使用的 MVVM 代码生成工具的主要思路是比较简单粗暴的,通过一个 xml 文件配置一些属性,比如起一个名字,设置一下文件输出的路径,然后在 Java 里用字符串拼接和文件流读取的方式来生成模板代码。

原文发布于微信公众号 - WeaponZhi(WeaponZhi)

原文发表时间:2017-11-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏技术墨客

React——Flow代码静态检查 转

Flow是Facebook开源的静态代码检查工具,他的作用是在运行代码之前对React组件以及Jsx语法进行静态代码的检查以发现一些可能存在的问题。Flow可以...

371
来自专栏Java工程师日常干货

纯手写实现JDK动态代理前言JDK动态代理 手写代码实现JDK动态代理

在Java领域,动态代理应用非常广泛,特别是流行的Spring/MyBatis等框架。JDK本身是有实现动态代理技术的,不过要求被代理的类必须实现接口,不过cg...

752
来自专栏Phoenix的Android之旅

其实热修复就这么简单

上几篇内容介绍了Java的ClassLoader和相关的知识点,总的来说 · Java加载class逻辑是双亲委托模式 · 对于不在class path中的cl...

651
来自专栏Java进阶架构师

手把手带你实现JDK动态代理

业务接口Interface、业务实现类target、业务处理类Handler、JVM在内存中生成的动态代理类$Proxy0

671
来自专栏漫漫前端路

记录面试中一些回答不够好的题(Vue 居多)

grid 学习:https://www.jianshu.com/p/d183265a8dad

1312
来自专栏WeaponZhi

MVVM 面向接口型框架封装和单元测试

大家好,今天给大家带来一个我自己开发改造的 MVVM 封装框架。代码不难,但我更想说一些我在开发这样一个架构过程中的想法和思路,我们不仅要善于作一个搬运工,更要...

3766
来自专栏叁金大数据

自学Python四 爬虫基础知识储备

  首先,推荐两个关于python爬虫不错的博客:Python爬虫入门教程专栏   和 Python爬虫学习系列教程 。写的都非常不错,我学习到了很多东西!在此...

691
来自专栏技术墨客

React学习(7)—— 高阶应用:性能优化 原

在React内部已经使用了许多巧妙的技术来最小化由于Dom变更导致UI渲染所耗费的时间。对于很多应用来说,使用React后无需太多工作就会让客户端执行性能有质的...

772
来自专栏salesforce零基础学习

salesforce零基础学习(八十九)使用 input type=file 以及RemoteAction方式上传附件

在classic环境中,salesforce提供了<apex:inputFile>标签用来实现附件的上传以及内容获取。salesforce 零基础学习(二十四)...

741
来自专栏spring源码深度学习

重拾python爬虫之urllib

学习一门技术,总是要踩好多坑,然后收货一大堆疑惑,这么多相似的方式该学哪个呢?外面公司常用的是哪个呢? 就比如python爬虫,可以作为网络请求的方式有四种,...

832

扫码关注云+社区