前言

DataBinding只是一种工具,用来解决View和数据之间的绑定。

Data Binding,顾名思义:数据绑定,它能够将布局页面中的组件和利用中的数据进行绑定,反对单向绑定和双向绑定,单向绑定就是如果数据有变动就会驱动页面进行变动,双向绑定就是除了单向绑定之外还反对页面的变动驱动数据的变动,如果页面中有一个输入框,那么咱们就能够进行双向绑定,数据变动,它的显示内容就变了,咱们手动输出内容也能够扭转绑定它的数据。

官网文档:https://developer.android.goo...

官网Demo地址:https://github.com/googlecode...

本文代码地址

如何应用DataBinding呢?

1.启用DataBinding

援用官网文档:

Databinding与 Android Gradle 插件捆绑在一起。您无需申明对此库的依赖项,但必须启用它。

留神:即便模块不间接应用数据绑定,也必须为依赖于应用数据绑定的库的所有模块启用数据绑定。

//在gradle的android下退出,而后点击syncandroid {    ...    //android studio 4.0以下    dataBinding{        }    //android studio4.1当前    buildFeatures {        dataBinding true    }}

2.生成DataBinding布局

在咱们的布局文件中,抉择根目录的View,按下Alt+回车键,点击Convert to data binding layout,就能够转换为DataBinding布局啦。


而后咱们的布局就会变成这样:

<?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>    </data>    <androidx.constraintlayout.widget.ConstraintLayout        android:layout_width="match_parent"        android:layout_height="match_parent"        tools:context=".MainActivity">        <TextView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:text="Hello World!"            app:layout_constraintBottom_toBottomOf="parent"            app:layout_constraintEnd_toEndOf="parent"            app:layout_constraintStart_toStartOf="parent"            app:layout_constraintTop_toTopOf="parent" />    </androidx.constraintlayout.widget.ConstraintLayout></layout>

咱们能够发现,最里面变成了layout元素,外面有data元素。咱们将在data元素中申明这个布局中应用到的变量,以及变量的类型。

举个例子:

<data>    <import type="com.example...."/>    <variable        name="color"        type="java.lang.String" /></data>
  • data: 在标签内进行变量申明和导入等
  • variable: 进行变量申明
  • import: 导入须要的类

3.申明一个User实体类

class User() {    var name = "Taxze"    var age = 18    fun testOnclick() {        Log.d("onclick", "test")    }}

4.在xml中应用

而后在data中申明变量,以及类名

<data>    <!-- <variable-->    <!-- name="user"-->    <!-- type="com.taxze.jetpack.databinding.User" />-->    <import type="com.taxze.jetpack.databinding.User" />    <variable        name="user"        type="User" /></data>

而后在布局中应用@{}语法

//伪代码,请勿间接CV<TextView    ...    android:text="@{user.name}"/>

5.在Activity或Fragment中应用DataBinding

在Activity中通过DataBindingUtil设置布局文件,同时省略Activity的setContentView办法

class MainActivity : AppCompatActivity() {    private lateinit var mainBinding: ActivityMainBinding    private lateinit var mainUser: User    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        mainBinding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)        mainUser = User()        mainBinding.user = mainUser    }}

在Fragment中应用:

class BlankFragment : Fragment() {    private lateinit var mainFragmentBinding:FragmentBlankBinding    override fun onCreateView(        inflater: LayoutInflater, container: ViewGroup?,        savedInstanceState: Bundle?    ): View {        mainFragmentBinding = DataBindingUtil.inflate(inflater,R.layout.fragment_blank,container,false)        return mainFragmentBinding.root    }}

零碎会为每个布局文件都生成一个绑定类。个别默认状况下,类的名称是布局文件名称转化为Pascal大小写模式,而后在开端增加Binding后缀,例如:名称为activity_main的布局文件,对应的类名就是ActivityMainBinding

运行之后的成果:

留神:只有当布局文件转换为layout款式之后,databinding才会依据布局文件的名字主动生成一个对应的binding类,你也能够在build/generated/data_binding_base_source_out目录下查看生成的类


