用代码手把手教你使用MVVM

源码请点击:github.com/shuaijia/Js…android

您还能够关注个人微信公众号——安卓干货营,与我交流和获取更多精彩内容。git

概述

说到Android MVVM,就会联想到DataBinding框架。然而二者的概念是不同的,不能混为一谈。MVVM是一种架构模式,而DataBinding是一个实现数据和UI绑定的框架,是构建MVVM模式的一个工具。github

网上关于MVVM框架的搭建和使用的文章不多,大多提到MVVM框架,就是在介绍DataBinding的使用。对于MVVM中各模块之间如何划分,如何定义,又是如何配合实现高度解耦的文章更是少之又少。你们看完后仍是一头雾水,只是对MVVM有个大概的了解,并不很清楚如何上手。数据库

接下来,咱们先认识什么是MVVM,而后再一步一步来设计整个MVVM框架。json

MVC、MVP简介

MVC、MVP和MVVM都是在安卓开发中常用的模式,咱们在认识MVVM以前先回顾一下MVC和MVP。bash

MVC

  • View:xml布局
  • Model:数据层,负责数据交互、存储和实体类定义
  • Controller:业务处理层

Android开发自己仍是比较符合MVC架构的,可是Android中纯粹做为View的XML视图功能太弱,咱们大量处理View的逻辑只能写在Activity中,这样Activity就充当了View和Controller两个角色,直接致使Activity中的代码臃肿、混乱,致使阅读困难、重用困难和维护困难。相信大多数Android开发者都遇到过一个Acitivty数以千行的代码状况吧!因此,更贴切的说法是,这个MVC结构最终其实只是一个Model-View(Activity:View&Controller)的结构。服务器

MVP

  • View:xml文件及对应的Activity或Fragment,负责界面展现和交互
  • Model:数据层,负责数据交互、存储和实体类定义
  • Presenter:负责View层和Model层之间的逻辑处理

前面咱们说,Activity充当了View和Controller两个角色,MVP就能很好地解决这个问题,其核心理念是经过一个抽象的View接口(不是真正的View层)将Presenter与真正的View层进行解耦。Persenter持有该View接口,对该接口进行操做,而不是直接操做View层。这样就能够把视图操做和业务逻辑解耦,从而让Activity成为真正的View层。微信

不足的是,MVP模式中定义了大量的接口,使得代码结构变大和复杂;MVP是UI和事件驱动,须要手动调用大量的方法来进行实现,缺少自动性。网络

因此咱们迎来了MVVM框架,固然得首先感谢google爸爸提供得DataBinding,真的是很强大!架构

MVVM简介

这里写图片描述

在MVVM模式中,将程序结构分为三层——View-ViewModel-Model,接下来咱们一块儿来认识它们:

View:

View层负责和UI相关的工做,咱们只在XML、Activity和Fragment写View层的代码,View层不进行业务处理,也就是咱们在Activity不写业务逻辑和业务数据相关的代码。

更新UI经过数据绑定实现,尽可能在ViewModel里面作,Activity要作的事就是初始化一些控件(如RecyclerView设置LayoutManager或者控件的显隐),View层能够经过数据来驱动更改UI,UI事件经过Command来绑定。

简而言之:View层不作任何业务逻辑、不涉及操做数据,UI和数据严格的分开。 **UI更新和事件相应所有使用数据绑定,也就是DataBinding来实现。**这就是MVVM和MVP、MVC很明显的不一样之处。

ViewModel

ViewModel层作的事情恰好和View层相反,ViewModel只负责业务逻辑,不作任何和UI相关的事情。

同时DataBinding框架已经支持双向绑定,让咱们能够经过双向绑定获取View层反馈给ViewModel层的数据,并对这些数据上进行操做。

事件的处理,咱们也但愿能把这些事件处理绑定到控件上,并把这些事件的处理统一化,为此咱们经过使用BindingAdapter对一些经常使用的事件作封装,把一个个事件封装成一个个Command,对于每一个事件咱们用一个ReplyCommand去处理就好了,ReplyCommand会把你可能须要的数据带给你,这使得咱们在ViewModel层处理事件的时候只须要关心处理数据就好了,具体见MVVM Light Toolkit 使用指南的Command部分。

Model

