DataBinding的高级用法的演示:
1、双向绑定
2、自定义属性(todo 注意databinding支持view的属性,需要有对应setXXX,getXXX的格式的函数(或者boolean的isXXX),才能被框架感知。
如果一个View的属性,不是规范的setXXX,getXXX的设置/获取函数,那么就不行。可以继承该函数,设置setXXX/getXXX,在xml中自定义的View,就可以支持。)
3、转换器converters
4、自定义控件支持dataBinding,@BindingMethods、@InverseBindingMethods等
DataBinding的高级用法的问题:
1、@{user.name}如果user为null,是否运行崩溃
2、DataBinding是否支持所有View的属性
3、双向绑定时候,是否会数据陷入死循环。
问题解答:
1、如果xml中的variable没有binding赋值,如myBg那一行如果注释掉。运行时就会崩溃。而如果只是name为空,就会显示null,而不会崩溃。
2、上面已经知道,不是所有的View的属性都可以直接dataBinding的,需要满足标准setXXX/getXXX的函数方式,或者按照[MyImageView]中注释写的那三种方法,扩展view的属性binding支持
3、双向绑定也不会死循环,因为实现类会做old==new的value值校验,并return,避免陷入双向刷新的死循环。
1. 项目 build.gradle 添加引用库
- //SwipeRefreshLayout
- implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
- //glide
- implementation 'com.github.bumptech.glide:glide:4.12.0'
- kapt 'com.github.bumptech.glide:compiler:4.12.0'//kotlin代码需要这个kapt注入进程
2. 布局文件 activity_advanced_use.xml
- <?xml version="1.0" encoding="utf-8"?>
- <layout xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools">
-
- <data>
-
- <variable
- name="user"
- type="org.hanyang.jetpack.binding.bean.ObUser" />
- <!--用于加载图片的url-->
- <variable
- name="url"
- type="String" />
-
- <variable
- name="myBg"
- type="android.graphics.drawable.Drawable" />
-
- <!--用于演示双向绑定,刷新view的swipeRefreshLayout控件需要用到-->
- <variable
- name="activity"
- type="org.hanyang.jetpack.binding.activity.AdvancedUseActivity" />
- </data>
-
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center_horizontal"
- android:orientation="vertical">
-
- <!--1、双向绑定,使用的是@={} 下面输入框的修改,就会同步修改user的name字段-->
- <androidx.appcompat.widget.AppCompatTextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@{`Name ` + user.name }"
- tools:text="在输入框修改user的name,这里会变" />
-
- <androidx.appcompat.widget.AppCompatEditText
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:hint="这里演示的输入名字,更改为user的name"
- android:singleLine="true"
- android:text="@={user.name}" />
-
- <!--2、转换,也就是将原有不支持的属性,转换为支持,兼容,如 background
- 的属性不能设置普通 String,这里可以通过@BindingConversion的静态函数,来适配-->
- <androidx.appcompat.widget.AppCompatTextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:background="@{`red`}"
- android:text="这个文案的背景色属性,就是通过converters转换才行的" />
-
- <!--3、添加或扩展已有控件的属性,或者给它的set支持dataBinding
- 因为dataBinding只是控件属性set/get,需要标准的函数命名getXXX,setXXX才会识别该属性。
- 如果不是这样命名的函数属性set,就不会支持dataBinding。这里我们可以扩展兼容-->
-
- <!--这里的src属性,并不支持网络加载url,或者uri,我们可以在静态函数中,适配兼容-->
- <androidx.appcompat.widget.AppCompatImageView
- imgSrc="@{url}"
- android:layout_width="wrap_content"
- android:layout_height="80dp"
- android:layout_marginTop="10dp"
- android:src="@drawable/img_banner1" />
- <!--这里是用的自定义的View,使用img属性,在view中添加set函数,这样和上面的@BindingAdapter对比,上面更方便,扩展性强-->
- <org.hanyang.jetpack.binding.view.MyImageView
- img="@{myBg}"
- android:layout_width="wrap_content"
- android:layout_height="80dp"
- android:layout_marginTop="10dp" />
- <!--这里的 image 属性,是原控件没有的,而是通过@BindingMethods来实现函数映射-->
- <androidx.appcompat.widget.AppCompatImageView
- image="@{myBg}"
- android:layout_width="wrap_content"
- android:layout_height="80dp"
- android:layout_marginTop="10dp" />
-
- <!--4.自定义View 双向绑定演示 记住双向绑定使用@={}-->
- <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginTop="10dp"
- app:sfl_refreshing="@={activity.refreshing}">
-
- <androidx.appcompat.widget.AppCompatTextView
- android:id="@+id/tv_long_text_ad_binding"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:text="long text 长文本" />
- </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
- </LinearLayout>
- </layout>
3. 使用工具文件 BCTool.kt
- /**
- * Binding高级用法中,辅助工具类,演示@BindingConversion,@bindadapter等
- */
- object BCTool {
-
- /**
- * 兼容适配view的background的str转color属性,这里函数名 可以随意,而且不需要其他地方显式的调用。
- * 只需要在此 ,静态函数的声明即可。(java的写法就是public static,这里不写java版的了。)
- */
- @JvmStatic
- @BindingConversion
- fun conversionStr2Color(str: String): Drawable {
- return when (str) {
- "red" -> {
- ColorDrawable(Color.RED)
- }
- "blue" -> ColorDrawable(Color.BLUE)
- else -> {
- ColorDrawable(Color.YELLOW)
- }
- }
- }
-
- /**
- * 用于appCompatImageView的自定义属性,bind:imgSrc,命名空间bind:可以省略,也就是写作 imgSrc亦可。可以用于加载url的图片
- * 函数名也是随意,主要是value的声明,就是新加的属性名了,可以多个属性同用,并配置是否必须一起作用
- * 函数名随意,方法签名才重要,匹配对象控件,以及属性参数。这里还可以添加old 参数,获取修改新参数 之前对应的值。
- * todo 加载网络图片,需要网络权限,别忘了
- */
- @JvmStatic
- @BindingAdapter(value = ["imgSrc"], requireAll = false)
- fun urlImageSrc(view: AppCompatImageView, /*old: String?, */url: String?) {
- Glide.with(view)
- .load(url)
- .placeholder(R.drawable.img_banner1)
- .centerInside()
- .into(view)
- }
-
- /**
- * 这个是 databinding高级用法中,配合演示swipeRefreshLayout的刷新状态的感知
- * 第一步:单向的,数据变化,刷新UI
- */
- @JvmStatic
- @BindingAdapter("sfl_refreshing", requireAll = false)
- fun setSwipeRefreshing(view: SwipeRefreshLayout, oldValue: Boolean, newValue: Boolean) {
- //判断是否是新的值,避免陷入死循环
- if (oldValue != newValue)
- view.isRefreshing = newValue
- }
-
- /**
- * 第二步:ui的状态,反向绑定给数据变化
- */
- @JvmStatic
- @BindingAdapter("sfl_refreshingAttrChanged", requireAll = false)
- fun setRefreshCallback(view: SwipeRefreshLayout, listener: InverseBindingListener?) {
- listener ?: return
- view.setOnRefreshListener {
- //由ui层的刷新状态变化,反向通知数据层的变化
- listener.onChange()
- }
- }
-
- /**
- * 反向绑定的实现,将UI的变化,回调给bindingListener,listener就会onChange,通知数据变化
- * 注意这里的attr和event,是跟上面两步配合一致才有效
- */
- @JvmStatic
- @InverseBindingAdapter(attribute = "sfl_refreshing", event = "sfl_refreshingAttrChanged")
- fun isSwipeRefreshing(view: SwipeRefreshLayout): Boolean {
- return view.isRefreshing
- }
- }
4. 测试页面 AdvancedUseActivity.kt
- /**
- * DataBinding的高级用法的演示
- * 1、双向绑定
- * 2、自定义属性(todo 注意databinding支持view的属性,需要有对应setXXX,getXXX的格式的函数(或者boolean的isXXX),才能被框架感知。
- * 如果一个View的属性,不是规范的setXXX,getXXX的设置/获取函数,那么就不行。可以继承该函数,设置setXXX/getXXX,在xml中自定义的View,就可以支持。)
- * 3、转换器converters
- * 4、自定义控件支持dataBinding,@BindingMethods、@InverseBindingMethods等
- */
- class AdvancedUseActivity : AppCompatActivity() {
- val TAG = AdvancedUseActivity::class.java.simpleName
-
- //标记是否刷新liveData中的对象。不能 private,因为要用在xml中
- val refreshing = MutableLiveData<Boolean>()
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- //Glide 初始化
- //Glide.init(this, GlideBuilder())
- val binding = DataBindingUtil.setContentView<ActivityAdvancedUseBinding>(
- this,
- R.layout.activity_advanced_use
- )
-
- //user 设置
- val user = ObUser("张三", 30, 1, "没有修改名字前的数据")
- binding.user = user
-
- //url
- val url = "https://t7.baidu.com/it/u=2931491413,1199396761&fm=193&f=GIF"
- binding.url = url
-
- //设置myView的 img 参数
- binding.myBg = getDrawable(R.drawable.img_banner1)
-
- //演示自定义View实现双向绑定
- // 1、先单向绑定 也就是将viewModel的数据变化,通知UI来刷新;(这里也就是在[BCTool]中static声明自定义属性,refreshing)
- // 2、将UI的变化,反向绑定,来通知数据模型的状态变化。
- // 3、完成双向绑定,避免死循环。
- //这里是个长文本,配合演示swipeRefreshLayout的状态感知
- binding.tvLongTextAdBinding.text = strText
- //
- binding.activity = this
- //这里记录log, liveData感知,也就证明,UI的刷新,将状态反响绑定给了data
- refreshing.observe(this, Observer<Boolean> {
- Log.i(TAG, "refreshing $it")
- })
- }
-
- }
-
- const val strText = """
- James Ray edited this page on 2 Apr · 241 revisions
- Welcome to the Ethereum Wiki!
- Documentation chat standard-readme compliant
- Ethereum wiki covering all things related to Ethereum
- Contents
- Issues and pull requests
- Contribution guidelines
- Introduction
- Fixing vandalism
- Page titles
- Wikipedia pillars
- Translating
- License and contributor license agreement
- Editing locally (requires access permission)
- Setup
- Usage
- Getting started
- """
5. 效果图
