SkillAgentSearch skills...

AndroidDataBinding

Android master MVVM, Data binding and RxJava

Install / Use

/learn @chiclaim/AndroidDataBinding
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

项目整体效果:

这里写图片描述

Awesome-Android-MVVM

  • 什么是MVVM, 为什么需要 MVVM?
  • 如何在android中使用DataBinding实现MVVM架构?

什么是MVVM , 为什么需要MVVM?

MVVM是Model-View-ViewModel的简写. 它是有三个部分组成:Model、View、ViewModel。

Model:数据模型层。包含业务逻辑和校验逻辑。

View:屏幕上显示的UI界面(layout、views)。

ViewModel:View和Model之间的链接桥梁,处理视图逻辑。

MVVM功能图如下:

Alt text

MVVM架构通过ViewModel隔离了UI层和业务逻辑层,降低程序的耦合度。

Android App 中MVC的不足

一般来说,我们开发Android App是基于MVC,由于MVC的普及和快速开发的特点,一个app从0开发一般都是基于MVC的。

Activity、Fragment相当于C (Controller), 布局相当于V(View), 数据层相当于M(Model)

随着业务的增长,Controller里的代码会越来越臃肿,因为它不只要负责业务逻辑,还要控制View的展示。也就是说Activity、Fragment杂糅了Controller和View,耦合变大。并不能算作真正意义上的MVC。

编写代码基本的过程是这样的,在Activity、Fragment中初始化Views,然后拉取数据,成功后把数据填充到View里。

假如有如下场景

我们基于MVC开发完第一版本,然后企业需要迭代2.0版本,并且UI界面变化比较大,业务变动较小,怎么办呢? 当2.0的所有东西都已经评审过后。这个时候,新建布局,然后开始按照新的效果图,进行UI布局。然后还要新建Activity、Fragment把相关逻辑和数据填充到新的View上。 如果业务逻辑比较复杂,需要从Activity、Fragment中提取上个版本的所有逻辑,这个时候自己可能就要晕倒了,因为一个复杂的业务,一个Activity几千行代码也是很常见的。千辛万苦做完提取完,可能还会出现很多bug。

一开始我尝试使用MVP架构, MVP功能图如下:

Alt text

MVP 把视图层抽象到View接口,逻辑层抽象到 Presenter 接口,提到了代码的可读性。降低了视图逻辑和业务逻辑的耦合。

但是有 MVP 的不足:

  1. 接口过多,一定程度影响了编码效率。
  2. 业务逻辑抽抽象到Presenter中,较为复杂的界面Activity代码量依然会很多。
  3. 导致Presenter的代码量过大。

这个时候MVVM就闪亮登场了。从上面的MVVM功能图我们知道:

  1. 可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。 在Android中,布局里可以进行一个视图逻辑,并且Model发生变化,View也随着发生变化。
  2. 低耦合。以前Activity、Fragment中需要把数据填充到View,还要进行一些视图逻辑。现在这些都可在布局中完成(具体代码请看后面) 甚至都不需要再Activity、Fragment去findViewById。这时候Activity、Fragment只需要做好的逻辑处理就可以了。

现在我们回到上面从app1.0到app2.0迭代的问题,如果用MVVM去实现那就比较简单,这个时候不需要动Activity、Fragment, 只需要把布局按照2.0版本的效果实现一遍即可。因为视图逻辑和数据填充已经在布局里了,这就是上面提到的可重用性。

发展过程: MVC->MVP->MVVP

Android中如何实现MVVM架构?

Google在2015年的已经为我们DataBinding技术。下面就详细讲解如何使用DataBinding。

1,环境准备

在工程根目录build.gradle文件加入如下配置,把Android Gradle 插件升级到最新:

dependencies {
    classpath 'com.android.tools.build:gradle:1.5.0'
}

在app里的build.gradle文件加入如下配置,启用data binding 功能:

dataBinding {
    enabled true
}

2,来个简单的例子

实现上面效果的“Data Binding Simple Sample”

data binding 布局格式和以往的有些区别:

<?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>
   
   //normal layout
   <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"/>
</layout>
  • 布局的根节点为<layout/>

  • 布局里使用的model 通过<data>中的<variable>指定:

       <variable name="user" type="com.example.User"/>
    
    
  • 设置空间属性的值,通过@{}语法来设置:

       android:text="@{user.firstName}"
    
    

