目录
Jetpack的基本知识:
1.Android开发架构的发展历程:
互联网技术日新月异,越来越多优秀的开发工程师开始追寻更高效率的开发模式,因此,不断涌现出新的软件开发模式,其中MVC、MVP以及MVVM这三种模式一直是软件行业争论的焦点。下面就分别来看一下这三种开发模式在Android应用开发中是如何应用的吧。
1.1:MVC:
MVC的全称是Model-View-Controller,即模型-视图一控制器,Model负责数据的管理,View负责UI的显示,Controller负责逻辑控制。在Android中充当视图层角色的是各种xml文件,充当逻辑控制层角色的是Activity或者Fragment,充当模型层的是网络请求等部分。
由于XML的能力较弱,在实际项目中数据设置一般都是在Activity或Fragment中完成的,因此导致Activity既充当了Controller层又充当了View层,且Controller层需要调用Model层获取数据,从而导致绝大多数的任务都是在Controller中完完成的,这也就使得Controller层不易维护,因为Model层与View层耦合性较高,容易牵一发而动全身。
1.2:MVP:
MVP的全称是Model-View-Presenter, Model负责数据的管理,View负责UI的显示,Presenter负责逻辑控制,但是与MVC不同的是,MVP改变了通信方向,View层和Model层不再直接通信,而是通过Presenter层作为“中间人”。
View层产生事件,通知Presenter层,Presenter层则通知Model层更新数据,Model层更新数据后,返回并通知Presenter层,Presenter层再通知View层更新界面。MVP相比于MVC的好处显而易见,即将View层与Model层解耦,使得每一层的职责更清晰、明确。但MVP作为“中间人”,需要借助接口回调的方式转发消息,从而导致接口类文件增多,且实现类无法避免许多无用的空实现。
1.3:MVVM:
其实MVP已经算是一种很好的开发模式了,MVVM模式则相当于MVP的一种改进版本,MVVM的全称是Model-View-ViewModel,要注意的是,这里的ViewModel并不能直接与Jetpack中的ViewModel组件划等号。ViewModel中有一个Binder,在不同系统的MVVM开发模式中对Binder有不同的实现,比如前端开发中的Vue.js或iOS开发中的RAC,而在Android开发中充当Binder角色的则是Jetpack组件中的DataBinding,Binder的作用就是替代MVP中Presenter层的“中间人”角色。此模式会将View和ViewModel层完全解耦,从而使得职责划分更清晰。
MVVM开发模式是当前Google最推荐的开发模式,为了便于使用MVVM开发模式,Google还打造了一套工具集——Jetpack。
2.什么是Jetpack:
按照Google官方的介绍,Jetpack是一个由多个库组成的套件,可帮助开发者遵循最佳做法,减少样板代码并编写可以在各种Android版本和设备中一致运行的代码,这样开发者就可以集中精力编写重要的代码了。早在2017年的时候,Google就推出了一系列架构组件,称为ArchitectureComponents,并于2018年在Google I/O大会上提出Jetpack,且将Architecture Components纳入其中,时至今日,越来越多的组件,如room,paging3等等也被纳入其中。Jetpack主要分为基础,架构组件,行为,页面这四个模块。
3.如何构建Jetpack项目:
Jetpack所有的库都是发布在AndroidX下面的,所以我们只需要新建支持AndroidX的项目就可以在项目中引用任意的Jetpack组件了。那什么是AndroidX呢?相信每个Android开发人员都使用过support-v4和appcompat-v7支持库,这两种支持库是Android早期为了解决新版API的向后兼容问题而发布的,但是Google随后意识到这种包含v4、v7版本号的命名方式已经不合时宜,因此推出了AndroidX,将所有API的包名都统一为androidx.*的方式,AndroidX不仅提供与支持库同等的功能,而且还提供了新的库,28compileSdkVersion的编译版本不能低于API28。
gradle.properties中的android.useAndroidX属性必须存在且值为true,这Android插件才会使用对应的AndroidX库,而非支持库。如果未指定,那该标志默认为false。
新建项目成功后,就可以在项目中使用Jetpack的组件库了。.0.0是支持库的最后一个版本。Google将不再发布android.support库版本,因此对于开发者来说,使用AndroidX替代支持库是或早或晚的事情,接下来我们一起来看如何新建支持AndroidX的项目。从Android Studio 3.4版本开始,新建的项目已经默认勾选使用AndroidX了,但是可以勾选使用Android支持库,不过,这会影响使用最新的服务和Jetpack库,所以这里不用勾选Use legacy android.support libraries选项。
如果想要更改项目中的配置,有两点需要注意的,避免影响使用AndroidX:
- compileSdkVersion的编译版本不能低于API28。
- gradle.properties中的android.useAndroidX属性必须存在且值为true,这Android插件才会使用对应的AndroidX库,而非支持库。如果未指定,那该标志默认为false。
新建项目成功后,就可以在项目中使用Jetpack的组件库了。
Lifecycle:
感知activity的生命周期并不复杂,在一个activity中感知它的生命周期非常简单,而如果要在一个非activity的类中去感知activity的生命周期,我们除了可以在activity中嵌入一个fragment进行感知,或者通过手写监听器的方式进行感知,还可以使用Lifecycle组件,它可以让任何一个类都可以轻松感知到activity的生命周期,且不需要在activity编写大量的逻辑处理。当然Lifecycle组件也可以感知fragment组件。
1.从广告引导页的需求说起:
在实际App项目开发中,广告引导页是一个很常见的需求,具体描述如下:
- 用户打开App显示5秒钟的广告,广告结束后进入App主页面。
- 广告结束前,用户可以点击跳过广告。
- 页面销毁时,计时器销毁。
2.基本使用:
写法1:
class MainActivity : AppCompatActivity(){
lateinit var button: Button
lateinit var textView: TextView
private var advertisementManager:AdvertisementManager?=null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button=findViewById(R.id.button)
textView=findViewById(R.id.textView)
advertisementManager= AdvertisementManager()
advertisementManager?.advertisementManagerListener=object:AdvertisementManagerListener{
override fun timing(second:Int){
textView.text="广告剩余$second 秒"
}
override fun enterMainActivity(){
MainActivity2.actionStart(this@MainActivity)
finish()
}
}
//点击跳过广告
button.setOnClickListener{
MainActivity2.actionStart(this@MainActivity)
finish()
}
//开始广告
advertisementManager?.start()
}
override fun onDestroy() {
super.onDestroy()
advertisementManager?.onCancel()
}
}
interface AdvertisementManagerListener {
//计时
fun timing(second:Int)
//技术结束,进入主页面
fun enterMainActivity()
}
class AdvertisementManager {
var TAG=" AdvertisementManager "
var advertisementManagerListener:AdvertisementManagerListener?=null
private var countDownTimer:CountDownTimer?=object:CountDownTimer(5000,1000){
override fun onTick(millisUntilFinished: Long) {
Log.d(TAG, "广告剩余:${(millisUntilFinished/1000).toInt()}")
advertisementManagerListener?.timing((millisUntilFinished/1000).toInt())
}
override fun onFinish() {
Log.d(TAG, "广告结束")
advertisementManagerListener?.enterMainActivity()
}
}
fun start(){
Log.d(TAG,"开始计时")
countDownTimer?.start()
}
fun onCancel(){
Log.d(TAG, "停止计时")
countDownTimer?.cancel()
countDownTimer=null
}
}
写法2:Lifecycle
加上以下代码:
lifecycle.addObserver(advertisementManager!!)
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun start(){
Log.d(TAG,"开始计时")
countDownTimer?.start()
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onCancel(){
Log.d(TAG, "停止计时")
countDownTimer?.cancel()
countDownTimer=null
}
LifecycleOwner:
LifecycleOwner它可以把activity的生命周期通知给实现Lifecycle的类。首先调用LifecycleOwner的getLifecycle方法,得到一个Lifecycle对象,然后调用它的addObserver方法来观察LifecycleOwner的生命周期,再把实现Lifecycle的类传进去。那怎么才可以获取一个LifecycleOwner实例呢?
- 只要当前Activity或者fragment继承于androidX.core.app.ComponentActivity,androidx.fragment.app.FragmentActivity,androidx.appcompat.app.AppCompatActivity,androidx.fragment.appFragment。都是可以自己使用Lifecycle的,也就是直接使用 lifecycle.addObserver() ,因为这些类实现了LifecycleOwner接口,也就是它们本身就是一个LifecycleOwner的实例。
- 如果当前的activity是没有继承实现LifecycleOwner接口的android.app.Activity,那就可以自定义一个LifecycleOwner。只需要在当前的Activity页面实现LifecycleOwner接口,并且重写getLifecycle方法即可。
补充:
- 如果想要让实现Lifecycle的类主动获取当前的生命周期状态,可以把在类中的构造方法中加入一个Lifecycle类型的参数,然后再调用它的currentState方法即可获取当前的生命周期状态。
- getLifecycle方法实际上返回的是一个LifecycleRegistry对象,该对象是Lifecycle的唯一实现类,Lifecycle抽象类中定义了添加观察者,移除观察者和获取当前生命周期状态的方法。
ViewModel:
ViewModel可以分担Activity的一部分工作,以生命周期的方式存储和管理界面相关的数据,且可以在发生屏幕旋转等等配置更改之后让数据继续保存,只有当Activity退出的时候才会跟着Activity一起销毁,因此,将与界面相关的变量存放在ViewModel中,即使旋转屏幕,界面上显示的数据也不会丢失。
当然,屏幕旋转保存数据还有以下方式:
- 屏幕旋转时,生命周期的变化取决于configChanges属性,这里未配置configChanges的属性,所以屏幕由竖屏切换为横屏时,会重新执行每个生命周期的方法。当然也可以通过修改configChanges属性使得APP旋转的时候不被销毁。
- 通过重写onSavelnstanceState方法在Activity被销毁的时候将当前计时点存储起来,重新开始计时的时候从上次计时的节点开始计时。
1.屏幕旋转保存数据:
写法1:重写onSavelnstanceState方法
savedInstanceState?.let {
//获取key对应的值
millis=it.getLong(KEY_MILLIS)
}
override fun onSaveInstanceState(outState:Bundle){
//重写onSaveInstanceState方法并且把要保存的数据存储
super.onSaveInstanceState(outState)
outState.putLong(KEY_MILLIS,millis)
}
companion object{
const val KEY_MILLIS="keyMillis"
}
ViewModel虽然可以防止屏幕旋转引起的数据丢失,但ViewModel并不能代替onSaveInstanceState方法,主要原因有如下两点:
- onSaveInstanceState方法可以存储少量的序列化数据,ViewModel可以存储任意数据,只是使用时的限制不同。
- onSaveInstanceState可以达到数据持久化的目的,但是ViewModel不可以,使用场景不同。
为什么说ViewModel不能达到数据持久化的目的呢?因为当Activity被真正销毁的时候,ViewModel会将资源进行回收。
写法2:ViewModel
//获取ViewModel的实例
lateinit var advertisingViewModel: AdvertisingViewModel
advertisingViewModel= ViewModelProvider(this).get(AdvertisingViewModel::class.java)
//ViewModel类
class AdvertisingViewModel: ViewModel() {
var vMillis:Long=5000
}
若开发者想要在ViewModel类中使用资源文件,则要使用到Context上下文了。但是一定不能够把Activity的上下文传递给ViewModel,否则会存在内存泄漏的风险。处理方法是:把父类ViewModel修改为AndroidViewModel即可。
class AdvertisingRCViewModel(application: Application):AndroidViewModel(application) {
var vMillis:Long=5000
}
注意:
不能直接的去创建ViewModel的实例,而是要通过ViewModelProvider来获取ViewModel的实例,之所以要这么写是因为ViewModel有自己独立的生命周期,并且其的生命周期还要长于Activity,所以直接在onCreate方法中创建时,在每次onCreate方法执行的时候,ViewModel都会创建一个新的实例,导致数据无法保存。语法规则为:
ViewModelProvider(Activity或者Fragment实例).get(ViewModel::class.java)
而get方法的参数不为null的情况下,会创建ViewModel,创建好之后将其缓存在ViewModelStore中,如果当前需要创建的ViewModel对象已经存在的话,则直接从ViewModelStore中取出来。所以在屏幕旋转前后使用的是同一个对象。也就是说在创建ViewModel的时候,只要传入的class对象是一样的,那么获取的ViewModel就是同一个对象,基于这一点可以在同一个宿主Activity的不同Fragment之间进行数据的共享。
2.向ViewModel传递参数:
向ViewModel的构造函数中传递参数,需要借助ViewModelProvider.Factory接口即可。
- ViewModelProviders.Factory 接口是负责实例化 ViewModels 对象的启动装置.
- ViewModelProvider.Factory 是一个包含 create 方法的接口。这个 create 方法负责创建你的 VeiwModel实例.
- 这是由于你在实例化 ViewModel 对象时,不能直接在activity或者fragment中调用 ViewModel 的构造方法,而且你又想去设置 ViewModel 构造方法的参数,因此你需要将参数传入 ViewModelProvider.Factory 它将会帮你创建你的 ViewModel。
//ViewModel类
class AdvertisingViewModel(parameter:Int): ViewModel() {
//
}
在这里给ViewModel添加了一个参数,然后新建一个实现了ViewModelProvider.Factory接口的类:
class MyViewModelProviderFactory(private val parameter:Int):ViewModelProvider.Factory{
override fun<T:ViewModel>create(modelClass:Claa<T>):T{
return AdvertisingViewModel(parameter) as T
}
}
在MyViewModelProviderFactory类中的构造函数中添加了一个参数,利用这个参数就可以在create方法中创建ViewModel的实例了,并且传入需要的参数。最后在Activity中获取ViewModel实例的时候也要改变一下:
//获取ViewModel的实例
advertisingViewModel= ViewModelProvider(this,MyViewModelProviderFactory(parameter))
.get(AdvertisingViewModel::class.java)
3.使用ViewModel实现数据共享:
在实际开发中,经常会遇到两个Fragment之间有通信的需求,假设现在有AFragment和BFragment,这两个Fragment中都有滑动的标签,我们想要让两个Fragment的标签选项实现同步滑动,比如:在AFragment中选中了“新闻”标签,切换到BFragment时也会自动切换到“新闻”标签。一般情况下实现这个需求的方式有两种:
- 在某个Fragment中选中数据时将选择的标签位置记录下来,当切换Fragment时,取出当前记录的位置进行切换。
- 通过为宿主Activity增加实现接口的方式进行通信。
上面是开发者经常使用的两种方式,现在使用ViewModel的特性便可以很简单地解决这个问题。
注意:在Fragment中通过ViewModelProvider获取ViewModel对象时,如果参数是this,则获取的是Fragment自对应的ViewModel对象,此种方式不能用来实现数据共享功能。而如果参数是requireActivity(),则获取的是宿主Activity对应的ViewModel,这种获取方式可以用来实现数据共享。
LiveData:
LiveData是Jetpack提供的一种响应式编程组件,它可以包含任何类型的数据,并且在数据发生变化的时候通知给观察者。LiveData特别适合与ViewModel结合在一起使用,虽然它也可以使用在其他的地方,但是在绝大多数的情况下,都是使用在ViewModel中的。
1.基本使用:
在ViewModel实现类中声明一个MutableLiveData<>类型的变量,其中MutableLiveData是LiveData的实现类。
var timingResult=MutableLiveData<Long>()
然后提供MutableLiveData的读写方法,MutableLiveData有3种读写方法,分别是getValue方法,setValue方法和postValue方法,其中postValue方法是用于在非主线程中给LiveData设置数据,而前面两者是提供给主线程中调用的。
fun setTimingResult(vMillis:Long){
timingResult.value=vMillis
}
然后在变量发生变化时需要将变量通过setTimingResult方法赋值给timingResult变量。
advertisingViewModel.setTimingResult(millisUntilFinished/1000)
最后在需要观察的地方通过observer方法来订阅LiveData对象,这样当LiveData的值改变时,就可以收到更新的通知了。
advertisingViewModel.timingResult.observe(this, Observer {
textView.text="广告剩余$it 秒"
if (it==0L){
Log.d(TAG,"广告结束")
MainActivity2.actionStart(this@MainActivity)
finish()
}
})
任何的LivaData对象都可以调用它的observer方法来观察数据的变化,该方法接收两个参数:第一个是LifecycleOwner对象,第二个是一个observer接口,当LiveData对象包含的数据发生变化时,就会回调到这里。
缺陷:
这里的MutableLiveData<>类型的变量并没有设置为private,所以在ViewModel的外部也是可以对变量进行赋值的,但是如果声明为private之后,也没有办法在Activity中观察数据的变化了,对于这种情况,一般会单独声明非私有类型的LiveData类型的变量。
private var timingResult=MutableLiveData<Long>()
val _timingResult:LiveData<Long>
get() = timingResult
那么在Activity中调用时就可以:
advertisingViewModel._timingResult.observe(this, Observer {
textView.text="广告剩余$it 秒"
if (it==0L){
Log.d(TAG,"广告结束")
MainActivity2.actionStart(this@MainActivity)
finish()
}
})
这样,当外部在调用_timingResult变量时实际上调用的是timingResult 变量的值,由于_timingResult是val的,所以无法给_timingResult重新赋值,只能是timingResult。但是timingResult的值却可以更改,保证数据的完整性。
2.map和switchmap:
LiveData的基本用法已经可以满足大部分的需求了,而有些特殊的需求,LivaData提供了两种转换方法:map方法和switchmap方法。
2.1:map方法:
map方法的作用是将实际包含数据的LiveData和仅仅用于观察数据的LiveData进行转换。
data class Student(var name:String,var score:Int)
private var timingResult=MutableLiveData<Student>()
val _timingResult:LiveData<Int>
get() = Transformations.map(timingResult){it.score}
这么写的意义是:如果外部只想要Student类的score变量,我们还把整个Student通过timingResult传递给_timingResult让外部调用,导致整个Student类暴露给外部,倒是有点不合适了。所以map方法就是来解决这种问题的,它可以将Student类型的LiveData自由的转换成为任意其他类型的LiveData,以此来传递外部想要的类型。
2.2:switchMap方法:
使用switchMap方法的前提是ViewModel中的某一个LiveData对象是调用另外的方法获取的,那么就可以借助switchMap方法,将这个LiveData对象转换为另外一个可以观察的LiveData对象。switchMap()方法同样接收两个参数;第一个参数传入我们新增的LiveData,switchMap()方法会对它进行观察;第二个参数是一个转换函数。注意,我们必须在这个转换函数中返回一个 LiveData对象,因为 switchMap()方法的工作原理就是要将转换函数中返回的lLiveData对象转换成另一个可观察的LiveData对象。
3.补充:
- LiveData本质上是观察者模式。
- observer方法首先会使用assertMainThread方法检查程序是否运行在主线程中,所以使用observer方法时要确保它在主线程中执行。
- 如果想要让数据的监测变化不受活动状态的影响的话,可以使用observerForever方法,这样即使activity不处于活动状态,也可以接收到改变的数据,但是当activity销毁时,一定要调用removeObserver方法,否则LiveData会一直存在,导致内存泄漏。
ViewBinding:
1.从findViewById说起:
通过findViewById方法可以获取视图中的控件,进而编写与视图交互的代码。在activity中绑定布局中的控件一般有三种实现的方式,分别是:
- 使用findViewById方法进行绑定。
- 使用ButterKnife开源框架实现,其中ButterKnife是一个只专注于Android系统的View注入框架。
- 使用kotlin的扩展插件来获取视图的控件。
2.基本使用:
ViewBinding提供了视图绑定功能,为开发者提供了更加简便的方式编写与视图交互的代码。首先在模块的build.gradle配置之后,系统会为该模块中的每个XML布局文件生成一个绑定类,这个绑定类的命名就是XML文件的名称转换为驼峰式,并且在末尾加上Binding,绑定类可以直接引用布局内所有具有id的视图。以activity_main.xml为例子,系统自动生成的绑定类的名称为ActivityMainBinding。
class MainActivity : AppCompatActivity() {
//声明一个ActivityMainBinding类型的变量
lateinit var activityMainBinding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//生成实例
activityMainBinding= ActivityMainBinding.inflate(layoutInflater)
// setContentView(R.layout.activity_main)
//设置根视图
setContentView(activityMainBinding.root)
//这样就可以通过绑定类实例来操作包含id的控件
activityMainBinding.button.setOnClickListener { }
}
}
如果在Fragment中使用,流程与activity中使用是类似的,声明一个FragmentBinding实例,通过inflate方法生成实例,设置根视图,但是因为Fragment的存在时间比其的视图时间长,所以需要在onDestroyView方法中清除对绑定类实例的引用,避免造成内存泄漏的风险。
class Fragment:Fragment() {
lateinit var fragmentMainBinding:FragmentMainBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
fragmentMainBinding=FragmentMainBinding.inflate(inflater,container,false)
return fragmentMainBinding.root
}
override fun onDestroyView(){
super.onDestroyView()
//清除
fragmentMainBinding=null
}
}
3.总结:
- 启用ViewBinding功能的配置是对整个模块而言的,即会为整个模块的所有布局文件生成对应的绑定类。如果某一个布局文件不需要的话,可以通过设置tool:viewBindingIgnore="true"属性,这样的话,系统就不会为该XML文件自动生成绑定类了。
- 相比于findViewById方法,ViewBinding具有以下优点:具有Null安全(视图绑定会对视图直接引用),具有类型安全(每个绑定类中的字段都具有与它们在XML文件中引用的视图相匹配的类型)。
- 原理是在模块的build.gradle开启之后,若有项目要进行编译,那么会扫描layout下的所有布局文件,并且生成对应的绑定类,最终还是调用findViewById方法来实现的。
- ViewBinding是通过编译时扫描layout文件生成的ViewBinding类的,而ButterKnife是通过ART运行时注解生成的ViewBinding类实现的。
DataBinding:
通过视图绑定组件VicwBinding可以编写更简洁的与视图交互的代码。而DataBinding组件与ViewBinding组件一样,也可以引用视图id,但DataBinding有着更加丰富的功能。
1.基本使用:
DataBinding组件通过使用声明式格式将数据源绑定到布局中。DataBinding在Google推荐的MVVM架构中发挥着重要的作用,MVVM架构的本质是数据驱动页面,而目前Android系统提供给开发者的实现这一功能的最佳组件只有DataBinding和Jetpack Compose。 在配置完成后进入使用。
dataBinding{
enabled=true
}
1.1:基础布局绑定表达式:
写法1:
需求把编辑框的name和id显示在文本框内。常用写法:
//新建数据类user,包含name和id
data class User(var name:String?,var id:String?)
然后完成布局,两个编辑框,两个文本框和一个按钮。
在点击按钮时,获取编辑框的内容生成一个user对象,并且给文本框进行赋值。
class MainActivity : AppCompatActivity() {
lateinit var activityMainBinding:ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//绑定
activityMainBinding= ActivityMainBinding.inflate(layoutInflater)
setContentView(activityMainBinding.root)
//按钮
activityMainBinding.button.setOnClickListener {
//获取user对象
val user=getUser()
activityMainBinding.textView.text=user.name
activityMainBinding.textView2.text=user.id
}
}
private fun getUser():User{
return User(getName(),getId())
}
private fun getName():String?{
return activityMainBinding.name.text?.toString()
}
private fun getId():String?{
return activityMainBinding.id.text?.toString()
}
}
写法2:DataBinding
如果使用DataBinding组件的话,就可以省去主动设置的过程。首先要修改布局文件,因为DataBinding的布局文件必须使用layout根标记,并且通过data标签设置对应的数据实体类。
<layout>
<data>
<variable
name="user"
type="com.example.myapplication.User" />
</data>
......
</layout>
其中name属性声明了布局文件中可以使用的对象名称,type属性是对应的实体类。然后就可以使用对象的数据为文本组件进行赋值。
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{user.name}" />
<TextView
android:id="@+id/textView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{user.id}" />
接着在activity中进行数据绑定,与ViewBinding一样,DataBinding也会为布局文件生成一个绑定类。例如:activity_main.xml,对应的生成类是ActivityMainBinding。绑定之后设置activityMainBinding.user属性之后,user类的数据就会自动的填充到XML布局中了。
activityMainBinding= ActivityMainBinding.inflate(layoutInflater)
setContentView(activityMainBinding.root)
//按钮
activityMainBinding.button.setOnClickListener {
//获取user对象
val user=getUser()
//设置
activityMainBinding.user=user
}
视图中也可以引入表达式,例如:
<data>
<variable
name="user"
type="com.example.myapplication.User" />
<import type="android.view.View"/>
</data>
<TextView
android:id="@+id/textView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{user.id}"
//表达式
android:visibility='@{user.id.equals("001")? View.GONE:View.VISIBLE}'/>
DataBinding不仅仅可以在Activity中使用,还可以在Fragment,RecycleView适配器中使用。
1.2:利用DataBinding绑定点击事件:
在上面的例子里,按钮的点击事件是通过设置setOnClickListener方法来处理的。除此之外,还可以通过onClick属性和使用DataBinding来绑定点击事件。
写法1:
<Button
android:id="@+id/button"
//增加属性
android:onClick="confirm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button" />
onClick属性的值是需要在activity中实现的方法,也是按钮点击事件之后要执行的内容。
fun confirm(view: View){
val user=getUser()
activityMainBinding.user=user
}
写法2:DataBinding
DataBinding的实现方式与上面有些类似,叫做方法引用。首先新建一个类,添加一个点击事件之后要触发的方法。
class ClickHandlers {
var TAG="ClickHandlers"
fun confirm(view: View){
Log.d(TAG, "触发点击事件了")
}
}
接着在XML中引入,并且设置onClick属性。
<variable
name="clickHandlers"
type="com.example.myapplication.ClickHandlers" />
android:onClick="@{clickHandlers::confirm}"
最后,在activity中绑定类。
activityMainBinding.clickHandlers=ClickHandlers()
方法引用的表达式是在编译时处理的,如果没有对应的方法,则报错。
1.3:标签布局使用DataBinding:
有时候为了优化布局会经常使用include,merge标签将部分的布局抽取出来,这种布局也叫做标签布局。DataBinding绑定标签布局的过程为:
<layout>
<data>
<variable
name="user"
type="com.example.myapplication.User" />
<data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{user.name}" />
<TextView
android:id="@+id/textView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{user.id}" />
</LinearLayout>
</layout>
这是一个标签文件user_data,它包括了两个文本框,因为在activity_main.xml中要使用它,所以在activity_main.xml文件中引入标签文件,然后设置user的值即可。
<include
layout="@layout/user_data"
bind:user="@{user}"/>
2.ViewBinding和DataBinding的区别:
View.Binding可以实现的功能DataBinding也可以实现,那么DataBinding与ViewBinding
有什么区别呢?DataBinding和ViewBinding都可生成可直接引用视图的绑定类,从这一点来说,DataBinding和 ViewBinding都可以代替findViewByld。但ViewBinding仅有引用视图的功能,因此和DataBinding相比,ViewBinding有以下优势:
- 编译速度快:ViewBinding不需要处理DataBinding的注解,编译时间短,编译速度更快。
- 使用简洁:ViewBinding对布局元素没有限制,不需要以layout开头,启动视图绑定后就可以在项目中使用。
DataBinding更像是ViewBinding的扩展版本,它提供了更多常用的功能,所以ViewBinding不具备布局表达式、双向数据绑定(可以实现编辑框的内容不通过按钮给到文本框)等功能。
在实际项目开发中,如果只是为了替代findViewByld的功能,使用ViewBinding完全足够。如果想使用数据绑定等一些更高级的操作,则需要使用DataBinding。不过,DataBinding虽然是MVVM模式的核心实现方式,但许多开发者却对其敬而远之,从前面的示例中也可以看出,设置数据的相关逻辑都写在了xml中,这会导致调试困难。不过,结合业务需求和团队开发技术,选择适合的方案才是最主要的。