先上效果:
4个view的文字都是通过DataBinding填充的。交互事件:点击图片,切换图片
创建项目(android Studio 2023.3.1)
Build.gradle(:app) 引入依赖库(完整源码)
buildFeatures {
viewBinding true
compose true
}
dataBinding {
enabled = true
}
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
id 'kotlin-kapt'
}
android {
namespace 'com.example.lanidemokt'
compileSdk 31
defaultConfig {
applicationId "com.example.lanidemokt"
minSdk 24
targetSdk 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
lintOptions {
abortOnError false
}
buildFeatures {
viewBinding true
compose true
}
dataBinding {
enabled = true
}
}
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
implementation "androidx.compose.ui:ui:1.0.1"
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'androidx.appcompat:appcompat-resources:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.compose.material:material:1.0.1'
implementation 'com.github.bumptech.glide:compiler:4.11.0'
implementation 'com.github.bumptech.glide:glide:4.11.0'
}
1. 基本使用意向绑定数据显示在界面
MainActivity.kt (完整源码)
在MainActivity.kt里,Databinding和我们的XML文件绑定起来了,现在你点击Databinding会发现直接可以跳转到对应的XML文件里面去了,
package com.example.lanidemokt
import android.os.Bundle
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.databinding.BaseObservable
import androidx.databinding.DataBindingUtil
import com.catchpig.utils.LogUtils
import com.example.lanidemokt.adapter.MainActivityBindingAdapter
import com.example.lanidemokt.databinding.ActivityMainBinding
import com.example.lanidemokt.viewmodel.ButtonClickListener
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.Date
class MainActivity : AppCompatActivity() {
/*
* DataBinding 对应一个Binding对象,对象名是布局文件文称加上Binding后缀
* binding,activity_main.xml的布局实例
* xml上所有变量与点击事件,必须是binding的成员属性或者成员方法函数,否则操作界面无效
*
* */
var binding: ActivityMainBinding? = null // 操作布局实例
private var login: Login? = null //声明一个响应式对象,用于ui
var clickListener: ButtonClickListener? = null // 布局点击对象封装
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) // setContentView(R.layout.activity_main)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
initView()
initData()
corGlobalTest()
}
private fun initData() {
LogUtils.init(this)
}
/* *
* 在Controller层,将我们的data与model相关联
* */
data class Login(var name: String = "LLL", var msg: String) //意向绑定响应式
data class Student(var name: String = "LLL", var score: Int) : BaseObservable() //双击绑定响应式
private fun initView() {
binding?.msg?.setText("我是谁")
login = Login("LANI", "我是谁")
// binding?.login = Login("LANI", "我是谁")
// 这一步必须要,否则点击没反应,否则界面不显示对应的名字与信息
binding?.setLogin(Login("LANI", "我是谁"))
binding?.setStudent(Student("LEE", 199))
binding?.picture1?.setOnClickListener {
println("图片点击")
MainActivityBindingAdapter.loadStudentDetails(
it as ImageView,
"http://192.168.1.207:8080/download/88.jpg"
)
}
clickListener=ButtonClickListener()
binding?.btnHandler = clickListener
}
/*
* 协程创建
* */
fun corGlobalTest() {
GlobalScope.launch {
println("|--开始global${Date()}")
delay(1000)
println("|--END global${Date()}")
}
println("|--END ${Date()}")
}
}
activity_main.xml
现在我们就来看看如何给我们的XML文件里面的View设置值。
在XML文件的layout标签下,创建data标签,在data标签中再创建variable标签,variable标签主要用到的就是name属性和type属性,类似于Java语言声明变量时,需要为该变量指定类型和名称。新建一个名为Login的数据类。
在XML文件中声明好variable属性后,接下来就可以在XML使用它了。
使用variable属性时需要使用到布局表达式: @{ }
。
可以在布局表达式@{ }
中获取传入variable对象的
activity_main.xml (源码 )
<?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="login"
type="com.example.lanidemokt.MainActivity.Login" />
<variable
name="student"
type="com.example.lanidemokt.MainActivity.Student" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{login.name}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="名字" />
<TextView
android:id="@+id/msg2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{login.msg}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/msg"
tools:text="消息" />
<TextView
android:id="@+id/msg4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{ ` `+ student.score}"
app:layout_constraintBottom_toBottomOf="@id/login"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/msg2"
tools:text="消息2" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
2. 给控件View添加响应事件:
方式一:直接在Controller层通过原来的方式添加
binding?.login?.setOnClickListener {
}
方式二:
创建一个工具类,在类中定义响应的点击事件
第一步:创建点击的工具类 ButtonClickListener.kt
第二步:在XML文件中添加工具类 ,在XML文件中添加响应事件:
第三步:在XML文件中添加响应事件 android:onClick="@{btnHandler::click}"
第四步:在Controller里面进行关联 binding?.btnHandler = clickListener
activity_main.xml (增加点击事件的完整源码)
<?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标签中再创建variable标签,variable标签主要用到的就是name属性和type属性 ,
类似于Java语言声明变量时,需要为该变量指定类型和名称
-->
<import type="android.view.View" />
<!-- <import type="com.example.lanidemokt.MainActivity" />-->
<!-- <variable
name="login"
type="com.example.lanidemokt.MainActivity.Login" />-->
<variable
name="btnHandler"
type="com.example.lanidemokt.viewmodel.ButtonClickListener" />
<variable
name="login"
type="com.example.lanidemokt.MainActivity.Login" />
<variable
name="student"
type="com.example.lanidemokt.MainActivity.Student" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{login.name}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="名字" />
<TextView
android:id="@+id/msg2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{login.msg}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/msg"
tools:text="消息" />
<TextView
android:id="@+id/msg4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{ ` `+ student.score}"
app:layout_constraintBottom_toBottomOf="@id/login"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/msg2"
tools:text="消息2" />
<Button
android:id="@+id/login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="杀生丸哥哥"
android:onClick="@{btnHandler::msgTextClickListener}"
android:layout_marginBottom="20dp"
app:layout_constraintBottom_toBottomOf="@id/picture1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/msg4"
tools:text="消息2" />
<ImageView
android:id="@+id/picture1"
android:layout_width="300dp"
android:layout_height="200dp"
android:layout_marginBottom="20dp"
android:layout_marginTop="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/login"
app:layout_constraintBottom_toBottomOf="parent"
app:url="@{`http://192.168.1.207:8080/download/kn.png`}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
ButtonClickListener.kt 源码
package com.example.lanidemokt.viewmodel
import android.view.View
import android.widget.TextView
import com.example.lanidemokt.utils.LogSetting
class ButtonClickListener {
/*
* 界面点击事件封装
* */
fun msgTextClickListener(view: View) {
view.text = "杀生丸丸哥哥一直很帅"
// view.setText( "杀生丸丸哥哥一直很帅") //Use of setter method instead of property access syntax
}
}
3. XXXBindingAdapter方法实现响应
使用DataBinding库时,DataBinding会针对控件属性生成对应的XXXBindingAdapter类,如TextViewBindingAdapter类,其对TextView的每个可以使用DataBinding的属性都生成了对应的方法,而且每个方法都使用了@BindingAdapter注解,注解中的参数就是对应View的属性。
自定义BindingAdapter 编写一个处理图片的自定义BindingAdapter类。然后定义一个静态方法,主要用于添加 BindingAdapter 注解,注解值是 ImageView 控件自定义的属性名,如下所示。
MainActivityBindingAdapter.kt (源码)
图片资源是部署到本地的Nginx上的:
http://192.168.1.207:8080/download/kn.png
package com.example.lanidemokt.adapter
import android.util.Log
import android.widget.ImageView
import androidx.databinding.BindingAdapter
import com.bumptech.glide.Glide
import com.catchpig.utils.LogUtils
import com.example.lanidemokt.utils.LogSetting
class MainActivityBindingAdapter {
companion object {
val TAG: String = "MainActivityBindingAdapter"
/*
* 通过默认adapter 设置自定app:xxx属性,并设置xx属性值,实现响应式修改更新
*
* */
@BindingAdapter("url")
@JvmStatic
fun loadStudentDetails(
view: ImageView,
url: String = "http://192.168.1.207:8080/download/kn.png"
) {
Glide.with(view!!).load(url).into(view)
}
}
}
多个参数的话,修改@BindingAdapter有value
@BindingAdapter(value = ["url", "placeholder", "error"])
@JvmStatic
4.双向响应绑定(输入框)
输入数字时,消息text同步更新
Build.gradle(:app) 引入依赖库(完整源码)
增加自动生成BR实体的依赖库,
id 'kotlin-kapt' kapt { generateStubs = true } kapt "androidx.room:room-compiler:2.4.0"
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
id 'kotlin-kapt'
}
android {
namespace 'com.example.lanidemokt'
compileSdk 31
defaultConfig {
applicationId "com.example.lanidemokt"
minSdk 24
targetSdk 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
lintOptions {
abortOnError false
}
buildFeatures {
viewBinding true
// dataBinding true
compose true
}
dataBinding {
enabled = true
}
kapt {
generateStubs = true
}
}
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
implementation "androidx.compose.ui:ui:1.0.1"
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'androidx.appcompat:appcompat-resources:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.compose.material:material:1.0.1'
implementation 'com.github.bumptech.glide:compiler:4.11.0'
implementation 'com.github.bumptech.glide:glide:4.11.0'
kapt "androidx.room:room-compiler:2.4.0"
}
MainActivity.kt 源码
增加绑定viewmodel: binding?.order = OrderViewModel() // 绑定双向响应实体
package com.example.lanidemokt
import android.os.Bundle
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.databinding.BaseObservable
import androidx.databinding.DataBindingUtil
import com.catchpig.utils.LogUtils
import com.example.lanidemokt.adapter.MainActivityBindingAdapter
import com.example.lanidemokt.databinding.ActivityMainBinding
import com.example.lanidemokt.viewmodel.ButtonClickListener
import com.example.lanidemokt.viewmodel.OrderViewModel
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.Date
class MainActivity : AppCompatActivity() {
/*
* DataBinding 对应一个Binding对象,对象名是布局文件文称加上Binding后缀
* binding,activity_main.xml的布局实例
* xml上所有变量与点击事件,必须是binding的成员属性或者成员方法函数,否则操作界面无效
* 布局取响应式值 -表达式: `@{ }
*
* */
var binding: ActivityMainBinding? = null // 操作布局实例
private var login: Login? = null //声明一个响应式对象,用于ui
var clickListener: ButtonClickListener? = null // 布局点击对象封装
var vm: OrderViewModel = OrderViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) // setContentView(R.layout.activity_main)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
initView()
initData()
corGlobalTest()
}
private fun initData() {
LogUtils.init(this)
}
/* *
* 在Controller层,将我们的data与model相关联
* */
data class Login(var name: String = "LLL", var msg: String) //意向绑定响应式
data class Student(var name: String = "LLL", var score: Int) : BaseObservable() //双击绑定响应式
private fun initView() {
binding?.msg?.setText("我是谁")
login = Login("LANI", "我是谁")
// binding?.login = Login("LANI", "我是谁")
// 这一步必须要,否则点击没反应,否则界面不显示对应的名字与信息
binding?.setLogin(Login("LANI", "我是谁"))
binding?.setStudent(Student("LEE", 199))
MainActivityBindingAdapter.loadStudentDetails(
binding?.picture1 as ImageView,
"http://192.168.1.207:8080/download/kn.png"
)
binding?.picture1?.setOnClickListener {
println("图片点击")
LogUtils.d("图片点击")
MainActivityBindingAdapter.loadStudentDetails(
it as ImageView,
"http://192.168.1.207:8080/download/88.jpg"
)
}
clickListener = ButtonClickListener()
binding?.btnHandler = clickListener //给控件添加响应事件 :点击事件
binding?.order = OrderViewModel() // 绑定双向响应实体
}
/*
* 协程创建
* */
fun corGlobalTest() {
GlobalScope.launch {
println("|--开始global${Date()}")
delay(1000)
println("|--END global${Date()}")
}
println("|--END ${Date()}")
}
}
OrderViewModel.kt 源码
实现双向绑定 viewmodel,BaseObservable :普通的数据对象包装成一个可观察的数据对象
package com.example.lanidemokt.viewmodel
import androidx.databinding.BaseObservable
import androidx.databinding.Bindable
import com.catchpig.utils.LogUtils
import com.example.lanidemokt.BR
class OrderViewModel : BaseObservable() {
/*
* 实现双向绑定 viewmodel,
* BaseObservable :普通的数据对象包装成一个可观察的数据对象
* 当使用name字段发生变更后,若想UI自动刷新,
* 要求方法名必须以get开头并且标记Bindable注解
* 注解才会自动在build目录BR类中生成entry
* 数据模型继承 BaseObservable;
* 要求获取数据方法名必须以 get 开头并且标记 @Bindable 注解;
* 设置数据方法必须以 set 开头然后调用 notify() 函数既可以刷新视图。
* BR 类是 BaseObservable 子类中由 @Bindable 注解修饰的函数生成;
* BR 类生成位置在 //app\build\generated\source\kapt\debug\com\example\lanidemokt
*
* */
@get:Bindable
var orderCount: String? = "100"
set(orderCount) {
LogUtils.d("当前orderCount=${orderCount}")
field = orderCount
notifyPropertyChanged(BR.orderCount)
}
}
activity_main.xml源码
引入viewmodel:
<variable name="order" type="com.example.lanidemokt.viewmodel.OrderViewModel" />
<?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>
<import type="android.view.View" />
<variable
name="order"
type="com.example.lanidemokt.viewmodel.OrderViewModel" />
<variable
name="btnHandler"
type="com.example.lanidemokt.viewmodel.ButtonClickListener" />
<variable
name="login"
type="com.example.lanidemokt.MainActivity.Login" />
<variable
name="student"
type="com.example.lanidemokt.MainActivity.Student" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:onClick="@{btnHandler::msgTextClickListener}"
android:text="杀生丸哥哥"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/msg4"
tools:text="杀生丸哥哥" />
<TextView
android:id="@+id/msg5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@{ ` 当前订单数量:`+order.orderCount}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/login"
tools:text="消息2" />
<!-- 双向响应数据,赋值语法 @={xx.xx}-->
<EditText
android:id="@+id/username"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="@={order.orderCount}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/msg5" />
<!-- XXXBindingAdapter方式设置app:url -->
<ImageView
android:id="@+id/picture1"
android:layout_width="300dp"
android:layout_height="200dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/login"
app:url="@{`http://192.168.1.207:8080/download/kn.png`}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
双向响应完结。
-- 设置网络图片在ImageView 打开网络权限
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/Theme.LaniDemoKt"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:usesCleartextTraffic="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
app\src\main\res\xml\network_securit_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>