{"id":13843742,"url":"https://github.com/leavesCZY/DataBindingSamples","last_synced_at":"2025-07-11T19:33:31.847Z","repository":{"id":108394351,"uuid":"134128798","full_name":"leavesCZY/DataBindingSamples","owner":"leavesCZY","description":"包含了 DataBinding 的大部分知识点","archived":false,"fork":false,"pushed_at":"2020-07-28T10:20:27.000Z","size":423,"stargazers_count":248,"open_issues_count":1,"forks_count":40,"subscribers_count":2,"default_branch":"kotlin","last_synced_at":"2024-08-04T17:13:29.079Z","etag":null,"topics":["databinding","databinding-android","databinding-samples"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/leavesCZY.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2018-05-20T07:25:43.000Z","updated_at":"2024-06-07T09:03:34.000Z","dependencies_parsed_at":null,"dependency_job_id":"f7167c9c-6fb2-4d1f-8f5b-1dfb7b8c6177","html_url":"https://github.com/leavesCZY/DataBindingSamples","commit_stats":null,"previous_names":["leavesc/databindingsamples"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leavesCZY%2FDataBindingSamples","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leavesCZY%2FDataBindingSamples/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leavesCZY%2FDataBindingSamples/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leavesCZY%2FDataBindingSamples/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/leavesCZY","download_url":"https://codeload.github.com/leavesCZY/DataBindingSamples/tar.gz/refs/heads/kotlin","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225729696,"owners_count":17515155,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["databinding","databinding-android","databinding-samples"],"created_at":"2024-08-04T17:02:25.907Z","updated_at":"2024-11-21T15:31:28.638Z","avatar_url":"https://github.com/leavesCZY.png","language":"Kotlin","funding_links":[],"categories":["Kotlin"],"sub_categories":[],"readme":"DataBinding 是谷歌官方发布的一个框架，顾名思义即为数据绑定，是 MVVM 模式在 Android 上的一种实现。借助该库，可以使用声明性格式（而非程序化地）将布局中的界面组件绑定到应用中的数据源，可以有效降低布局和业务代码之间的耦合性，使代码逻辑更加清晰。DataBinding 能够省去我们一直以来的 **findViewById()** 步骤，大量减少 **Activity** 内的代码，数据能够单向或双向绑定到 **layout** 文件中，有助于防止内存泄漏，而且能自动进行空检测以避免空指针异常\n\n启用 DataBinding 的方法是在对应 Model 的 **build.gradle** 文件里加入以下代码，同步后就能引入对 DataBinding 的支持\n\n```groovy\nandroid {\n    dataBinding {\n        enabled = true\n    }\n    \n    //或者是\n    buildFeatures {\n        dataBinding = true\n    }\n}\n```\n\n### 一、基础入门\n\n启用 DataBinding 后，这里先来看下如何在布局文件中绑定指定的变量\n\n打开布局文件，选中根布局的 **ViewGroup**，按住 **Alt + 回车键**，点击 “**Convert to data binding layout**”，就可以自动生成 DataBinding 需要的布局规则\n\n![](https://images.xiaozhuanlan.com/photo/2020/30672cb9ed72c87ee0dad7dc7dbd74a5.jpg)\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n\u003clayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\u003e\n\n    \u003cdata\u003e\n\n    \u003c/data\u003e\n\n    \u003candroidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\u003e\n\n    \u003c/androidx.constraintlayout.widget.ConstraintLayout\u003e\n\u003c/layout\u003e\n```\n\n和原始布局的区别在于多出了一个 **layout** 标签将原布局包裹了起来，**data** 标签用于声明要用到的变量以及变量类型，要实现 MVVM 的 ViewModel 就需要把数据（Model）与 UI（View）进行绑定，**data** 标签的作用就像一个桥梁搭建了 View 和 Model 之间的通道\n\n这里先来声明一个 Modle \n\n```kotlin\ndata class UserBean(var name: String, var password: String)\n```\n\n在 **data** 标签里声明要使用到的变量名、类的全路径\n\n```java\n    \u003cdata\u003e\n        \u003cvariable\n            name=\"userInfo\"\n            type=\"github.leavesc.databinding.UserBean\" /\u003e\n    \u003c/data\u003e\n```\n\n如果 User 类型要多处用到，也可以直接将之 **import** 进来，这样就不用每次都指明整个包名路径了，而 `java.lang.*` 包中的类会被自动导入，所以可以直接使用\n\n```java\n    \u003cdata\u003e\n        \u003cimport type=\"github.leavesc.databinding.UserBean\"/\u003e\n        \u003cvariable\n            name=\"userInfo\"\n            type=\"UserBean\"/\u003e\n    \u003c/data\u003e\n```\n\n如果存在 **import** 的类名相同的情况，可以使用 **alias** 指定别名\n\n```xml\n    \u003cdata\u003e\n        \u003cimport type=\"github.leavesc.databinding.UserBean\" /\u003e\n        \u003cimport\n            alias=\"OtherUserBean\"\n            type=\"github.leavesc.databinding.other.UserBean\" /\u003e\n        \u003cvariable\n            name=\"userInfo\"\n            type=\"UserBean\" /\u003e\n        \u003cvariable\n            name=\"otherUserBean\"\n            type=\"OtherUserBean\" /\u003e\n    \u003c/data\u003e\n```\n\n这里声明了一个 UserBean 类型的变量 **userInfo**，我们要做的就是使这个变量与两个 TextView 控件挂钩，通过设置 userInfo 的变量值同时使 TextView 显示相应的文本\n完整的布局代码如下所示\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n\u003clayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\u003e\n\n    \u003cdata\u003e\n\n        \u003cimport type=\"github.leavesc.databinding.UserBean\" /\u003e\n\n        \u003cvariable\n            name=\"userInfo\"\n            type=\"UserBean\" /\u003e\n\n    \u003c/data\u003e\n\n    \u003cLinearLayout xmlns:tools=\"http://schemas.android.com/tools\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_margin=\"20dp\"\n        android:orientation=\"vertical\"\n        tools:context=\".MainActivity2\"\u003e\n\n        \u003cTextView\n            style=\"@style/TextViewStyle\"\n            android:text=\"单向数据绑定：\" /\u003e\n\n        \u003cTextView\n            style=\"@style/TextViewStyle\"\n            android:text=\"@{userInfo.name}\" /\u003e\n\n        \u003cTextView\n            style=\"@style/TextViewStyle\"\n            android:text=\"@{userInfo.password}\" /\u003e\n\n    \u003c/LinearLayout\u003e\n\n\u003c/layout\u003e\n```\n\n通过 `@{userInfo.name} ` 使 TextView 引用到相关的变量，DataBinding 会将之映射到相应的 **getter** 方法\n之后可以在 Activity 中通过 `DataBindingUtil` 设置布局文件，省略原先 Activity 的 `setContentView()` 方法，并为变量 **userInfo** 赋值\n\n```kotlin\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        val binding: ActivityMain2Binding =\n            DataBindingUtil.setContentView(this, R.layout.activity_main2)\n\n        val user = UserBean(\"leavesC\", \"123456\")\n        binding.userInfo = user\n    }\n```\n\n![](https://images.xiaozhuanlan.com/photo/2020/3309050468c13b99be6801a59c5a94c3.png)\n\n由于 `@{userInfo.name}`在布局文件中并没有明确的值，所以在预览视图中什么都不会显示，不便于观察文本的大小和字体颜色等属性，此时可以为之设定默认值（文本内容或者是字体大小等属性都适用），默认值将只在预览视图中显示，且默认值不能包含引号\n\n```xml\n\tandroid:text=\"@{userInfo.name,default=defaultValue}\"\n```\n\n此外，也可以通过 ActivityMain2Binding 直接获取到指定 ID 的控件\n\n```kotlin\n\tbinding.tvUserName.text = \"leavesC\"\n```\n\n每个数据绑定布局文件都会生成一个绑定类，**ViewDataBinding** 的实例名是根据布局文件名来生成，将之改为首字母大写的驼峰命名法来命名，并省略布局文件名包含的下划线。控件的获取方式类似，但首字母小写\n\n也可以通过如下方式自定义 ViewDataBinding 的实例名\n\n```xml\n    \u003cdata class=\"CustomBinding\"\u003e\n\n    \u003c/data\u003e\n```\n\n此外，在绑定表达式中会根据需要生成一个名为`context`的特殊变量，`context`的值是根 **View** 的`getContext()`方法返回的`Context`对象， `context`变量会被具有该名称的显式变量声明所覆盖\n\nDatabinding 同样是支持在 **Fragment** 和 **RecyclerView** 中使用 。例如，可以看 Databinding 在 Fragment 中的使用\n\n```kotlin\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?\n    ): View? {\n        val binding: FragmentBlankBinding =\n            DataBindingUtil.inflate(inflater, R.layout.fragment_blank, container, false)\n        binding.hint = \"Hello\"\n        return binding.root\n    }\n```\n\n**以上实现数据绑定的方式，每当绑定的变量发生变化的时候，都需要重新向 ViewDataBinding 传递新的变量值才能刷新 UI 。接下来看如何实现自动刷新 UI**\n\n### 二、单向数据绑定\n\n实现数据变化自动驱动 UI 刷新的方式有三种：`BaseObservable`、`ObservableField`、`ObservableCollection` \n\n#### 2.1、BaseObservable\n\n一个纯净的 ViewModel 类被更新后，并不会让 UI 自动更新。而数据绑定后，我们自然会希望数据变更后 UI 会即时刷新，Observable 就是为此而生的概念\n\n**BaseObservable** 提供了 **notifyChange()** 和 **notifyPropertyChanged()** 两个方法，前者会刷新所有的值域，后者则只更新对应 **BR** 的 **flag**，该 BR 的生成通过注释 **@Bindable** 生成，可以通过 **BR notify** 特定属性关联的视图\n\n```kotlin\nclass GoodsBean : BaseObservable() {\n\n    @Bindable\n    var name = \"\"\n        set(value) {\n            field = value\n            //只更新本字段\n            notifyPropertyChanged(BR.name)\n        }\n\n    @Bindable\n    var details = \"\"\n        set(value) {\n            field = value\n            //更新所有字段\n            notifyChange()\n        }\n\n    var price = 0F\n\n}\n```\n\n在 **setName()** 方法中更新的只是本字段，而 **setDetails()** 方法中更新的是所有字段\n\n添加两个按钮用于改变 goods 变量的三个属性值，由此可以看出两个 notify 方法的区别。当中涉及的按钮点击事件绑定，在下面也会讲到\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n\u003clayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\u003e\n\n    \u003cdata\u003e\n\n        \u003cimport type=\"github.leavesc.databinding.GoodsBean\" /\u003e\n\n        \u003cimport type=\"github.leavesc.databinding.MainActivity3.GoodsHandler\" /\u003e\n\n        \u003cvariable\n            name=\"goods\"\n            type=\"GoodsBean\" /\u003e\n\n        \u003cvariable\n            name=\"goodsHandler\"\n            type=\"GoodsHandler\" /\u003e\n    \u003c/data\u003e\n\n    \u003cLinearLayout xmlns:tools=\"http://schemas.android.com/tools\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\"\n        android:padding=\"20dp\"\n        tools:context=\".MainActivity3\"\u003e\n\n        \u003cTextView\n            style=\"@style/TextViewStyle\"\n            android:text=\"@{goods.name}\" /\u003e\n\n        \u003cTextView\n            style=\"@style/TextViewStyle\"\n            android:text=\"@{goods.details}\" /\u003e\n\n        \u003cTextView\n            style=\"@style/TextViewStyle\"\n            android:text=\"@{String.valueOf(goods.price)}\" /\u003e\n\n        \u003cButton\n            style=\"@style/BtnStyle\"\n            android:onClick=\"@{()-\u003egoodsHandler.changeGoodsName()}\"\n            android:text=\"改变属性 name\" /\u003e\n\n        \u003cButton\n            style=\"@style/BtnStyle\"\n            android:onClick=\"@{()-\u003egoodsHandler.changeGoodsDetails()}\"\n            android:text=\"改变属性 details 和 price\" /\u003e\n\n    \u003c/LinearLayout\u003e\n\u003c/layout\u003e\n```\n\n```kotlin\n/**\n * 作者：leavesC\n * 时间：2020/6/29 22:37\n * 描述：\n * GitHub：https://github.com/leavesC\n */\nclass MainActivity3 : AppCompatActivity() {\n\n    private val TAG = \"Main3Activity\"\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        val binding: ActivityMain3Binding =\n            DataBindingUtil.setContentView(this, R.layout.activity_main3)\n\n        val goods = GoodsBean()\n        goods.name = \"code\"\n        goods.details = \"hi\"\n        goods.price = 24f\n\n        binding.goods = goods\n        binding.goodsHandler = GoodsHandler(goods)\n\n        goods.addOnPropertyChangedCallback(object : OnPropertyChangedCallback() {\n            override fun onPropertyChanged(sender: Observable, propertyId: Int) {\n                when (propertyId) {\n                    BR.name -\u003e {\n                        Log.e(TAG, \"BR.name\")\n                    }\n                    BR.details -\u003e {\n                        Log.e(TAG, \"BR.details\")\n                    }\n                    BR._all -\u003e {\n                        Log.e(TAG, \"BR._all\")\n                    }\n                    else -\u003e {\n                        Log.e(TAG, \"未知\")\n                    }\n                }\n            }\n        })\n    }\n\n    class GoodsHandler(private val goodsBean: GoodsBean) {\n\n        fun changeGoodsName() {\n            goodsBean.price = Random.nextFloat()\n            goodsBean.name = \"code\" + Random.nextInt(100)\n        }\n\n        fun changeGoodsDetails() {\n            goodsBean.price = Random.nextFloat()\n            goodsBean.details = \"hi\" + Random.nextInt(100)\n        }\n    }\n\n}\n```\n\n![](https://upload-images.jianshu.io/upload_images/2552605-57d2a2611feb7f60.gif?imageMogr2/auto-orient/strip)\n\n**可以看到，name 视图的刷新没有同时刷新 price 视图，而 details 视图刷新的同时也刷新了 price 视图**\n\n实现了 **Observable** 接口的类允许注册一个监听器，当可观察对象的属性更改时就会通知这个监听器，此时就需要用到 `OnPropertyChangedCallback`\n\n当中 `propertyId` 就用于标识特定的字段\n\n```kotlin\n\t\tgoods.addOnPropertyChangedCallback(object : OnPropertyChangedCallback() {\n            override fun onPropertyChanged(sender: Observable, propertyId: Int) {\n                when (propertyId) {\n                    BR.name -\u003e {\n                        Log.e(TAG, \"BR.name\")\n                    }\n                    BR.details -\u003e {\n                        Log.e(TAG, \"BR.details\")\n                    }\n                    BR._all -\u003e {\n                        Log.e(TAG, \"BR._all\")\n                    }\n                    else -\u003e {\n                        Log.e(TAG, \"未知\")\n                    }\n                }\n            }\n        })\n```\n\n#### 2.2、ObservableField\n\n继承于 Observable 类相对来说限制有点高，且也需要进行 notify 操作，因此为了简单起见可以选择使用 **ObservableField**。ObservableField 可以理解为官方对 BaseObservable 中字段的注解和刷新等操作的封装，官方原生提供了对基本数据类型的封装，例如 **ObservableBoolean、ObservableByte、ObservableChar、ObservableShort、ObservableInt、ObservableLong、ObservableFloat、ObservableDouble** 以及 **ObservableParcelable** ，也可通过 **ObservableField** 泛型来申明其他类型\n\n```kotlin\nclass ObservableGoodsBean(name: String, details: String, price: Float) {\n    val name: ObservableField\u003cString\u003e = ObservableField(name)\n    val details: ObservableField\u003cString\u003e = ObservableField(details)\n    val price: ObservableFloat = ObservableFloat(price)\n}\n```\n\n对 ObservableGoods 属性值的改变都会立即触发 UI 刷新，概念上与 Observable 区别不大，具体效果可看之后提供的 demo，这里不再赘述\n\n#### 2.3、ObservableCollection\n\nDataBinding 也提供了包装类用于替代原生的 `List` 和 `Map`，分别是 `ObservableList` 和 `ObservableMap`,当其包含的数据发生变化时，绑定的视图也会随之进行刷新\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n\u003clayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\u003e\n\n    \u003cdata\u003e\n\n        \u003cimport type=\"androidx.databinding.ObservableList\" /\u003e\n\n        \u003cimport type=\"androidx.databinding.ObservableMap\" /\u003e\n\n        \u003cvariable\n            name=\"list\"\n            type=\"ObservableList\u0026lt;String\u0026gt;\" /\u003e\n\n        \u003cvariable\n            name=\"map\"\n            type=\"ObservableMap\u0026lt;String,String\u0026gt;\" /\u003e\n\n        \u003cvariable\n            name=\"index\"\n            type=\"int\" /\u003e\n\n        \u003cvariable\n            name=\"key\"\n            type=\"String\" /\u003e\n\n    \u003c/data\u003e\n\n    \u003cLinearLayout xmlns:tools=\"http://schemas.android.com/tools\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\"\n        tools:context=\".MainActivity5\"\u003e\n\n        \u003cTextView\n            style=\"@style/TextViewStyle\"\n            android:text=\"@{list[index]}\" /\u003e\n\n        \u003cTextView\n            style=\"@style/TextViewStyle\"\n            android:text=\"@{map[key]}\" /\u003e\n\n        \u003cButton\n            style=\"@style/BtnStyle\"\n            android:onClick=\"onClick\"\n            android:text=\"改变数据\" /\u003e\n\n    \u003c/LinearLayout\u003e\n\u003c/layout\u003e\n```\n\n```java\n/**\n * 作者：leavesC\n * 时间：2020/6/29 22:49\n * 描述：\n * GitHub：https://github.com/leavesC\n */\nclass MainActivity5 : AppCompatActivity() {\n\n    private val map = ObservableArrayMap\u003cString, String\u003e().apply {\n        put(\"name\", \"leavesC\")\n        put(\"age\", \"24\")\n    }\n\n    private val list = ObservableArrayList\u003cString\u003e().apply {\n        add(\"Ye\")\n        add(\"leavesC\")\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        val binding: ActivityMain5Binding =\n            DataBindingUtil.setContentView(this, R.layout.activity_main5)\n        binding.map = map\n        binding.list = list\n        binding.index = 0\n        binding.key = \"name\"\n    }\n\n    fun onClick(view: View) {\n        map[\"name\"] = \"leavesC,\" + Random.nextInt(100)\n    }\n\n}\n```\n\n![](https://images.xiaozhuanlan.com/photo/2020/821ab561120ee71503f8751c353946b1.gif)\n\n### 三、双向数据绑定\n\n双向绑定的意思即为当数据改变时同时使视图刷新，而视图改变时也可以同时改变数据\n\n看以下例子，当 EditText 的输入内容改变时，会同时同步到变量 `goods`,绑定变量的方式比单向绑定多了一个等号：` android:text=\"@={goods.name}\"`\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n\u003clayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\u003e\n\n    \u003cdata\u003e\n\n        \u003cimport type=\"github.leavesc.databinding.UserBean\" /\u003e\n\n        \u003cimport type=\"github.leavesc.databinding.ObservableGoodsBean\" /\u003e\n\n        \u003cvariable\n            name=\"userInfo\"\n            type=\"UserBean\" /\u003e\n\n        \u003cvariable\n            name=\"goods\"\n            type=\"ObservableGoodsBean\" /\u003e\n    \u003c/data\u003e\n\n    \u003cLinearLayout xmlns:tools=\"http://schemas.android.com/tools\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_margin=\"20dp\"\n        android:orientation=\"vertical\"\n        tools:context=\".MainActivity2\"\u003e\n\n        \u003cTextView\n            style=\"@style/TextViewStyle\"\n            android:text=\"单向数据绑定：\" /\u003e\n\n        \u003cTextView\n            style=\"@style/TextViewStyle\"\n            android:text=\"@{userInfo.name}\" /\u003e\n\n        \u003cTextView\n            style=\"@style/TextViewStyle\"\n            android:text=\"@{userInfo.password}\" /\u003e\n\n        \u003cTextView\n            style=\"@style/TextViewStyle\"\n            android:text=\"双向数据绑定：\" /\u003e\n\n        \u003cTextView\n            style=\"@style/TextViewStyle\"\n            android:text=\"@{goods.name}\" /\u003e\n\n        \u003cEditText\n            style=\"@style/EditTextStyle\"\n            android:text=\"@={goods.name}\" /\u003e\n\n    \u003c/LinearLayout\u003e\n\n\u003c/layout\u003e\n```\n\n```kotlin\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        val binding: ActivityMain2Binding =\n            DataBindingUtil.setContentView(this, R.layout.activity_main2)\n\n        val user = UserBean(\"leavesC\", \"123456\")\n        binding.userInfo = user\n\n        val goods = ObservableGoodsBean(\"code\", \"coding\", 23F)\n        binding.goods = goods\n    }\n```\n\n![](https://images.xiaozhuanlan.com/photo/2020/d80c18e5480e78a0b66c8faac0455488.gif)\n\n### 四、事件绑定\n\n严格意义上来说，事件绑定也是一种变量绑定，只不过设置的变量是回调接口而已\n事件绑定可用于以下多种回调事件\n\n- android:onClick\n- android:onLongClick\n- android:afterTextChanged\n- android:onTextChanged\n- ...\n\n在 Activity 内部新建一个 **UserPresenter** 类来声明 **onClick()** 和 **afterTextChanged()** 事件相应的回调方法\n\n```kotlin\n\tclass UserPresenter(\n        private val context: Context,\n        private val user: UserBean,\n        private val binding: ActivityMain6Binding\n    ) {\n\n        fun onUserNameClick(user: UserBean) {\n            Toast.makeText(context, \"用户名：\" + user.name, Toast.LENGTH_SHORT).show()\n        }\n\n        fun afterTextChanged(s: Editable) {\n            user.name = s.toString()\n            binding.userInfo = user\n        }\n\n        fun afterUserPasswordChanged(s: Editable) {\n            user.password = s.toString()\n            binding.userInfo = user\n        }\n\n    }\n```\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n\u003clayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\u003e\n\n    \u003cdata\u003e\n\n        \u003cimport type=\"github.leavesc.databinding.UserBean\" /\u003e\n\n        \u003cimport type=\"github.leavesc.databinding.MainActivity6.UserPresenter\" /\u003e\n\n        \u003cimport type=\"github.leavesc.databinding.StringUtils\" /\u003e\n\n        \u003cvariable\n            name=\"userInfo\"\n            type=\"UserBean\" /\u003e\n\n        \u003cvariable\n            name=\"userPresenter\"\n            type=\"UserPresenter\" /\u003e\n    \u003c/data\u003e\n\n    \u003cLinearLayout xmlns:tools=\"http://schemas.android.com/tools\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_margin=\"20dp\"\n        android:orientation=\"vertical\"\n        tools:context=\".MainActivity6\"\u003e\n\n        \u003cTextView\n            style=\"@style/TextViewStyle\"\n            android:onClick=\"@{()-\u003euserPresenter.onUserNameClick(userInfo)}\"\n            android:text=\"@{StringUtils.INSTANCE.toUpperCase(userInfo.name)}\" /\u003e\n\n        \u003cTextView\n            style=\"@style/TextViewStyle\"\n            android:text=\"@{userInfo.password}\" /\u003e\n\n        \u003cEditText\n            style=\"@style/EditTextStyle\"\n            android:afterTextChanged=\"@{userPresenter.afterTextChanged}\"\n            android:hint=\"用户名\" /\u003e\n\n        \u003cEditText\n            style=\"@style/EditTextStyle\"\n            android:afterTextChanged=\"@{userPresenter.afterUserPasswordChanged}\"\n            android:hint=\"密码\" /\u003e\n\n    \u003c/LinearLayout\u003e\n\n\u003c/layout\u003e\n```\n\n方法引用的方式与调用函数的方式类似，既可以选择保持事件回调方法的签名一致：**@{userPresenter.afterTextChanged}**，此时方法名可以不一样，但方法参数和返回值必须和原始的回调函数保持一致。也可以引用不遵循默认签名的函数：**@{()-\u003euserPresenter.onUserNameClick(userInfo)}**，这里用到了 Lambda 表达式，这样就可以不遵循默认的方法签名，将`userInfo`对象直接传回点击方法中。此外，也可以使用方法引用 **::** 的形式来进行事件绑定\n\n![](https://images.xiaozhuanlan.com/photo/2020/950b0de899c83b226e5922b3496e20f7.gif)\n\n### 五、使用类方法\n\n首先定义一个全局的静态变量 StringUtils，其内部包含的方法在使用上就类似于 Java 的静态方法\n\n```kotlin\nobject StringUtils {\n\n    fun toUpperCase(str: String): String {\n        return str.toUpperCase(Locale.ROOT)\n    }\n\n}\n```\n\n在 data 标签中导入该工具类\n\n```xml\n\t\t\u003cimport type=\"github.leavesc.databinding.StringUtils\" /\u003e\n```\n\n然后就可以像对待一般的函数一样来调用了\n\n```xml\n        \u003cTextView\n            style=\"@style/TextViewStyle\"\n            android:onClick=\"@{()-\u003euserPresenter.onUserNameClick(userInfo)}\"\n            android:text=\"@{StringUtils.INSTANCE.toUpperCase(userInfo.name)}\" /\u003e\n```\n\n### 六、运算符\n\n#### 6.1、基础运算符\n\nDataBinding 支持在布局文件中使用以下运算符、表达式和关键字\n\n- 算术运算符：  +  -  /  *  %\n- 字符串合并运算符：  +\n- 逻辑运算符：  \u0026\u0026  ||\n- 二元运算符：  \u0026  |  ^\n- 一元运算符：  +  -  !  ~\n- 移位运算符： \u003e\u003e  \u003e\u003e\u003e  \u003c\u003c\n- 比较运算符： ==  \u003e  \u003c  \u003e=  \u003c=\n- Instanceof\n- 分组运算符： ()\n- 字面量运算符：character, String, numeric, null\n- 类型转换\n- 方法调用\n- 字段访问\n- 数组访问 []\n- 三元运算符： ?:\n\n目前不支持以下操作\n\n- this\n- super\n- new\n- 显示泛型调用\n\n此外，DataBinding 还支持以下几种形式的调用\n\n#### 6.2、Null Coalescing \n\n空合并运算符 **??** 会取第一个不为 null 的值作为返回值\n\n```xml\n \u003cTextView\n     android:layout_width=\"match_parent\"\n     android:layout_height=\"wrap_content\"\n     android:text=\"@{user.name ?? user.password}\" /\u003e\n```\n\n等价于\n\n```java\n\tandroid:text=\"@{user.name != null ? user.name : user.password}\"\n```\n\n#### 6.3、属性控制\n\n可以通过变量值来控制 View 的属性\n\n```xml\n \u003cTextView\n     android:layout_width=\"match_parent\"\n     android:layout_height=\"wrap_content\"\n     android:text=\"可见性变化\"\n     android:visibility=\"@{user.male  ? View.VISIBLE : View.GONE}\" /\u003e\n```\n\n#### 6.4、避免空指针异常\n\nDataBinding 也会自动帮助我们避免空指针异常\n例如，如果 **\"@{userInfo.password}\"** 中 **userInfo** 为 **null** 的话，**userInfo.password** 会被赋值为默认值 **null**，而不会抛出空指针异常\n\n### 七、include 和 viewStub\n\n#### 7.1、include\n\n对于 include 的布局文件，一样是支持通过 dataBinding 来进行数据绑定，此时一样需要在待 include 的布局中依然使用 layout 标签，声明需要使用到的变量\n\n`view_include.xml` \n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n\u003clayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\u003e\n\n    \u003cdata\u003e\n\n        \u003cimport type=\"github.leavesc.databinding.UserBean\" /\u003e\n\n        \u003cvariable\n            name=\"userInfo\"\n            type=\"UserBean\" /\u003e\n    \u003c/data\u003e\n\n    \u003candroidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"#FF7043\"\u003e\n\n        \u003cTextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center\"\n            android:padding=\"20dp\"\n            android:text=\"@{userInfo.name}\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\" /\u003e\n\n    \u003c/androidx.constraintlayout.widget.ConstraintLayout\u003e\n\u003c/layout\u003e\n```\n\n在主布局文件中将相应的变量传递给 include 布局，从而使两个布局文件之间共享同一个变量\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n\u003clayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:bind=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\u003e\n\n    \u003cdata\u003e\n     \t\u003cimport type=\"github.leavesc.databinding.UserBean\" /\u003e\n        \u003cvariable\n            name=\"userInfo\"\n            type=\"UserBean\" /\u003e\n    \u003c/data\u003e\n\n    \u003cLinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\"\u003e\n        \n        \u003cinclude\n            layout=\"@layout/view_include\"\n            bind:userInfo=\"@{userInfo}\" /\u003e\n        \n    \u003c/LinearLayout\u003e\n\u003c/layout\u003e\n```\n\n#### 7.2、viewStub\n\ndataBinding 一样支持 ViewStub 布局\n\n在布局文件中引用 viewStub 布局\n\n```xml\n   \u003cViewStub\n        android:id=\"@+id/view_stub\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout=\"@layout/view_stub\"/\u003e\n```\n\n获取到 ViewStub 对象，由此就可以来控制 ViewStub 的可见性\n\n```java\n\tprivate val binding: ActivityMain7Binding by lazy {\n        DataBindingUtil.setContentView(this, R.layout.activity_main7) as ActivityMain7Binding\n    }\n\n\tval view = binding.viewStub.viewStub?.inflate()\n```\n\n如果需要为 ViewStub 绑定变量值，则 ViewStub 文件一样要使用 layout 标签进行布局，主布局文件使用自定义的 bind 命名空间将变量传递给 ViewStub\n\n```xml\n    \u003cViewStub\n        android:id=\"@+id/view_stub\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout=\"@layout/view_stub\"\n        bind:userInfo=\"@{userInfo}\" /\u003e\n```\n\n如果在 xml 中没有使用 `bind:userInfo=\"@{userInf}\" `对 ViewStub 进行数据绑定，则可以等到当 ViewStub **Inflate** 时再绑定变量，此时需要为 ViewStub 设置 `setOnInflateListener`回调函数，在回调函数中进行数据绑定\n\n```java\n\t\tbinding.viewStub.setOnInflateListener { _, inflated -\u003e\n            //如果在 xml 中没有使用 bind:userInfo=\"@{userInf}\" 对 viewStub 进行数据绑定\n            //那么可以在此处进行手动绑定\n            val viewStubBinding: ViewStubBinding? = DataBindingUtil.bind(inflated)\n            viewStubBinding?.let {\n                viewStubBinding.userInfo = user\n            }\n            Log.e(TAG, \"onInflate\")\n        }\n```\n\n### 八、BindingAdapter\n\ndataBinding 提供了 **BindingAdapter** 这个注解用于支持自定义属性，或者是修改原有属性。注解值可以是已有的 xml 属性，例如 `android:src`、`android:text`等，也可以自定义属性然后在 xml 中使用\n\n例如，对于一个 ImageView ，我们希望在某个变量值发生变化时，可以动态改变显示的图片，此时就可以通过 BindingAdapter 来实现\n\n需要先定义一个静态方法，为之添加 BindingAdapter 注解，注解值是为 ImageView 控件自定义的属性名，而该静态方法的两个参数可以这样来理解：当 ImageView 控件的 url 属性值发生变化时，dataBinding 就会将 ImageView 实例以及新的 url 值传递给 loadImage() 方法，从而可以在此动态改变 ImageView 的相关属性\n\n```kotlin\n@BindingAdapter(\"url\")\nfun loadImage(view: ImageView, url: String) {\n    Log.e(MainActivity9.TAG, \"loadImage url : $url\")\n}\n```\n\n在 xml 文件中关联变量值，当中，bind 这个名称可以自定义\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n\u003clayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:bind=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\u003e\n\n    \u003cdata\u003e\n        \u003cimport type=\"github.leavesc.databinding.ImageBean\" /\u003e\n        \u003cvariable\n            name=\"image\"\n            type=\"ImageBean\" /\u003e\n    \u003c/data\u003e\n\n    \u003cLinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\u003e\n\n        \u003cImageView\n            android:id=\"@+id/image\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:src=\"@drawable/ic_launcher_background\"\n            bind:url=\"@{image.url}\" /\u003e\n        \n    \u003c/LinearLayout\u003e\n\u003c/layout\u003e\n```\n\nBindingAdapter 更为强大的一点是可以覆盖 Android 原先的控件属性。例如，可以设定每一个 Button 的文本都要加上后缀：“-Button”\n\n```kotlin\n@BindingAdapter(\"android:text\")\nfun setText(view: Button, text: String) {\n    view.text = \"$text-Button\"\n}\n```\n\n```xml\n        \u003cButton\n            android:id=\"@+id/button\"\n            style=\"@style/BtnStyle\"\n            android:onClick=\"@{()-\u003ehandler.onClick(image)}\"\n            android:text='@{\"改变图片Url\"}' /\u003e\n```\n\n这样，整个工程中使用到了 **\"android:text\"** 这个属性的控件，其显示的文本就会多出一个后缀\n\n![](https://images.xiaozhuanlan.com/photo/2020/e3219804086630aabdf40ccccdd6cb80.png)\n\n### 九、BindingConversion\n\ndataBinding 还支持对数据进行转换，或者进行类型转换\n\n与 BindingAdapter 类似，以下方法会将布局文件中所有以`@{String}`方式引用到的`String`类型变量加上后缀`-conversionString`\n\n```java\n@BindingConversion\nfun conversionString(text: String): String? {\n    return \"$text-conversionString\"\n}\n```\n\nxml 文件\n\n```xml\n        \u003cTextView\n            android:id=\"@+id/textView\"\n            style=\"@style/BtnStyle\"\n            android:text='@{\"xxx\"}' /\u003e\n```\n\n![](https://images.xiaozhuanlan.com/photo/2020/b883c82e871b5d2b094ed607ef4c16ea.gif)\n\n可以看到，对于 Button 来说，BindingAdapter 和 BindingConversion 同时生效了，而 BindingConversion 的优先级要高些\n\n此外，BindingConversion 也可以用于转换属性值的类型\n\n看以下布局，此处在向 `background` 和 `textColor` 两个属性赋值时，直接就使用了字符串，按正常情况来说这自然是会报错的，但有了 BindingConversion 后就可以自动将字符串类型的值转为需要的 `Drawable` 和 `Color` 了\n\n```xml\n        \u003cTextView\n            android:id=\"@+id/textView1\"\n            style=\"@style/BtnStyle\"\n            android:layout_marginTop=\"30dp\"\n            android:background='@{\"红色\"}'\n            android:padding=\"20dp\"\n            android:text=\"红色背景蓝色字\"\n            android:textColor='@{\"蓝色\"}' /\u003e\n\n        \u003cTextView\n            style=\"@style/BtnStyle\"\n            android:background='@{\"蓝色\"}'\n            android:padding=\"20dp\"\n            android:text=\"蓝色背景红色字\"\n            android:textColor='@{\"红色\",default=@color/colorAccent}' /\u003e\n```\n\n```kotlin\n@BindingConversion\nfun convertStringToDrawable(str: String): Drawable {\n    return when (str) {\n        \"红色\" -\u003e {\n            ColorDrawable(Color.parseColor(\"#FF4081\"))\n        }\n        \"蓝色\" -\u003e {\n            ColorDrawable(Color.parseColor(\"#3F51B5\"))\n        }\n        else -\u003e {\n            ColorDrawable(Color.parseColor(\"#344567\"))\n        }\n    }\n}\n\n@BindingConversion\nfun convertStringToColor(str: String): Int {\n    return when (str) {\n        \"红色\" -\u003e {\n            Color.parseColor(\"#FF4081\")\n        }\n        \"蓝色\" -\u003e {\n            Color.parseColor(\"#3F51B5\")\n        }\n        else -\u003e {\n            Color.parseColor(\"#344567\")\n        }\n    }\n}\n```\n![](https://images.xiaozhuanlan.com/photo/2020/bdaae84de9ff7ae98673706b5e1c7508.png)\n\n### 十、Array、List、Set、Map ...\n\ndataBinding 也支持在布局文件中使用 **数组、Lsit、Set 和 Map**，且在布局文件中都可以通过 `list[index]` 的形式来获取元素\n\n而为了和 **variable** 标签的尖括号区分开，在声明 **Lsit\u003cString\u003e** 之类的数据类型时，需要使用尖括号的转义字符\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n\u003clayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\u003e\n\n\n    \u003cdata\u003e\n\n        \u003cimport type=\"java.util.List\" /\u003e\n\n        \u003cimport type=\"java.util.Map\" /\u003e\n\n        \u003cimport type=\"java.util.Set\" /\u003e\n\n        \u003cimport type=\"android.util.SparseArray\" /\u003e\n\n        \u003cvariable\n            name=\"array\"\n            type=\"String[]\" /\u003e\n\n        \u003cvariable\n            name=\"list\"\n            type=\"List\u0026lt;String\u0026gt;\" /\u003e\n\n        \u003cvariable\n            name=\"map\"\n            type=\"Map\u0026lt;String, String\u0026gt;\" /\u003e\n\n        \u003cvariable\n            name=\"set\"\n            type=\"Set\u0026lt;String\u0026gt;\" /\u003e\n\n        \u003cvariable\n            name=\"sparse\"\n            type=\"SparseArray\u0026lt;String\u0026gt;\" /\u003e\n\n        \u003cvariable\n            name=\"index\"\n            type=\"int\" /\u003e\n\n        \u003cvariable\n            name=\"key\"\n            type=\"String\" /\u003e\n    \u003c/data\u003e\n\n    \u003cLinearLayout xmlns:tools=\"http://schemas.android.com/tools\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\"\n        tools:context=\".MainActivity8\"\u003e\n\n        \u003cTextView\n            style=\"@style/TextViewStyle\"\n            android:text=\"@{array[1]}\" /\u003e\n\n        \u003cTextView\n            style=\"@style/TextViewStyle\"\n            android:text=\"@{sparse[index]}\" /\u003e\n\n        \u003cTextView\n            style=\"@style/TextViewStyle\"\n            android:text=\"@{list[index]}\" /\u003e\n\n        \u003cTextView\n            style=\"@style/TextViewStyle\"\n            android:text=\"@{map[key]}\" /\u003e\n\n        \u003cTextView\n            style=\"@style/TextViewStyle\"\n            android:text='@{map[\"leavesC\"]}' /\u003e\n\n        \u003cTextView\n            style=\"@style/TextViewStyle\"\n            android:text='@{set.contains(\"xxx\")?\"xxx\":key}' /\u003e\n    \u003c/LinearLayout\u003e\n\u003c/layout\u003e\n```\n\n### 十一、资源引用\n\ndataBinding 支持对尺寸和字符串这类资源的访问\n\n`dimens.xml`\n\n```xml\n    \u003cdimen name=\"paddingBig\"\u003e190dp\u003c/dimen\u003e\n    \u003cdimen name=\"paddingSmall\"\u003e150dp\u003c/dimen\u003e\n```\n\n`strings.xml`\n\n```xml\n    \u003cstring name=\"format\"\u003e%s is %s\u003c/string\u003e\n```\n\n```xml\n    \u003cdata\u003e\n        \u003cvariable\n            name=\"flag\"\n            type=\"boolean\" /\u003e\n    \u003c/data\u003e       \n\t\u003cButton\n         android:layout_width=\"match_parent\"\n         android:layout_height=\"wrap_content\"\n         android:paddingLeft=\"@{flag ? @dimen/paddingBig:@dimen/paddingSmall}\"\n         android:text='@{@string/format(\"leavesC\", \"Ye\")}'\n         android:textAllCaps=\"false\" /\u003e\n```\n\n### 十二、与 RecyclerView 搭配使用\n\ndataBinding 与 RecyclerView  搭配使用的话可以让代码更加简洁明了\n\n先声明需要的 item 布局文件\n\n```java\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n\u003clayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\u003e\n\n    \u003cdata\u003e\n\n        \u003cimport type=\"github.leavesc.databinding.UserBean\" /\u003e\n\n        \u003cvariable\n            name=\"user\"\n            type=\"UserBean\" /\u003e\n\n    \u003c/data\u003e\n\n    \u003cLinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:paddingLeft=\"10dp\"\u003e\n\n        \u003cTextView\n            android:id=\"@+id/tvName\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center\"\n            android:padding=\"8dp\"\n            android:text=\"@{user.name}\" /\u003e\n\n        \u003cTextView\n            android:id=\"@+id/tvPassword\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center\"\n            android:padding=\"8dp\"\n            android:text=\"@{user.password}\" /\u003e\n\n        \u003cView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"1dp\"\n            android:background=\"#c6cdd4\" /\u003e\n\n    \u003c/LinearLayout\u003e\n\n\u003c/layout\u003e\n```\n\n对应的 RecyclerView.Adapter \n\n```java\n/**\n * 作者：leavesC\n * 时间：2020/6/29 23:03\n * 描述：\n * GitHub：https://github.com/leavesC\n */\nclass UserAdapter(private val userList: List\u003cUserBean\u003e) :\n    RecyclerView.Adapter\u003cUserAdapter.UserAdapterHolder\u003e() {\n\n    override fun onCreateViewHolder(\n        parent: ViewGroup,\n        viewType: Int\n    ): UserAdapterHolder {\n        val binding: ItemUserBinding = DataBindingUtil.inflate(\n            LayoutInflater.from(parent.context),\n            R.layout.item_user,\n            parent,\n            false\n        )\n        return UserAdapterHolder(binding)\n    }\n\n    override fun onBindViewHolder(holder: UserAdapterHolder, position: Int) {\n        holder.getBinding().user = userList[position]\n    }\n\n    override fun getItemCount(): Int {\n        return userList.size\n    }\n\n    inner class UserAdapterHolder(private val binding: ItemUserBinding) : ViewHolder(binding.root) {\n\n        fun getBinding(): ItemUserBinding {\n            return binding\n        }\n\n    }\n\n}\n```\n\n### 十三、RecyclerView Adapter 高效率刷新\n\n前文讲到了 **ObservableList** ，此处就可以通过 **ObservableList** 的实现类 **ObservableArrayList** 来实现 **RecyclerView Adapter 的高效刷新**，而不是每次都是直接 **notifyDataSetChanged**\n\n可以先看下 ObservableArrayList 的源码，可以发现在每次**增删改数据**时，都会触发到 **ListChangeRegistry** 内的 **OnListChangedCallback** 回调，且 **OnListChangedCallback** 是把每次改动到的数据位置都给透传到外部，我们可以通过这些信息来只刷新 Adapter 的特定位置，从而实现高效刷新，并且获得一些动画效果\n\n```java\npublic class ObservableArrayList\u003cT\u003e extends ArrayList\u003cT\u003e implements ObservableList\u003cT\u003e {\n   \n    private transient ListChangeRegistry mListeners = new ListChangeRegistry();\n\n   \t···\n\n    @Override\n    public boolean add(T object) {\n        super.add(object);\n        notifyAdd(size() - 1, 1);\n        return true;\n    }\n    \n    @Override\n    public void clear() {\n        int oldSize = size();\n        super.clear();\n        if (oldSize != 0) {\n            notifyRemove(0, oldSize);\n        }\n    }\n\n    @Override\n    public T remove(int index) {\n        T val = super.remove(index);\n        notifyRemove(index, 1);\n        return val;\n    }\n\n    @Override\n    public T set(int index, T object) {\n        T val = super.set(index, object);\n        if (mListeners != null) {\n            mListeners.notifyChanged(this, index, 1);\n        }\n        return val;\n    }\n\n    private void notifyAdd(int start, int count) {\n        if (mListeners != null) {\n            mListeners.notifyInserted(this, start, count);\n        }\n    }\n\n    private void notifyRemove(int start, int count) {\n        if (mListeners != null) {\n            mListeners.notifyRemoved(this, start, count);\n        }\n    }\n    \n    ···\n    \n}\n\n```\n\n此处通过 **DynamicChangeCallback** 来实现对 **Adapter** 的刷新操作\n\n```kotlin\n/**\n * 作者：leavesC\n * 时间：2020/6/29 23:07\n * 描述：\n * GitHub：https://github.com/leavesC\n */\nclass DynamicChangeCallback\u003cT\u003e(private val adapter: RecyclerView.Adapter\u003c*\u003e) :\n    OnListChangedCallback\u003cObservableList\u003cT\u003e\u003e() {\n\n    override fun onChanged(sender: ObservableList\u003cT\u003e) {\n        adapter.notifyDataSetChanged()\n    }\n\n    override fun onItemRangeChanged(\n        sender: ObservableList\u003cT\u003e,\n        positionStart: Int,\n        itemCount: Int\n    ) {\n        adapter.notifyItemRangeChanged(positionStart, itemCount)\n    }\n\n    override fun onItemRangeInserted(\n        sender: ObservableList\u003cT\u003e,\n        positionStart: Int,\n        itemCount: Int\n    ) {\n        adapter.notifyItemRangeInserted(positionStart, itemCount)\n    }\n\n    override fun onItemRangeMoved(\n        sender: ObservableList\u003cT\u003e,\n        fromPosition: Int,\n        toPosition: Int,\n        itemCount: Int\n    ) {\n        adapter.notifyItemRangeRemoved(fromPosition, itemCount)\n        adapter.notifyItemRangeInserted(toPosition, itemCount)\n    }\n\n    override fun onItemRangeRemoved(\n        sender: ObservableList\u003cT\u003e,\n        positionStart: Int,\n        itemCount: Int\n    ) {\n        adapter.notifyItemRangeRemoved(positionStart, itemCount)\n    }\n\n}\n```\n\n通过几个按钮来分别测试 Adapter 的数据刷新情况\n\n```java\nclass MainActivity13 : AppCompatActivity() {\n\n    private val userObservableArrayList = ObservableArrayList\u003cUserBean\u003e().apply {\n        for (i in 0..19) {\n            val user = UserBean(\"user_$i\", (Random().nextInt() * 4).toString())\n            add(user)\n        }\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_main13)\n        val rvList = findViewById\u003cRecyclerView\u003e(R.id.rvList)\n        rvList.layoutManager = LinearLayoutManager(this)\n        val userAdapter = UserAdapter(userObservableArrayList)\n        userAdapter.notifyDataSetChanged()\n        userObservableArrayList.addOnListChangedCallback(DynamicChangeCallback\u003cUserBean\u003e(userAdapter))\n        rvList.adapter = userAdapter\n    }\n\n    fun addItem(view: View) {\n        if (userObservableArrayList.size \u003e= 3) {\n            val user = UserBean(\"user_\" + 100, (Random().nextInt() * 4).toString())\n            userObservableArrayList.add(1, user)\n        }\n    }\n\n    fun addItemList(view: View) {\n        if (userObservableArrayList.size \u003e= 3) {\n            val userList: MutableList\u003cUserBean\u003e = ArrayList\u003cUserBean\u003e()\n            for (i in 0..2) {\n                val user = UserBean(\"user_\" + 100, (Random().nextInt() * 4).toString())\n                userList.add(user)\n            }\n            userObservableArrayList.addAll(1, userList)\n        }\n    }\n\n    fun removeItem(view: View) {\n        if (userObservableArrayList.size \u003e= 3) {\n            userObservableArrayList.removeAt(1)\n        }\n    }\n\n    fun updateItem(view: View) {\n        if (userObservableArrayList.size \u003e= 3) {\n            val user: UserBean = userObservableArrayList[1]\n            user.name = \"user_\" + Random().nextInt()\n            userObservableArrayList[1] = user\n        }\n    }\n\n}\n```\n\n![](https://images.xiaozhuanlan.com/photo/2020/4a5f4e3d8566a996636db837d2f7e2ee.gif)\n\nDemo 下载：[DataBindingSamples](https://github.com/leavesC/DataBindingSamples)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FleavesCZY%2FDataBindingSamples","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FleavesCZY%2FDataBindingSamples","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FleavesCZY%2FDataBindingSamples/lists"}