最最最根底的应用就是这样,接下来咱们来讲讲如何更好的应用DataBinding

如何在xml布局中更好的应用DataBinding

1.应用汇合中的元素

  • 退出咱们传入了一个汇合books,咱们能够通过以下形式应用:

    • 获取汇合的值
    • android:text="@{books.get(0).name}"android:text="@{books.[0].name}"
    • 增加默认值(⚡默认值无需加引号,且只在预览视图显示)
    • android:text="@{books.pages,default=330}"
    • 通过??或?:来实现
    • android:text="@{books.pages != null ? book.pages : book.defaultPages}"
    • android:text="@{books.pages ?? book.defaultPages}"

2.应用map中的数据

  • map类型的构造也能够通过get和[]两种形式获取

    • //须要留神单双引号android:text="@{books.get('name')}"
    **3.转换数据类型**
  • 因为DataBinding不会主动做类型转换,所有咱们须要手动转换,例如在text标签内应用String.valueOf()转换为String类型,在rating标签内咱们能够应用Float.valueOf()进行转换

    • android:text="@{String.valueOf(book.pages)}"
    • android:rating="@{Float.valueOf(books.rating),default=2.0}"

    4.导入包名抵触解决

  • 如果咱们导入的包名有抵触,咱们能够通过alias为它设置一个别名

    • //伪代码,请勿间接CV<data>      <import type="com.xxx.a.Book" alias="aBook"/>      <import type="com.xxx.B.Book" alias="bBook"/>      ...

5.隐式援用属性

  • 在一个view上援用其余view的属性

    • //伪代码,请勿间接CV<import type="android.view.View"/>...<LinearLayout    android:layout_width="match_parent"    android:layout_height="match_parent">    <CheckBox android:id="@+id/checkOne" .../>    <ImageView android:visibility="@{checkOne.checked ? View.VISIBLE : View.GONE}" .../>
  • include标签和ViewStub标签

    • includemerge标签的作用是实现布局文件的重用。就是说,为了高效复用及整合布局,使布局轻便化,咱们能够应用includemerge标签将一个布局嵌入到另一个布局中,或者说将多个布局中的雷同元素抽取进去,独立治理,再复用到各个布局中,便于对立的调整。 比方,一个利用中的多个页面都要用到对立款式的标题栏或底部导航栏,这时就能够将标题栏或底部导航栏的布局抽取进去,再以include标签模式嵌入到须要的布局中,而不是屡次copy代码,这样在批改时只需批改一处即可。而咱们同样能够通过DataBinding来进行数据绑定。
    • 例如:
    • //layout_title.xml<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android">    <data>        <import type="com.xxx.User" />        <variable            name="userInfo"            type="User" />    </data>    <android.support.constraint.ConstraintLayout        ...        >        <TextView            ...            android:text="@{userInfo.name}" />    </android.support.constraint.ConstraintLayout></layout>
    • 应用该布局,并传值
    • <include    layout="@layout/layout_title"    bind:test="@{userInfo}"/>
    • ViewStub也是相似的用法,这里就不说了。
    • DataBinding不反对merge标签

    6.绑定点击事件

  • //伪代码,请勿间接CVonclick="@{()->user.testOnclick}"onclick="@{(v)->user.testOnclick(v)}"onclick="@{()->user.testOnclick(context)}"onclick="@{BindHelp::staticClick}"onclick="@{callback}"//例如:<Button    android:layout_width="match_parent"    android:layout_height="match_parent"    android:onClick="@{()->user.testOnclick}"    />

文章到这里讲的都是DataBinding如何设置数据,以及通过DataBinding在xml中的一些根底应用。如果只是应用DataBinding这个性能,那就有点大材小用了。它还有一个很弱小的性能咱们还没有讲,那就是数据更新时主动刷新UI。

实现数据变动时自动更新UI

