当前位置:网站首页>当你真的学会DataBinding后,你会发现“这玩意真香”!
当你真的学会DataBinding后,你会发现“这玩意真香”!
2022-07-01 13:19:00 【InfoQ】
前言
如何使用DataBinding呢?
//在gradle的android下加入,然后点击sync
android {
...
//android studio 4.0以下
dataBinding{
}
//android studio4.1以后
buildFeatures {
dataBinding true
}
}
Convert to data binding layoutDataBinding
<?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>
layoutdatadata<data>
<import type="com.example...."/>
<variable
name="color"
type="java.lang.String" />
</data>
- data:在标签内进行变量声明和导入等
- variable:进行变量声明
- import:导入需要的类
class User() {
var name = "Taxze"
var age = 18
fun testOnclick() {
Log.d("onclick", "test")
}
}
<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}"
/>
DataBindingUtilActivity的setContentViewclass 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
}
}
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
}
}
Bindingactivity_mainActivityMainBinding
layoutdatabindingbindingbuild/generated/data_binding_base_source_out
如何在xml布局中更好的使用DataBinding
- 加入我们传入了一个集合
books,我们可以通过以下方式使用:
- 获取集合的值
- 添加默认值(默认值无需加引号,且只在预览视图显示)
- ```xml
android:text="@{books.pages,default=330}"
- 通过??或?:来实现
- ```xml
android:text="@{books.pages != null ? book.pages : book.defaultPages}"
- ```xml
android:text="@{books.pages ?? book.defaultPages}"
map类型的结构也可以通过get和[]两种方式获取
**3.转换数据类型**
- 因为`DataBinding`不会自动做类型转换,所有我们需要手动转换,例如在text标签内使用String.valueOf()转换为String类型,在rating标签内我们可以使用Float.valueOf()进行转换
- ```xml
android:text="@{String.valueOf(book.pages)}"
- ```xml
android:rating="@{Float.valueOf(books.rating),default=2.0}"
**4.导入包名冲突处理**
- 如果我们导入的包名有冲突,我们可以通过`alias`为它设置一个别名
- ```xml
//伪代码,请勿直接CV
<data>
<import type="com.xxx.a.Book" alias="aBook"/>
<import type="com.xxx.B.Book" alias="bBook"/>
...
</data>
- 在一个
view上引用其他view的属性
- **`include`**标签和**`ViewStub`**标签
- `include`和`merge`标签的作用是实现布局文件的重用。就是说,为了高效复用及整合布局,使布局轻便化,我们可以使用`include`和`merge`标签将一个布局嵌入到另一个布局中,或者说将多个布局中的相同元素抽取出来,独立管理,再复用到各个布局中,便于统一的调整。 比如,一个应用中的多个页面都要用到统一样式的标题栏或底部导航栏,这时就可以将标题栏或底部导航栏的布局抽取出来,再以`include`标签形式嵌入到需要的布局中,而不是多次copy代码,这样在修改时只需修改一处即可。而我们同样可以通过`DataBinding`来进行数据绑定。
- 例如:
- ```xml
//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.绑定点击事件**
- ```xml
//伪代码,请勿直接CV
onclick="@{()->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}"
/>
实现数据变化时自动更新UI
BaseObservableObservableFieldObservableCollection- BaseObservable
- BaseObservable提供了两个刷新UI的方法,分别是 notifyPropertyChanged() 和 notifyChange() 。
- 第一步:修改实体类
- 将我们的实体类继承与BaseObservable。需要响应变化的字段,就在对应变量的get函数上加 @Bindable 。然后set中notifyChange是kotlin的写法,免去了java的getter setter的方式。成员属性需要响应变化的,就在其set函数中,notify一下属性变化,那么set的时候,databinding就会感知到。
import androidx.databinding.BaseObservable
import androidx.databinding.Bindable
import androidx.databinding.library.baseAdapters.BR
class 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
- 实体类修改:
//伪代码,请勿直接cv
class 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中的使用

- 第一步:创建实体类
- 就是我们之前的,使用了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.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.RecyclerView
import com.taxze.jetpack.databinding.databinding.TextItemBinding
class 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.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
class 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
}
}
高级用法
//伪代码,请勿直接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)
}
- 第一步:单向的,数据变化,刷新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 使用MutableLiveData
class 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([email protected] 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"
}
}
帮你踩坑:
TextView的text属性,需要注意data不能为Number类型
xml中字符不能为中文(检查一下你的输入法)
- 反射属性、函数必须是
public
observableField数据的时候,在某些场合需要初始化,否则会运行报错!
- 使用
LiveData作为dataBinding的时候,需要在ui中设置binding.lifecycleOwner
尾述
关于我
边栏推荐
- 9. Use of better scroll and ref
- leetcode 322. Coin Change 零钱兑换(中等)
- 图灵奖得主Judea Pearl:最近值得一读的19篇因果推断论文
- Feign & Eureka & zuul & hystrix process
- 股票开户要认识谁?实际上网上开户安全么?
- nexus搭建npm依赖私库
- Apache-atlas-2.2.0 independent compilation and deployment
- [development of large e-commerce projects] performance pressure test - basic concept of pressure test & jmeter-38
- Use of shutter SQLite
- Nexus builds NPM dependent private database
猜你喜欢

软件测试中功能测试流程

Judea pearl, Turing prize winner: 19 causal inference papers worth reading recently

minimum spanning tree

Hardware development notes (9): basic process of hardware development, making a USB to RS232 module (8): create asm1117-3.3v package library and associate principle graphic devices

啟動solr報錯The stack size specified is too small,Specify at least 328k

MySQL statistical bill information (Part 2): data import and query

洞态在某互联⽹⾦融科技企业的最佳落地实践
![[Niu Ke's questions -sql big factory interview real questions] no2 User growth scenario (a certain degree of information flow)](/img/a0/e9e7506c9c34986dc73562539c8410.png)
[Niu Ke's questions -sql big factory interview real questions] no2 User growth scenario (a certain degree of information flow)

焱融看 | 混合云时代下,如何制定多云策略

1553B environment construction
随机推荐
04 redis source code data structure dictionary
研发效能度量框架解读
机器学习总结(一):线性回归、岭回归、Lasso回归
The 14th five year plan of China's environmental protection industry and the report on the long-term goals for 2035 Ⓖ 2022 ~ 2028
9. Use of better scroll and ref
Global and Chinese silicone defoamer production and marketing demand and investment forecast analysis report Ⓨ 2022 ~ 2027
MySQL 66 questions, 20000 words + 50 pictures in detail! Necessary for review
Professor Li Zexiang, Hong Kong University of science and technology: I'm wrong. Why is engineering consciousness more important than the best university?
面试题目总结(1) https中间人攻击,ConcurrentHashMap的原理 ,serialVersionUID常量,redis单线程,
VM virtual machine configuration dynamic IP and static IP access
Introduction to reverse debugging PE structure input table output table 05/07
2.15 summary
Leetcode第一题:两数之和(3种语言)
洞态在某互联⽹⾦融科技企业的最佳落地实践
leetcode 322. Coin Change 零钱兑换(中等)
彩色五角星SVG动态网页背景js特效
Spark source code (V) how does dagscheduler taskscheduler cooperate with submitting tasks, and what is the corresponding relationship between application, job, stage, taskset, and task?
单工,半双工,全双工区别以及TDD和FDD区别
Simple two ball loading
Analysis report on the development prospect and investment strategy of the global and Chinese laser chip industry Ⓑ 2022 ~ 2027