• 【Android-Jetpack进阶】7、DataBinding 布局的变量与事件绑定、inlclude 二级页面绑定、自定义 BindingAdapter


    七、DataBinding

    DataBinding 可让布局文件承担部分页面的工作,而不再需要在页面中用 findViewByID() 查找到 view,并赋值。

    7.1 布局使用 DataBinding 变量,页面赋值 DataBinding 变量

    新建项目,项目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中
        }
    }
    

    运行后,效果如下:

    在这里插入图片描述

    7.2 DataBinding 响应事件

    首先,新建 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 中的 按钮点击事件来响应,效果如下:

    在这里插入图片描述

    7.3 通过 绑定二级页面

    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>
    

    7.4 BindingAdapter 源码

    使用 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}"/>
    

    7.5 自定义 BindingAdapter

    我们可以自定义 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
            }
        }
    }
    
  • 相关阅读:
    java家教信息计算机毕业设计MyBatis+系统+LW文档+源码+调试部署
    一、领域驱动设计核心思想与设计过程
    C++进阶之继承详解
    关于用css设置input输入框hover的时候的样式以及当input为disabled的时候,不要让hover样式生效
    1488. 避免洪水泛滥
    java JVM总结
    VMware Aria Automation 8.10 - 现代基础架构自动化平台
    【2022蓝帽杯】file_session && 浅入opcode
    LeetCode:342. 数位和相等数对的最大和(C++)
    学习笔记-SQLi
  • 原文地址:https://blog.csdn.net/jiaoyangwm/article/details/127091408