一个一般的实体类或者ViewModel被更新后,并不会让UI自动更新。而咱们心愿,当数据变更后UI要自动更新,那么要实现数据变动时自动更新UI,有三种办法能够应用,别离是BaseObservableObservableFieldObservableCollection

单向数据绑定:

  • BaseObservable

    BaseObservable提供了两个刷新UI的办法,别离是 notifyPropertyChanged() 和 notifyChange() 。
    • 第一步:批改实体类

      将咱们的实体类继承与BaseObservable。须要响应变动的字段,就在对应变量的get函数上加 @Bindable 。而后set中notifyChange是kotlin的写法,免去了java的getter setter的形式。成员属性须要响应变动的,就在其set函数中,notify一下属性变动,那么set的时候,databinding就会感知到。
      import androidx.databinding.BaseObservableimport androidx.databinding.Bindableimport androidx.databinding.library.baseAdapters.BRclass User() : BaseObservable() {    constructor(name: String, age: Int) : this() {        this.name = name        this.age = age    }    //这是独自在set上@bindable,name能够为申明private    var name: String = ""        set(value) {            field = value            notifyPropertyChanged(BR.name)        }        @Bindable        get() = field    //这是在整个变量上申明@bindable,所以必须是public的    @Bindable    var age:Int = 18        set(value) {            field = value            notifyPropertyChanged(BR.age)        }        get() = field}
    • 第二步:在Activity中应用

      class MainActivity : AppCompatActivity() {    private val TAG = "MainActivity"    private lateinit var mainBinding: ActivityMainBinding    private lateinit var mainUser: User    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        mainBinding =            DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)        mainUser = User("Taxze", 18)        mainUser.addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {            override fun onPropertyChanged(sender: Observable, propertyId: Int) {                when {                    BR.user == propertyId -> {                        Log.d(TAG, "BR.user")                    }                    BR.age == propertyId -> {                        Log.d(TAG, "BR.age")                    }                }            }        })        mainBinding.user = mainUser        mainBinding.onClickPresenter = OnClickPresenter()    }    inner class OnClickPresenter {        fun changeName() {            mainUser.name = "Taxze2222222"        }    }}
    • 须要留神的点

      • 官方网站只是提醒了开启DataBinding只须要在build.gradle中退出上面这行代码
      • buildFeatures {    dataBinding true}

        然而,如果你想更好的应用DataBinding这是不够的,你还须要增加这些配置:

      • compileOptions {    sourceCompatibility JavaVersion.VERSION_1_8    targetCompatibility JavaVersion.VERSION_1_8}kotlinOptions {    jvmTarget = '1.8'}
      • 重点:在应用DataBinding得时候,BR对象,发现调用不了,生成也会报错,运行,须要在咱们build.gradle中进行一下配置:

        apply plugin: 'kotlin-kapt'kapt {    generateStubs = true}

        而后从新运行一遍代码,你就会发现,BR文件主动生成啦!

  • ObservableField

    解说了BaseObservable后,当初来将建最简略也是最罕用的。只须要将实体类变动成这样即可:
    //留神observable的属性须要public权限,否则dataBinding则无奈通过反射解决数据响应class User() : BaseObservable() {    var name: ObservableField<String> = ObservableField("Taxze")    var age:ObservableInt = ObservableInt(18)}
  • ObservableCollection

    dataBinding 也提供了包装类用于代替原生的 List 和 Map,别离是 ObservableList 和 ObservableMap

    实体类批改:

    //伪代码,请勿间接cvclass User(){    var userMap = ObservableArrayMap<String,String>()}//应用时:mainUser.userMap["name"] = "Taxze"mainUser.userMap["age"] = "18"

    应用ObservableCollection后,xml与下面的略有不同,次要是数据的获取,须要指定Key

    //伪代码,请勿间接cv...<import type="android.databinding.ObservableMap" /><variable         name="userMap"         type="ObservableMap<String, String>" />//应用时:<TextView        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:hint="Name"        android:text="@{userMap[`userName`]}" />