Model层不只包括实体类的定义,还须要对数据进行处理和读写。例如:使用Retrofit或okHttp进行网络请求,或着如数据库操做等等。

优势:

  • 数据驱动
  • 低耦合
  • 主线程更新UI
  • 可复用性
  • 方便单元测试

咱们再来看下这张图:

这里写图片描述

简述下数据流走向:

View中使用DataBinding的Command来绑定事件和响应事件,触发网络请求;ViewModel进行分析处理,调用Model的数据请求方法;Model将收到的请求参数等信息封装,调用网络请求库;网络库(Retrofit等)与服务器进行交互;

服务器将json数据返回Retrofit等网络库,再返回到Model层中,ViewModel在回调中收到返回的实体类对象;

由于xml与实体类对象实现了双向绑定,实体类更新,使得UI更新!

ok!接下来咱们就用活生生的例子来实现MVVM吧

A、实体类定义

/**
 * Description:
 * Created by jia on 2017/11/3.
 * 人之因此能,是相信能
 */
public class NewslistBean extends BaseObservable {
 
    private String ctime;
    private String title;
    private String description;
    private String picUrl;
    private String url;

    public NewslistBean(String ctime, String title, String description, String picUrl, String url) {
        this.ctime = ctime;
        this.title = title;
        this.description = description;
        this.picUrl = picUrl;
        this.url = url;
    }

    public String getCtime() {
        return ctime;
    }

