当前位置:网站首页>当你真的学会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
尾述
关于我
边栏推荐
- Fiori applications are shared through the enhancement of adaptation project
- Chen Yu (Aqua) - Safety - & gt; Cloud Security - & gt; Multicloud security
- String input function
- nexus搭建npm依赖私库
- In the next stage of digital transformation, digital twin manufacturer Youyi technology announced that it had completed a financing of more than 300 million yuan
- La taille de la pile spécifiée est petite, spécifiée à la sortie 328k
- 20个实用的 TypeScript 单行代码汇总
- China NdYAG crystal market research conclusion and development strategy proposal report Ⓥ 2022 ~ 2028
- Jenkins+webhooks- multi branch parametric construction-
- Analysis report on the development pattern of China's smart emergency industry and the 14th five year plan Ⓠ 2022 ~ 2028
猜你喜欢

Build a vc2010 development environment and create a tutorial of "realizing Tetris game in C language"

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

Meta再放大招!VR新模型登CVPR Oral:像人一样「读」懂语音

Cs5268 advantages replace ag9321mcq typec multi in one docking station scheme

Svg diamond style code

The future of game guild in decentralized games

【大型电商项目开发】性能压测-压力测试基本概念&JMeter-38

Google Earth Engine(GEE)——全球人类居住区网格数据 1975-1990-2000-2014 (P2016)

香港科技大学李泽湘教授:我错了,为什么工程意识比上最好的大学都重要?

IO的几种模型 阻塞,非阻塞,io多路复用,信号驱动和异步io
随机推荐
JS变色的乐高积木
Machine learning - performance metrics
Analysis report on production and marketing demand and investment forecast of global and Chinese diamond powder industry Ⓤ 2022 ~ 2027
3.4 《数据库系统概论》之数据查询—SELECT(单表查询、连接查询、嵌套查询、集合查询、多表查询)
运行游戏时出现0xc000007b错误的解决方法[通俗易懂]
French Data Protection Agency: using Google Analytics or violating gdpr
Simple two ball loading
Global and Chinese n-butanol acetic acid market development trend and prospect forecast report Ⓧ 2022 ~ 2028
Application of 5g industrial gateway in scientific and technological overload control; off-site joint law enforcement for over limit, overweight and overspeed
La taille de la pile spécifiée est petite, spécifiée à la sortie 328k
Research Report on China's software outsourcing industry investment strategy and the 14th five year plan Ⓡ 2022 ~ 2028
1.8新特性-List
Investment analysis and prospect prediction report of global and Chinese p-nitrotoluene industry Ⓙ 2022 ~ 2027
Flow management technology
Computer network interview knowledge points
Build a vc2010 development environment and create a tutorial of "realizing Tetris game in C language"
Apache-atlas-2.2.0 independent compilation and deployment
MySQL gap lock
Asp.netcore利用dynamic简化数据库访问
[development of large e-commerce projects] performance pressure test - basic concept of pressure test & jmeter-38