自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
MVVM(全称Model-View-ViewModel)同MVC
和MVP
一样,是逻辑分层解偶的模式(如果你还不了解MVC
和MVP
,建议还是提前了解一下)。
从上图我们可以了解到MVVM的三要素,他们分别是:
Data Binding
不算特别新的东西,2015年Google就推出了,但即便是现在,很多人都没有学习过它,我就是这些工程师中的一位,因为我觉得MVP已经足够帮我处理日常的业务,Android Jetpack
的出现,是我研究Data Binding
的一个契机。
在进行下文之前,我有必要声明一下,MVVM
和Data Binding
是两个不同的概念,MVVM是一种架构模式,而Data Binding是一个实现数据和UI绑定的框架,是构建MVVM模式的一个工具。
下面是我的学习资料主要来源:
在这里,我打算先在上一节Android Jetpack - Navigation的基础代码上进行拓展(如有涉及到Navigation
的代码,我会注明),本文会在登录和注册模块的基础上进行讲解,后期如有需要,会拓展到其他模块。
效果图,和之前的有点不一样:
第一步 在app模块下的build.gradle文件添加内容
android {
...
dataBinding {
enabled true
}
}
第二步 构建LoginModel
创建登录的LoginModel
,LoginModel
主要负责登录逻辑的处理以及两个输入框内容改变的时候数据更新的处理:
class LoginModel constructor(name: String, pwd: String, context: Context) {
val n = ObservableField(name)
val p = ObservableField(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布局文件:
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. 事件绑定
事件绑定包括方法引用和监听绑定:
3. 表达式
如果你注意到了·btn_login· Button在密码没有内容的时候是灰色的:
是因为它在android:enabled
使用了表达式:@{(model.p.get().isEmpty()||model.n.get().isEmpty()) ? false : true}
,它的意思是用户名和密码为空的时候登录的enable
属性为false,这是普通的三元表达式,除了上述的||
和三元表达式之外,Data Binding
还支持:
除了上述之外,Data Binding
新增了空合并操作符??
,例如android:text="@{user.displayName ?? user.lastName}"
,它等价于android:text="@{user.displayName != null ? user.displayName : user.lastName}"
。
我们的布局文件创建完毕之后,点击Build
下面的Make Project
,让系统帮我生成绑定类,生成绑定的类如下:
下面我们只需在LoginFragment
完成绑定即可,绑定操作既可以使用上述生成的FragmentLoginBinding
也可以使用自带的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
}
FragmentLoginBinding
使用方法与第一种类似,仅需将生成方式改成val binding = FragmentLoginBinding.inflate( inflater , container , false )
即可
运行一下代码,开始图的效果就出现了。
Data Binding
还有一些有趣的功能,为了让同学们了解到更多的知识,我们在这里有必要探讨一下:
如果XXXView类有成员变量borderColor
,并且XXXView类有setBoderColor(int color)
方法,那么在布局中我们就可以借助Data Binding
直接使用app:borderColor
这个属性,不太明白?没关系,以DrawerLayout
为例,DrawerLayout
没有声明app:scrimColor
、app:drawerListener
,但是DrawerLayout
有mScrimColor:int
、mListener:DrawerListener
这两个成员变量并且具有这两个属性的setter
的方法,他就可以直接使用app:scrimColor
、app:drawerListener
这两个属性,代码如下:
还用XXXView为例,它有成员变量borderColor
,这次设置borderColor
的方法是setBColor
(总有程序员乱写方法名~),强行用app:borderColor
显然是行不通的,可以这样用的前提是必须有setBoderColor(int color)
方法,显然setBColor
不匹配,但我们可以通过BindingMethods
注解实现app:borderColor
的使用,代码如下:
@BindingMethods(value = [
BindingMethod(
type = 包名.XXXView::class,
attribute = "app:borderColor",
method = "setBColor")])
这次不仅没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在实现双向绑定之后既不需要添加SimpleWatcher
也不需要用方法调用,怎么实现呢?代码如下:
仅仅在将@{model.n.get()}
替换为@={model.n.get()}
,多了一个=
号而已,需要注意的是,属性必须是可观察的,可以使用上面提到的ObservableField
,也可以自定义实现BaseObservable
接口,双向绑定的时候需要注意无限循环,更多关于双向绑定还请查看官方文档。
Data Binding
的介绍可能没有那么全面,基本使用没什么问题了,想要了解更多可以查看官方文档呦~,本人水平有限,难免理解有误差,欢迎指正。
Over~
参考文章:
《DataBinding最全使用说明》
《官方文档》
如果觉得本文不错,可以关注我,后面会更新Android Jetpack
系列的其他文章。
原文地址:https://www.jianshu.com/p/e3b881d80c6d
来源:简书 九心