    public void setCtime(String ctime) {
        this.ctime = ctime;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getPicUrl() {
        return picUrl;
    }

    public void setPicUrl(String picUrl) {
        this.picUrl = picUrl;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    @BindingAdapter("bind:imageUrl")
    public static void loadImage(ImageView imageView, String picUrl) {
        Glide.with(imageView.getContext())
                .load(picUrl)
                .into(imageView);
    }

    public void onItemClick(View pView) {
        Toast.makeText(pView.getContext(), title, Toast.LENGTH_SHORT).show();
    }

}
复制代码

这和平时写的实体类是否是没啥区别!

是的,全部的属性咱们依旧如原来原来同样定义和设置get、set方法。可是,有一点不一样的是实体类继承了BaseObservable,稍后咱们再说。

B、Model类

/**
 * Description: 新闻网络请求model
 * Created by jia on 2017/11/3.
 * 人之因此能,是相信能
 */
public class NewsModel {

    public void getNews(final OnCallBack onCallBack) {
        HttpMethod.getInstance().getNews(new Subscriber<News>() {
            @Override
            public void onCompleted() {

            }

            @Override
            public void onError(Throwable e) {
                onCallBack.onFaile(e.toString());
            }

            @Override
            public void onNext(News news) {
                onCallBack.onSuccess(news);
            }
        });
    }


    public interface OnCallBack {
        void onSuccess(News news);

        void onFaile(String errorInfo);
    }
}
复制代码

这里呢,我使用的是本身封装过的Retrofit+RxJava的网络请求库,上面的Model用来进行新闻实体类News的网络请求;

也定义了一个CallBack接口:此回调可让接下的ViewModel得到Model请求回来的实体类。

每一个项目的网络请求库和方法都会不一样,符合本身的就是最好的!(●ˇ∀ˇ●)

C、View

xml中

先看示例:

<?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"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="news"
            type="com.jia.jsmvvm.home.viewmodel.NewslistBean" />

    </data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".home.view.MainActivity">

        <android.support.v7.widget.CardView
            android:id="@+id/cv_tuijian"
            android:layout_width="match_parent"
            android:layout_height="130dp"
            android:layout_margin="15dp"
            android:background="#ffffff"
            android:elevation="5dp"
            android:onClick="@{news.onItemClick}"
            app:cardElevation="5dp">

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_margin="5dp">

                <ImageView
                    android:id="@+id/iv_tuijian"
                    android:layout_width="120dp"
                    android:layout_height="match_parent"
                    android:layout_margin="15dp"
                    android:scaleType="fitXY"
                    app:imageUrl="@{news.picUrl}" />

                <TextView
                    android:id="@+id/tv_tuijian_title"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignTop="@id/iv_tuijian"
                    android:layout_toRightOf="@id/iv_tuijian"
                    android:ellipsize="end"
                    android:maxLines="2"
                    android:text="@{news.title}"
                    android:textColor="#111111"
                    android:textSize="18sp" />

                <TextView
                    android:id="@+id/tv_tuijian_time"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentBottom="true"
                    android:layout_alignParentRight="true"
                    android:ellipsize="end"
                    android:lines="3"
                    android:singleLine="true"
                    android:text="@{news.ctime}"
                    android:textColor="#777777"
                    android:textSize="14sp" />
            </RelativeLayout>
        </android.support.v7.widget.CardView>


    </RelativeLayout>
</layout>
复制代码

你们可定已经发现了:布局的编写和往常比仍是又较大变化的。

熟悉DataBinding的朋友能够直接跳过这趴。因为本人对DataBinding也不是特别熟练,因此也只能和你们分享本身了解的一点使用方法。DataBinding拥有很是强大的功能,想深刻了解的能够网上搜索,固然,本人不久也会把本身了解的DataBinding的知识整理成一篇博客,敬请期待!

  1. 咱们使用 layout 做为布局文件的跟节点
  2. layout中包含data节点和普通的布局
  3. data节点中建立variable
  4. variable中有两个“属性”:name和type
  5. type声明实体类,格式为 包名.类名
  6. name为type中的实体类定义“名字”,供如下布局中使用
  7. 定义了data属性后,就至关于xml布局已和实体类绑定
  8. 在控件中引用实体类属性的格式为: @{实体类.属性名}
  9. 在控件中引用实体类方法的格式为: @{实体类.方法名}
  10. 涉及到图片加载:在实体类中使用@BindingAdapter注解图偏加载方法,在布局中引用url便可

由于本篇文章重点在于讲述MVVM框架的使用,因此DataBinding只进行粗略简介,若有错误,还望你们及时提出,咱们一块儿进步!

Activity中

在Activity中设置布局,咱们再也不使用Activity的setContentView方法,取而代之的是:DataBindingUtil.setContentView

ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
复制代码

所返回的变量类型怎么来的呢?

将使用了DataBinding的布局名字,去掉全部下划线,将全部单词首字母大写,直接进行拼接,最后加上 Binding便可!

View层中这样就能够了!哈哈!

D、ViewModel

ViewModel层你们比较不熟悉,他和MVC的Controller、MVP的Presenter到底有什么区别呢?

ViewModel类应该怎么写呢?

先看下代码:

/**
 * Description: 新闻ViewModel类
 * Created by jia on 2017/11/3.
 * 人之因此能,是相信能
 */
public class NewsViewModel {

    public Activity activity;

    public ActivityMainBinding activityMainBinding;

    public NewslistBean news;

    public NewsModel model;

    private int num=1;

    public NewsViewModel(Activity activity, final ActivityMainBinding activityMainBinding) {
        this.activity = activity;
        this.activityMainBinding = activityMainBinding;

        model=new NewsModel();

        model.getNews(new NewsModel.OnCallBack() {
            @Override
            public void onSuccess(News news) {
                activityMainBinding.setNews(news.getNewslist().get(0));
            }

            @Override
            public void onFaile(String errorInfo) {
                news=new NewslistBean("error","error","error","error","error");
                activityMainBinding.setNews(news);
            }
        });
    }

}
复制代码

看看里边有些啥:

  • Context或Activity对象(这个应该好理解把)
  • 在Activity中建立的Binding对象
  • 实体类对象
  • Model层对象
  • ChildViewModel(例如Activity中嵌套多个Fragment的状况)

将实体类对象经过setXXX方法,设置给Binding对象。

当事件触发时,Model进行网络请求,在回调中更新实体类,即可对应的更新UI界面。

总结

实例中只是一个简单的功能的展现,你们在熟悉了MVVM后可再深度封装。

本文主要讲解了一些本人再开发过程当中总结的Android MVVM构建思想,更可能是理论上各个模块如何分工、代码如何设计。虽然在现实生产中用Android MVVM模式开发还比较少,可是随着DataBinding 1.0的发布,相信在Android MVVM 这一领域会更多的人来尝试。

因为时间有限,能力有限,文中难免有错误或不足的地方,还请你们提出,咱们一同进步!

源码请点击:github.com/shuaijia/Js…

您还能够关注个人微信公众号——Android机动车,获取更多精彩内容!

相关文章
相关标签/搜索