DataBinding 可让布局文件承担部分页面的工作,而不再需要在页面中用 findViewByID() 查找到 view,并赋值。
新建项目,项目github地址详见,在 build.gradle(app) 中添加如下设置:
android {
dataBinding {
enabled = true
}
}
新建名为 activity_simple_text_view.xml 的布局文件,鼠标悬浮在顶层标签,将其转换为 DataBinding 布局,示例如下:
转换为 DataBinding 布局后,布局如下:
<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>
然后,在 activity_simple_text_view.xml 中设置3个 TextView 和 data 数据,此时 build 该项目即会生成 DataBinding 类(即 ActivitySimpeTextViewBinding 类,activity_simple_text_view.xml 布局如下:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="com.bignerdranch.android.jetpack8databindingtest.simpletextview.BookRatingUtil" />
<variable
name="book"
type="com.bignerdranch.android.jetpack8databindingtest.model.Book" />
data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{book.title}"
android:textSize="28sp" />
<TextView
android:id="@+id/tvAuthor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{book.author}"
android:textSize="28sp" />
<TextView
android:id="@+id/tvRating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{BookRatingUtil.INSTANCE.getRatingString(book.rating)}"
android:textSize="28sp" />
LinearLayout>
layout>
新建 simpletextview/BookRatingUtil.kt,代码如下:
package com.bignerdranch.android.jetpack8databindingtest.simpletextview
object BookRatingUtil {
/**
* 为书本打星,注意,该方法需要为静态方法,在布局文件中,通过 标签引入
*/
fun getRatingString(rating: Int): String {
when (rating) {
0 -> return "零星"
1 -> return "一星"
2 -> return "二星"
3 -> return "三星"
4 -> return "四星"
5 -> return "五星"
}
return ""
}
}
新建 model/Book.kt,代码如下:
package com.bignerdranch.android.jetpack8databindingtest.model
class Book(var title: String, var author: String) {
var rating = 0
var image: String? = null
}
然后在 MainActivity 中实例化 DataBinding 对象,并赋值其 data 变量,代码如下:
package com.bignerdranch.android.jetpack8databindingtest
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import com.bignerdranch.android.jetpack8databindingtest.databinding.ActivitySimpleTextViewBinding
import com.bignerdranch.android.jetpack8databindingtest.model.Book
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val vb: ActivitySimpleTextViewBinding = DataBindingUtil.setContentView(this, R.layout.activity_simple_text_view)
val book = Book("Linux", "Linus")
book.rating = 5
vb.book = book //将对象传递到layout中
}
}
运行后,效果如下:
首先,新建 activity_event_handle.xml 的布局文件,代码如下:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="EventHandler"
type="com.bignerdranch.android.jetpack8databindingtest.eventhandle.EventHandleActivity.EventHandleListener" />
data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{EventHandler::onButtonClicked}"
android:text="Click me" />
LinearLayout>
layout>
然后,在 EventHandleActivity,kt 中,写 onButtonClicked() 函数,代码如下:
package com.bignerdranch.android.jetpack8databindingtest.eventhandle
import android.content.Context
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import com.bignerdranch.android.jetpack8databindingtest.R
import com.bignerdranch.android.jetpack8databindingtest.databinding.ActivityEventHandleBinding
class EventHandleActivity : AppCompatActivity() {
class EventHandleListener(private val context: Context) {
fun onButtonClicked(view: View?) {
Toast.makeText(context, "I am clicked!", Toast.LENGTH_SHORT).show()
}
}
}
最终,在 MainActivity 中,实例化 DataBindingUtil,并设置其 eventHandler,即可将 activity_event_handle.xml 中的 android:onClick 和 EventHandleListener 事件做绑定,代码如下:
package com.bignerdranch.android.jetpack8databindingtest
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import com.bignerdranch.android.jetpack8databindingtest.databinding.ActivityEventHandleBinding
import com.bignerdranch.android.jetpack8databindingtest.databinding.ActivitySimpleTextViewBinding
import com.bignerdranch.android.jetpack8databindingtest.eventhandle.EventHandleActivity
import com.bignerdranch.android.jetpack8databindingtest.model.Book
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// val b: ActivitySimpleTextViewBinding = DataBindingUtil.setContentView(this, R.layout.activity_simple_text_view)
// val book = Book("Linux", "Linus")
// book.rating = 5
// b.book = book //将对象传递到layout中
val b: ActivityEventHandleBinding = DataBindingUtil.setContentView(this, R.layout.activity_event_handle)
b.eventHandler = EventHandleActivity.EventHandleListener(this)
}
}
运行后,当点击布局中的按钮时,即可用 DataBindingUtil 中的 按钮点击事件来响应,效果如下:
绑定二级页面Activity、Fragment 为一级页面,其通过 可引用二级页面,数据需要从一级页面传递到二级页面,架构如下:
首先,新建 activity_include_layout.xml 布局,其通过 将数据传递给二级页面,布局如下:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="book"
type="com.michael.databindingdemo.model.Book" />
data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/secondary"
app:book="@{book}"/>
LinearLayout>
layout>
其次,新建 secondary.xml 布局文件,布局如下:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="book"
type="com.michael.databindingdemo.model.Book" />
data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{book.title}" />
<TextView
android:id="@+id/tvAuthor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{book.author}" />
LinearLayout>
layout>
使用 DataBindingUtil 后,生成的 XXXBindingAdapter 类中含有各种静态方法,这些方法中都有 @BindingAdapter
标签,标签的别名对应于 UI 控件在布局文件中的属性。
例如,DataBinding 库为 View 生成的 ViewBindingAdapter 类,其关于 android:padding 属性的源码如下:
public class ViewBindingAdapter {
public static final int FADING_EDGE_NONE = 0;
public static final int FADING_EDGE_HORIZONTAL = 1;
public static final int FADING_EDGE_VERTICAL = 2;
@BindingAdapter({"android:padding"})
public static void setPadding(View view, float paddingFloat) {
final int padding = pixelsToDimensionPixelSize(paddingFloat);
view.setPadding(padding, padding, padding, padding);
}
}
例如,DataBinding 库为 View 生成的 TextViewBindingAdapter 类,其 android:text 属性的源码如下:
public class TextViewBindingAdapter {
private static final String TAG = "TextViewBindingAdapters";
@SuppressWarnings("unused")
public static final int INTEGER = 0x01;
public static final int SIGNED = 0x03;
public static final int DECIMAL = 0x05;
@BindingAdapter("android:text")
public static void setText(TextView view, CharSequence text) {
final CharSequence oldText = view.getText();
if (text == oldText || (text == null && oldText.length() == 0)) {
return;
}
if (text instanceof Spanned) {
if (text.equals(oldText)) {
return; // No change in the spans, so don't set anything.
}
} else if (!haveContentsChanged(text, oldText)) {
return; // No content changes, so don't set anything.
}
view.setText(text);
}
因为 DataBinding 库以 static function 的方式,为各 UI 控件使用了布局表达式,所以当布局文件被渲染时,属性对应绑定的 static function 会被调用。例如下例布局中,当 TextView 控件被渲染时,Android:padding 属性会调用 ViewBindingAdapter.setPadding() 方法,android:text 属性会调用 TextViewBindingAdapter.setText() 方法。布局如下:
<TextView
android:padding="@{myPaddding}"
android:text="@{book.title}"/>
我们可以自定义 BindingAdapter,让 UI 承担更多复杂逻辑。本节我们通过 ImageView 展示如何自定义 BindingAdapter。
首先,在 build.gradle 添加 implementation 'com.squareup.picasso:picasso:2.71828'
依赖 和 id 'kotlin-kapt'
插件,在 AndroidManifest.xml 中添加网络访问权限
。
其次,新建 ImageViewBindingAdapter 类,代码如下:
package com.bignerdranch.android.jetpack8databindingtest.bindingadapter
import android.graphics.Color
import android.text.TextUtils
import android.util.Log
import android.view.View
import android.widget.ImageView
import androidx.databinding.BindingAdapter
import com.bignerdranch.android.jetpack8databindingtest.R
import com.squareup.picasso.Picasso
object ImageViewBindingAdapter {
private const val TAG = "ImageViewBindingAdapter"
/**
* 加载网络图片
*/
@BindingAdapter("image")
fun setImage(imageView: ImageView, imageUrl: String?) {
if (!TextUtils.isEmpty(imageUrl)) {
Picasso.get()
.load(imageUrl)
.placeholder(R.drawable.ic_launcher_background)
.error(R.drawable.ic_launcher_background)
.into(imageView)
} else {
imageView.setBackgroundColor(Color.DKGRAY)
}
}
/**
* 加载资源文件中的图片
*/
@BindingAdapter("image")
fun setImage(imageView: ImageView, imageResource: Int) {
imageView.setImageResource(imageResource)
}
/**
* 加载网络图片,多个参数的情况
*/
@BindingAdapter(value = ["image", "defaultImageResource"], requireAll = false)
fun setImage(imageView: ImageView, imageUrl: String?, imageResource: Int) {
if (!TextUtils.isEmpty(imageUrl)) {
Picasso.get()
.load(imageUrl)
.placeholder(R.drawable.ic_launcher_background)
.error(R.drawable.ic_launcher_background)
.into(imageView)
} else {
imageView.setImageResource(imageResource)
}
}
/**
* 演示旧参数,新参数
*/
@BindingAdapter("padding")
fun setPadding(view: View, oldPadding: Int, newPadding: Int) {
Log.e(TAG, "oldPadding:$oldPadding newPadding:$newPadding")
if (oldPadding != newPadding) {
view.setPadding(newPadding, newPadding, newPadding, newPadding)
}
}
}
然后,在布局文件 activity_binding_adapter.xml 中为 ImageView 绑定了 localImage、networkImage 和 imagePadding 三个变量,并为 Button 绑定了 onClick 事件,布局如下:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="networkImage"
type="String" />
<variable
name="localImage"
type="int" />
<variable
name="imagePadding"
type="int" />
<variable
name="ClickHandler"
type="com.bignerdranch.android.jetpack8databindingtest.bindingadapter.BindingAdapterActivity.ClickHandler" />
data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="400dp"
android:background="@color/white"
app:defaultImageResource="@{localImage}"
app:image="@{networkImage}"
app:padding="@{imagePadding}" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{ClickHandler.onClick}"
android:text="change padding" />
LinearLayout>
layout>
activity_binding_adapter.xml 布局 只要有xmlns:app="http://schemas.android.com/apk/res-auto"
,即可将布局中的变量,和 ImageViewBindingAdapter 类,一一对应,示例如下:
最终,新建 BindingAdapterActivity 类,其实例化了 ActivityBindingAdapterBinding 对象,绑定了 activity_binding_adapter.xml 文件,并为布局变量赋值:即设置了图片url 和 padding 间距,代码如下:
package com.bignerdranch.android.jetpack8databindingtest.bindingadapter
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import com.bignerdranch.android.jetpack8databindingtest.R
import com.bignerdranch.android.jetpack8databindingtest.databinding.ActivityBindingAdapterBinding
class BindingAdapterActivity : AppCompatActivity() {
private var activityBindingAdapterBinding: ActivityBindingAdapterBinding? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityBindingAdapterBinding = DataBindingUtil.setContentView(this, R.layout.activity_binding_adapter)
with(activityBindingAdapterBinding) {
this?.networkImage = "https://img1.doubanio.com/view/subject/l/public/s29612688.jpg"
this?.localImage = R.mipmap.ic_launcher
this?.imagePadding = 40
this?.setClickHandler(ClickHandler())
}
}
inner class ClickHandler {
fun onClick(view: View?) {
activityBindingAdapterBinding?.imagePadding = 180
}
}
}