双向数据绑定:

  • 只须要在之前的单向绑定的根底上,将布局文件@{}变为@={},用于针对属性的数据扭转的同时监听用户的更新

DataBinding在RecyclerView中的应用

在RecyclerView中应用DataBinding稍有变动,咱们在ViewHolder中进行binding对象的产生,以及数据对象的绑定。

咱们通过一个非常简单的例子来解说如何在RecyclerView中应用DataBinding。

效果图:

  • 第一步:创立实体类

    就是咱们之前的,应用了BaseObservable的那个实体类,这里就不放代码了

  • 第二步:创立activity_main用于寄存recyclerview

    <?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:id="@+id/activty_main"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context=".MainActivity">    <androidx.recyclerview.widget.RecyclerView        android:id="@+id/recyclerView"        android:layout_width="match_parent"        android:layout_height="wrap_content"        /></RelativeLayout>
  • 第三步:创立text_item.xml用于展现recyclerview中的每一行数据

    <?xml version="1.0" encoding="utf-8"?><layout xmlns:tools="http://schemas.android.com/tools"    xmlns:android="http://schemas.android.com/apk/res/android">    <data>        <import type="com.taxze.jetpack.databinding.User" />        <variable            name="user"            type="User" />    </data>    <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:orientation="vertical">        <LinearLayout            android:layout_width="match_parent"            android:layout_height="40dp"            android:background="#ffffff"            android:orientation="horizontal"            android:paddingStart="10dp">            <TextView                android:id="@+id/tv_name"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:layout_gravity="center_vertical"                android:text="@{`这个人的姓名是` + user.name}" />            <TextView                android:id="@+id/tv_age"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:layout_gravity="center_vertical"                android:layout_marginLeft="20dp"                android:text="@{String.valueOf(user.age)}" />        </LinearLayout>    </LinearLayout></layout>
  • 第四步:创立Adapter

    有了之前的根底之后,大家看上面这些代码应该很容易了,就不做过多解说啦
    import android.content.Contextimport android.view.LayoutInflaterimport android.view.ViewGroupimport androidx.databinding.DataBindingUtilimport androidx.recyclerview.widget.RecyclerViewimport com.taxze.jetpack.databinding.databinding.TextItemBindingclass FirstAdapter(users: MutableList<User>, context: Context) :    RecyclerView.Adapter<FirstAdapter.MyHolder>() {    //在构造函数中申明binding变量,这样holder能力援用到,如果不加val/var,就援用不到,就须要在class的{}内写get函数    class MyHolder(val binding: TextItemBinding) : RecyclerView.ViewHolder(binding.root)    private var users: MutableList<User> = arrayListOf()    private var context: Context    init {        this.users = users        this.context = context    }    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyHolder {        val inflater = LayoutInflater.from(context)        val binding: TextItemBinding =            DataBindingUtil.inflate(inflater, R.layout.text_item, parent, false)        return MyHolder(binding)    }    override fun onBindViewHolder(holder: MyHolder, position: Int) {        //java 写法能够setVariable        holder.binding.user = users[position]        holder.binding.executePendingBindings()    }    //kotlin中,return的形式,能够简写    override fun getItemCount() = users.size}
  • 第五步:在MainActivity中应用

    import android.os.Bundleimport android.view.Viewimport androidx.appcompat.app.AppCompatActivityimport androidx.recyclerview.widget.LinearLayoutManagerimport androidx.recyclerview.widget.RecyclerViewclass MainActivity : AppCompatActivity() {    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_main)        initView()    }    //给RecyclerView设置数据    private fun initView() {        val recyclerView = findViewById<View>(R.id.recyclerView) as RecyclerView        recyclerView.layoutManager = LinearLayoutManager(this)        val users: MutableList<User> = ArrayList()        for (i in 0..100) {            val user = User()            user.name = "Taxze"            user.age = i            users.add(user)        }        val adapter = FirstAdapter(users, this)        recyclerView.adapter = adapter    }}

