Android Jetpack从入门到精通:Data Binding

自Google在2018年推出Android Jetpack到现在,Jetpack几乎已经成为搞Android开发必会的内容了,Android岗的面试基本上都会问到。

今天主要是分享一些技术文, 带大家一路从Jetpack入门到精通。

前言

即学即用Android Jetpack系列Blog的目的是通过学习Android Jetpack完成一个简单的Demo,本文是即学即用Android Jetpack系列Blog的第二篇。

Google在2018年推出Android Jetpack,本人最近在学习Android Jetpack,如果你有研究过Android Jetpack,你会发现Livedata,ViewModel和Livecycles等一系列Android Jetpack组件非常适用于实现MVVM,因此,在进行Android Jetpack的下一步研究之前,我们有必要学习一下MVVM设计模式以及Android中实现MVVM的Data Binding组件。

语言:kotlin
我的Demo:https://github.com/mCyp/Hoo

目录

一、介绍

1.MVVM介绍

MVVM(全称Model-View-ViewModel)同MVCMVP一样,是逻辑分层解偶的模式(如果你还不了解MVCMVP,建议还是提前了解一下)。

1.1 结构图

MVVM结构图

从上图我们可以了解到MVVM的三要素,他们分别是:

  • View层:xml、Activity、Fragment、Adapter和View等
  • Model层:数据源(本地数据和网络数据等)
  • ViewModel层:View层处理数据以及逻辑处理

2. Data Binding介绍

Data Binding不算特别新的东西,2015年Google就推出了,但即便是现在,很多人都没有学习过它,我就是这些工程师中的一位,因为我觉得MVP已经足够帮我处理日常的业务,Android Jetpack的出现,是我研究Data Binding的一个契机。

在进行下文之前,我有必要声明一下,MVVMData Binding是两个不同的概念,MVVM是一种架构模式,而Data Binding是一个实现数据和UI绑定的框架,是构建MVVM模式的一个工具。

2.1 学习姿势

下面是我的学习资料主要来源:

二、实战

在这里,我打算先在上一节Android Jetpack - Navigation的基础代码上进行拓展(如有涉及到Navigation的代码,我会注明),本文会在登录和注册模块的基础上进行讲解,后期如有需要,会拓展到其他模块。

效果图,和之前的有点不一样:

第一步 在app模块下的build.gradle文件添加内容

android {
...
    dataBinding {
       enabled true
    }
}

第二步 构建LoginModel

创建登录的LoginModelLoginModel主要负责登录逻辑的处理以及两个输入框内容改变的时候数据更新的处理:

class LoginModel constructor(name: String, pwd: String, context: Context) {
    val n = ObservableField<String>(name)
    val p = ObservableField<String>(pwd)
    var context: Context = context

    /**
     * 用户名改变回调的函数
     */
    fun onNameChanged(s: CharSequence) {
        n.set(s.toString())
    }

    /**
     * 密码改变的回调函数
     */
    fun onPwdChanged(s: CharSequence, start: Int, before: Int, count: Int) {
        p.set(s.toString())
    }

    fun login() {
        if (n.get().equals(BaseConstant.USER_NAME)
            && p.get().equals(BaseConstant.USER_PWD)
        ) {
            Toast.makeText(context, "账号密码正确", Toast.LENGTH_SHORT).show()
            val intent = Intent(context, MainActivity::class.java)
            context.startActivity(intent)
        }
    }
}

我相信同学们可能会对ObservableField存在疑惑,那么ObservableField是什么呢?它其实是一个可观察的域,通过泛型来使用,可以使用的方法也就三个:

不过,除了使用ObservableField之外,Data Binding为我们提供了基本类型的ObservableXXX(如ObservableInt)和存放容器的ObservableXXX(如ObservableList)等,同样,如果你想让你自定义的类变成可观察状态,需要实现Observable接口。

我们再回头看看LoginModel这个类,它其实只有分别用来观察name和pwd的成员变量n和p,外加一个处理登录逻辑的方法,非常简单。

