【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 布局后,布局如下:

<?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>

然后,在 activity_simple_text_view.xml 中设置3个 TextView 和 data 数据,此时 build 该项目即会生成 DataBinding 类(即 ActivitySimpeTextViewBinding 类,activity_simple_text_view.xml 布局如下:

<?xml version="1.0" encoding="utf-8"?>

<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 {
    
    
    /**
     * 为书本打星,注意,该方法需要为静态方法,在布局文件中,通过<import></import>标签引入
     */
    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 的布局文件,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<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 通过 <include> 绑定二级页面

Activity、Fragment 为一级页面,其通过 可引用二级页面,数据需要从一级页面传递到二级页面,架构如下:

在这里插入图片描述

首先,新建 activity_include_layout.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">

    <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 布局文件,布局如下:

<?xml version="1.0" encoding="utf-8"?>
<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 中添加网络访问权限 <uses-permission android:name="android.permission.INTERNET" />

其次,新建 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 事件,布局如下:

<?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">

    <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
        }
    }
}

猜你喜欢

转载自blog.csdn.net/jiaoyangwm/article/details/127091408