这样就实现了在RecyclerView中应用DataBinding啦。

高级用法

第一个:用于appCompatImageView的自定义属性

//伪代码,请勿间接cv/** * 用于appCompatImageView的自定义属性,bind:imgSrc,命名空间bind:能够省略,也就是写作 imgSrc亦可。能够用于加载url的图片 * 函数名也是随便,次要是value的申明,就是新加的属性名了,能够多个属性同用,并配置是否必须一起作用 * 函数名随便,办法签名才重要,匹配对象控件,以及属性参数。 * 这里还能够增加old 参数,获取批改新参数 之前对应的值。 * todo 加载网络图片,须要网络权限!!! */@JvmStatic@BindingAdapter(value = ["bind:imgSrc"], requireAll = false)fun urlImageSrc(view: AppCompatImageView, /*old: String?, */url: String) {    Glide.with(view)        .load(url)        .placeholder(R.drawable.img_banner)        .centerInside()        .into(view)}

第二个:配合swipeRefreshLayout的刷新状态的感知

  • 第一步:单向的,数据变动,刷新UI

    //伪代码,请勿间接cv@JvmStatic@BindingAdapter("sfl_refreshing", requireAll = false)fun setSwipeRefreshing(view: SwipeRefreshLayout, oldValue: Boolean, newValue: Boolean) {    //判断是否是新的值,防止陷入死循环    if (oldValue != newValue)        view.isRefreshing = newValue}
  • 第二步:ui的状态,反向绑定给数据变动

    //伪代码,请勿间接cv@JvmStatic@BindingAdapter("sfl_refreshingAttrChanged", requireAll = false)fun setRefreshCallback(view: SwipeRefreshLayout, listener: InverseBindingListener?) {    listener ?: return    view.setOnRefreshListener {        //由ui层的刷新状态变动,反向告诉数据层的变动        listener.onChange()    }}
  • 第三步: 反向绑定的实现

    //伪代码,请勿间接cv/** * 反向绑定的实现,将UI的变动,回调给bindingListener,listener就会onChange,告诉数据变动 * 留神这里的attribute和event,是跟下面两步配合统一才无效 */@JvmStatic@InverseBindingAdapter(attribute = "sfl_refreshing", event = "sfl_refreshingAttrChanged")fun isSwipeRefreshing(view: SwipeRefreshLayout): Boolean {    return view.isRefreshing}

DataBinding配合ViewModel&LiveData一起应用

我将通过一个简略的例子带大家学习他们如何一起应用,话不多说,先上效果图:

  • 第一步:创立UserModel

    //将其继承于AndroidViewModel(AndroidViewModel也是继承于ViewModel的,然而ViewModel自身没有方法取得 Context,AndroidViewModel提供Application用作Context,并专门提供 Application 单例)//UserName 应用MutableLiveDataclass UserModel(application: Application) : AndroidViewModel(application) {    var UserName = MutableLiveData("")}