下面是完整的布局实现:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="user"
            type="com.mvvm.model.User"/>
    </data>

    <LinearLayout
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp"
        tools:context=".ui.MainActivity">


        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.realName}"
            android:textSize="14dp"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:text="@{user.mobile}"
            android:textSize="14dp"/>


        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(user.age)}"
            android:textSize="14dp"/>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="15dp"
            android:layout_marginBottom="40dp"
            android:layout_marginTop="40dp"
            android:gravity="center_vertical"
            android:orientation="horizontal">

            <View
                android:layout_width="match_parent"
                android:layout_height="1dp"

                android:layout_weight="1"
                android:background="@android:color/darker_gray"/>

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="With String Format"
                android:textSize="10dp"
                android:textStyle="bold"/>

            <View
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:layout_marginBottom="20dp"
                android:layout_marginTop="20dp"
                android:layout_weight="1"
                android:background="@android:color/darker_gray"/>


        </LinearLayout>

        <TextView
            android:id="@+id/tv_realName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{@string/name_format(user.realName)}"
            android:textSize="14dp"/>

        <TextView
            android:id="@+id/tv_phone"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:text="@{@string/mobile_format(user.mobile)}"
            android:textSize="14dp"/>

    </LinearLayout>
</layout>



####接下来实现数据模型类User:

public class User {

    private String userName;
    private String realName;
    private String mobile;
    private int age;

    public User(String realName, String mobile) {
        this.realName = realName;
        this.mobile = mobile;
    }

    public User() {
    }
    
    //ignore getter and setter. see code for detail.

}

在Activity中 绑定数据

	@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_simple);
        fetchData();
    }
    
    //模拟获取数据
    private void fetchData() {
        new AsyncTask<Void, Void, Void>() {

            @Override
            protected void onPreExecute() {
                super.onPreExecute();
                showLoadingDialog();
            }

            @Override
            protected Void doInBackground(Void... params) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return null;
            }

            @Override
            protected void onPostExecute(Void aVoid) {
                super.onPostExecute(aVoid);
                hideLoadingDialog();
                User user = new User("Chiclaim", "13512341234");
                binding.setUser(user);
                //binding.setVariable(com.mvvm.BR.user, user);
            }
        }.execute();
    }
}

通过DataBindingUtil.setContentView设置布局,通过binding类设置数据模型:

binding.setUser(user);

3,布局详解

import导入

  • 通过<import>标签导入:

    <data>
    	<import type="android.view.View"/>
    	<import type="com.mvvm.model.User"/>
    	<variable name="user" type="User">
    </data>
    android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"
    
  • 如果产生了冲突可以使用别名的方式:

    <import type="com.example.User"/>
    <import type="com.mvvm.model.User" alias="MyUser"/>
    <variable name="user" type="User">
    <variable name="user" type="MyUser">
    
  • 集合泛型左尖括号需要使用转译:

    <import type="com.example.User"/>
    <import type="java.util.List"/>
    <variable name="user" type="User"/>
    <variable name="userList" type="List&lt;User>"/>
    
    
  • 使用导入类的静态字段和方法:

    <data>
    	<import type="com.example.MyStringUtils"/>
    	<variable name="user" type="com.example.User"/>
    </data>
    …
    <TextView
    	android:text="@{MyStringUtils.capitalize(user.lastName)}"
    	android:layout_width="wrap_content"
    	android:layout_height="wrap_content"/>
    
    

像JAVA一样,java.lang.*是自动导入的。

Variables

<data>节点中使用<varibale>来设置。

<import type="android.graphics.drawable.Drawable"/>
<variable name="user"  type="com.example.User"/>
<variable name="image" type="Drawable"/>
<variable name="note"  type="String"/>

  • Binding类里将会包含通过variable设置name的getter和setter方法。如上面的setUser,getUser等。

  • 如果控件设置了id,那么该控件也可以在binding类中找到,这样就不需要findViewById来获取View了。

自定义Binding类名(Custom Binding Class Names)

<layout/>为根节点布局,android studio默认会自动产生一个Binding类。类名为根据布局名产生,如一个名为activity_simple的布局,它的Binding类为ActivitySimpleBinding,所在包为app_package/databinding。 当然也可以自定义Binding类的名称和包名:

  1. <data class="CustomBinding"></data> 在app_package/databinding下生成CustomBinding;

  2. <data class=".CustomBinding"></data> 在app_package下生成CustomBinding;

  3. <data class="com.example.CustomBinding"></data> 明确指定包名和类名。

Includes

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </LinearLayout>
</layout>

name.xml 和 contact.xml都必须包含 <variable name="user" ../>

4,DataBinding Obervable

在上面的一个例子上,数据是不变,随着用户的与app的交互,数据发生了变化,如何更新某个控件的值呢?

有如下几种方案(具体实现下载代码,运行,点击DataBinding Observable 按钮):

  1. BaseObservable的方式

使User继承BaseObservable,在get方法上加上注解@Bindable,会在BR(BR类自动生成的)生成该字段标识(in

Related Skills

View on GitHub
GitHub Stars83
CategoryDevelopment
Updated5mo ago
Forks33

Languages

Java

Security Score

92/100

Audited on Oct 29, 2025

No findings