第三步 创建布局文件

引入Data Binding之后的布局文件的使用方式会和以前的布局使用方式有很大的不同,且听我一一解释:

我们再看一下LoginFragment下的fragment_login.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>
        <!--需要的viewModel,通过mBinding.vm=mViewMode注入-->
        <variable
            name="model"
            type="com.joe.jetpackdemo.viewmodel.LoginModel"/>

        <variable
            name="activity"
            type="androidx.fragment.app.FragmentActivity"/>
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/txt_cancel"
            android:onClick="@{()-> activity.onBackPressed()}"
            />

        <TextView
            android:id="@+id/txt_title"
            app:layout_constraintTop_toTopOf="parent"
            .../>

        <EditText
            android:id="@+id/et_account"
            android:text="@{model.n.get()}"
            android:onTextChanged="@{(text, start, before, count)->model.onNameChanged(text)}"
            ...
            />

        <EditText
            android:id="@+id/et_pwd"
            android:text="@{model.p.get()}"
            android:onTextChanged="@{model::onPwdChanged}"
            ...
            />

        <Button
            android:id="@+id/btn_login"
            android:text="Sign in"
            android:onClick="@{() -> model.login()}"
            android:enabled="@{(model.p.get().isEmpty()||model.n.get().isEmpty()) ? false : true}"
            .../>


    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

variable有两个:

  • model:类型为com.joe.jetpackdemo.viewmodel.LoginModel,绑定用户名详见et_account EditText中的android:text="@{model.n.get()}",当EditText输入框内容变化的时候有如下处理android:onTextChanged="@{(text, start, before, count)->model.onNameChanged(text)}",以及登录按钮处理android:onClick="@{() -> model.login()}"
  • activity:类型为 androidx.fragment.app.FragmentActivity,主要用来返回按钮的事件处理,详见txt_cancel TextView的android:onClick="@{()-> activity.onBackPressed()}"
    对于以上的内容,我仍然有知识点需要讲解:

1. 属性的引用
如果想使用ViewModel中成员变量,如直接使用model.p。

2. 事件绑定
事件绑定包括方法引用和监听绑定:

  • 方法引用:参数类型和返回类型要一致,参考et_pwdEditText的android:onTextChanged引用。
  • 监听绑定:相比较于方法引用,监听绑定的要求就没那么高了,我们可以使用自行定义的函数,参考et_accountEditText的android:onTextChanged引用。

3. 表达式

如果你注意到了·btn_login· Button在密码没有内容的时候是灰色的:

是因为它在android:enabled使用了表达式:@{(model.p.get().isEmpty()||model.n.get().isEmpty()) ? false : true},它的意思是用户名和密码为空的时候登录的enable属性为false,这是普通的三元表达式,除了上述的||和三元表达式之外,Data Binding还支持:

  • 运算符 + - / * %
  • 字符串连接 +
  • 逻辑与或 && ||
  • 二进制 & | ^
  • 一元 + - ! ~
  • 移位 >> >>> <<
  • 比较 == > < >= <= (Note that < needs to be escaped as <)
  • instanceof
  • Grouping ()
  • Literals - character, String, numeric, null
  • Cast
  • 方法调用
  • 域访问
  • 数组访问
  • 三元操作符

除了上述之外,Data Binding新增了空合并操作符??,例如android:text="@{user.displayName ?? user.lastName}",它等价于android:text="@{user.displayName != null ? user.displayName : user.lastName}"

第四步 生成绑定类

我们的布局文件创建完毕之后,点击Build下面的Make Project,让系统帮我生成绑定类,生成绑定的类如下:

下面我们只需在LoginFragment完成绑定即可,绑定操作既可以使用上述生成的FragmentLoginBinding也可以使用自带的DataBindingUtil完成:

1. 使用DataBindingUtil

我们可以看一下DataBindingUtil的一些常用Api:

函数名作用
setContentView用来进行Activity下面的绑定
inflate用来进行Fragment下面的绑定
bind用来进行View的绑定