<!---->

  • 第二步:创立activity_main和对应的MainActivity

    <?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="loginModel"            type="com.taxze.jetpack.databinding.model.UserModel" />    </data>    <androidx.constraintlayout.widget.ConstraintLayout        android:layout_width="match_parent"        android:layout_height="match_parent">        <LinearLayout            android:id="@+id/linearLayout"            style="@style/InputBoxStyle"            android:layout_width="0dp"            android:layout_height="wrap_content"            android:layout_marginStart="17dp"            android:layout_marginEnd="17dp"            app:layout_constraintBottom_toTopOf="@+id/guideline2"            app:layout_constraintEnd_toEndOf="parent"            app:layout_constraintHorizontal_bias="0.0"            app:layout_constraintStart_toStartOf="parent"            tools:ignore="MissingConstraints">            <EditText                android:id="@+id/editText"                style="@style/EditTextStyle"                android:layout_width="match_parent"                android:layout_height="50dp"                android:hint="请输出账号"                android:text="@={loginModel.UserName}"                tools:ignore="MissingConstraints" />        </LinearLayout>        <TextView            android:id="@+id/textView2"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:gravity="center"            android:text='@{"您输出的账号名是:"+loginModel.UserName,default=123123123}'            android:textSize="24sp"            app:layout_constraintBottom_toBottomOf="parent"            app:layout_constraintEnd_toEndOf="parent"            app:layout_constraintStart_toStartOf="parent"            app:layout_constraintTop_toBottomOf="@+id/button"            tools:ignore="MissingConstraints" />        <androidx.constraintlayout.widget.Guideline            android:id="@+id/guideline2"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:orientation="horizontal"            app:layout_constraintGuide_percent="0.4" />        <Button            android:id="@+id/button"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_marginTop="32dp"            android:background="@drawable/button_drawable"            android:text="登录"            app:layout_constraintEnd_toEndOf="parent"            app:layout_constraintStart_toStartOf="parent"            app:layout_constraintTop_toBottomOf="@+id/linearLayout"            tools:ignore="MissingConstraints" />    </androidx.constraintlayout.widget.ConstraintLayout></layout>

<!---->

  • 第三步:在MainActivity中绑定页面和绑定申明周期

    class MainActivity : AppCompatActivity() {    lateinit var viewDataBinding: ActivityMainBinding    lateinit var model: UserModel    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        //绑定页面        viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)        //绑定生命周期        viewDataBinding.lifecycleOwner = this        model = ViewModelProvider.AndroidViewModelFactory.getInstance(this.application)            .create(UserModel::class.java)        viewDataBinding.loginModel = model        ...    }}

<!---->

  • 第四步:传值

    viewDataBinding.button.setOnClickListener {            val intent = Intent(MainActivity@ this, SecondActivity::class.java)            intent.putExtra("user", "${model.UserName.value}")            startActivity(                intent            )        }
  • 第五步:在另外一个activity中调用

    class SecondActivity : AppCompatActivity() {    lateinit var viewDataBinding: ActivitySecondBinding    lateinit var userName: String    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        //绑定页面        viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_second)        //绑定生命周期        viewDataBinding.lifecycleOwner = this        userName = intent.getStringExtra("user").toString()        viewDataBinding.tvName.text = "登录的账号是:$userName"    }}

帮你踩坑:

  • TextViewtext属性,须要留神data不能为Number类型
  • xml中字符不能为中文(检查一下你的输入法)
  • 反射属性、函数必须是public
  • observableField数据的时候,在某些场合须要初始化,否则会运行报错!
  • 应用LiveData作为dataBinding的时候,须要在ui中设置binding.lifecycleOwner

尾述

这篇文章曾经很具体的讲了DataBinding的大部分用法,不过在看完文章后,你仍需多多实际,置信你很快就能够把握DataBinding啦 有问题欢送在评论区留言探讨~

对于我

Hello,我是Taxze,如果您感觉文章对您有价值,心愿您能给我的文章点个❤️,也欢送关注我的博客。

如果您感觉文章还差了那么点货色,也请通过关注督促我写出更好的文章——万一哪天我提高了呢?

根底系列:

2022 · 让我带你Jetpack架构组件从入门到精通 — Lifecycle

学会应用LiveData和ViewModel,我置信会让你在写业务时变得轻松

当你真的学会DataBinding后,你会发现“这玩意真香”! (本文)

以下局部还在码字,连忙点个珍藏吧

2022 · 让我带你Jetpack架构组件从入门到精通 — Navigation

2022 · 让我带你Jetpack架构组件从入门到精通 — Room

2022 · 让我带你Jetpack架构组件从入门到精通 — Paging3

2022 · 让我带你Jetpack架构组件从入门到精通 — WorkManager

2022 · 让我带你Jetpack架构组件从入门到精通 — ViewPager2

2022 · 让我带你Jetpack架构组件从入门到精通 — 登录注册页面实战(MVVM)

进阶系列:

协程 + Retrofit网络申请状态封装

Room 缓存封装

.....