LoginFragment绑定代码如下:

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val binding: FragmentLoginBinding = DataBindingUtil.inflate(
            inflater
            , R.layout.fragment_login
            , container
            , false
        )
        loginModel = LoginModel("","",context!!)
        binding.model = loginModel
        binding.activity = activity
        return binding.root
    }

2. 使用生成的FragmentLoginBinding

使用方法与第一种类似,仅需将生成方式改成val binding = FragmentLoginBinding.inflate( inflater , container , false )即可

运行一下代码,开始图的效果就出现了。

三、更多

Data Binding还有一些有趣的功能,为了让同学们了解到更多的知识,我们在这里有必要探讨一下:

1. 布局中属性的设置
1.1 有属性有setter的情况

如果XXXView类有成员变量borderColor,并且XXXView类有setBoderColor(int color)方法,那么在布局中我们就可以借助Data Binding直接使用app:borderColor这个属性,不太明白?没关系,以DrawerLayout为例,DrawerLayout没有声明app:scrimColorapp:drawerListener,但是DrawerLayoutmScrimColor:intmListener:DrawerListener这两个成员变量并且具有这两个属性的setter的方法,他就可以直接使用app:scrimColorapp:drawerListener这两个属性,代码如下:

<android.support.v4.widget.DrawerLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:scrimColor="@{@color/scrim}"
    app:drawerListener="@{fragment.drawerListener}">

1.2 没有setter但是有相关方法

还用XXXView为例,它有成员变量borderColor,这次设置borderColor的方法是setBColor(总有程序员乱写方法名~),强行用app:borderColor显然是行不通的,可以这样用的前提是必须有setBoderColor(int color)方法,显然setBColor不匹配,但我们可以通过BindingMethods注解实现app:borderColor的使用,代码如下:

@BindingMethods(value = [
    BindingMethod(
        type = 包名.XXXView::class,
        attribute = "app:borderColor",
        method = "setBColor")])

1.3 自定义属性

这次不仅没setter方法,甚至连成员变量都需要自带(条件越来越刻苦~),这次我们的目标就是给EditText添加文本监听器,先在LoginModel中自定义一个监听器并使用@BindingAdapter注解:

    // SimpleWatcher 是简化了的TextWatcher
    val nameWatcher = object : SimpleWatcher() {
        override fun afterTextChanged(s: Editable) {
            super.afterTextChanged(s)

            n.set(s.toString())
        }
    }

    @BindingAdapter("addTextChangedListener")
    fun addTextChangedListener(editText: EditText, simpleWatcher: SimpleWatcher) {
        editText.addTextChangedListener(simpleWatcher)
    }

这样我们就可以在布局文件中对EditText愉快的使用app:addTextChangedListener属性了:

        <EditText
            android:id="@+id/et_account"
            android:text="@{model.n.get()}"
            app:addTextChangedListener="@{model.nameWatcher}"
            ...
            />

效果与我们之前使用的时候一样

2. 双向绑定

使用双向绑定可以简化我们的代码,比如我们上面的EditText在实现双向绑定之后既不需要添加SimpleWatcher也不需要用方法调用,怎么实现呢?代码如下:

        <EditText
            android:id="@+id/et_account"
            android:text="@={model.n.get()}"
            ...
            />

仅仅在将@{model.n.get()}替换为@={model.n.get()},多了一个=号而已,需要注意的是,属性必须是可观察的,可以使用上面提到的ObservableField,也可以自定义实现BaseObservable接口,双向绑定的时候需要注意无限循环,更多关于双向绑定还请查看官方文档。

四、总结

Data Binding的介绍可能没有那么全面,基本使用没什么问题了,想要了解更多可以查看官方文档呦~,本人水平有限,难免理解有误差,欢迎指正。
Over~

参考文章:

《DataBinding最全使用说明》
《官方文档》

🚀如果觉得本文不错,可以关注我,后面会更新Android Jetpack系列的其他文章。

学习推荐

原文地址:https://www.jianshu.com/p/e3b881d80c6d
来源:简书 九心

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值