【Android】5分钟带你快速了解DataBinding

277 阅读4分钟

前言

DataBinding是一种使Android UI与后台逻辑代码相连接的技术,它使得代码和XML文件之间的交互更加简单明了,可以将布局文件中的视图与数据绑定在一起,省去了传统的 findViewById 和手动设置值的步骤,使得代码更简洁、易读、易维护,减少了模板代码和空指针异常,提高了编码效率。 DataBinding 的使用分为两个步骤,第一步是在模块的 build.gradle 文件中开启 DataBinding 功能,第二步是在布局文件中添加 DataBinding 相关的配置。

本文主要说明怎样绑定视图以及DataBinding在Activity和Fragment中的封装

开启 DataBinding 功能

在模块的 build.gradle 文件中添加如下配置开启 DataBinding 功能:

groovyCopy code
android {
    ...
    dataBinding {
        enabled = true
    }
}

在布局文件中使用 DataBinding

定义绑定数据

首先需要在布局文件的根布局中定义绑定数据:

xmlCopy code
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="user"
            type="com.example.User" />
    </data>
    
    <!-- 布局中的其他视图 -->
    
</layout>

其中,<data> 标签用于定义绑定数据,<variable> 标签用于定义一个绑定变量。name 属性用于指定变量名,type 属性用于指定变量的类型。 使用 <layout> 标签包裹整个布局,这样系统会自动生成一个对应的 binding 类。

绑定视图与数据

接下来,在布局文件中绑定视图与数据:

xmlCopy code
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="user"
            type="com.example.User" />
    </data>
    
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name}" />
        
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.age}" />
        
    </LinearLayout>
</layout>

可以看到,通过 @{} 语法可以将视图与绑定变量中的属性关联起来。user.nameuser.age 分别表示 User 类中的 nameage 属性。

绑定数据

实现绑定主要靠DataBindingUtil这个类,DataBindingUtil是Android Data Binding库提供的一个工具类,用于将布局文件与生成的绑定类绑定在一起。它提供了两个主要方法:setContentView()和inflate()。

在 Activity 中绑定数据:

ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
User user = new User("Tom", 20);
binding.setUser(user);

DataBindingUtil.setContentView 方法会自动创建绑定对象,可以通过该对象将布局文件中的视图与数据绑定起来。

在 Fragment 中绑定数据:

Fragment 的 onCreateView() 方法中,通过 DataBindingUtil 类的 inflate() 方法来获取 binding 对象。该方法接受三个参数,分别是 LayoutInflater 实例、Fragment 的布局资源 ID 和 ViewGroup 实例。其中,LayoutInflater 实例和 ViewGroup 实例可以通过参数 inflatercontainer 获取。

kotlinCopy code
import androidx.databinding.DataBindingUtil
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.example.myapp.databinding.FragmentMyBinding

class MyFragment : Fragment() {
    private lateinit var binding: FragmentMyBinding
    
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        // 使用 DataBindingUtil 类的 inflate() 方法获取 binding 实例
        binding = DataBindingUtil.inflate(inflater, R.layout.fragment_my, container, false)  
        binding.user = User("Tom","29")
        // 返回 binding 的根视图
        return binding.root
    }
}

在获取到 binding 对象之后,就可以通过该对象来访问布局中的各个视图,以及为这些视图设置数据绑定逻辑。

总的来说,在 Fragment 中使用 DataBinding 和在 Activity 中使用 DataBinding 是类似的,只不过获取 DataBinding 实例的方式略有不同。

封装

利用泛型我们可以将DataBinding在Activity和Fragment中分别封装一下,方便使用。

Activity:

import android.os.Bundle
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding

abstract class BaseBindingActivity<V : ViewDataBinding> : AppCompatActivity() {
    lateinit var mBinding: V

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState) 
        //绑定视图,添加lifecycleOwner
        mBinding = DataBindingUtil.setContentView<V>(this,initLayoutId()).apply {
            lifecycleOwner = this@BaseBindingActivity
        }
    }
    
    override fun onDestroy() {
        super.onDestroy()
        //销毁时需要解绑
        mBinding.unbind()
    }
    
    //子类继承实现,传入布局资源,绑定布局由基类完成,不需要子类负责
    @LayoutRes
    abstract fun initLayoutId() : Int
}

Fragment

和Activity内容基本相似,唯一不同点在于Fragment是需要在onCreateView中加载布局,所以使用DataBindingUtil的inflate方法获取binding对象,onCreateView需要我们返回一个View,所以返回mBinding的root,我们来看一下root的官方定义:Returns the outermost View in the layout file associated with the Binding. If this binding is for a merge layout file, this will return the first root in the merge tag.,返回与 Binding 关联的布局文件中最外层的 View。如果此绑定用于合并布局文件,这将返回合并标记中的第一个根。所以可以作为Fragment所需的View返回。

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment

abstract class BaseBindingFragment<B : ViewDataBinding> : Fragment() {

    lateinit var mBinding: B

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        //初始化mBinding,添加lifecycleOwner
        mBinding = DataBindingUtil.inflate<B>(
            inflater,
            R.layout.fragment_my,
            container,
            false
        ).apply {
            lifecycleOwner = lifecycleOwner

        }
        //返回root作为Fragment的视图
        return mBinding.root
    }

    override fun onDestroy() {
        super.onDestroy()
        mBinding.unbind()
    }
    //子类继承实现,传入布局资源,绑定布局由基类完成,不需要子类负责
    @LayoutRes
    abstract fun initLayoutId() : Int

}

用例:

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

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/tv_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello!"
            android:textSize="20sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <Button
            android:id="@+id/bt_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:hint="button"
            app:layout_constraintEnd_toEndOf="@+id/tv_text"
            app:layout_constraintStart_toStartOf="@+id/tv_text"
            app:layout_constraintTop_toBottomOf="@+id/tv_text" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Activity:

import android.os.Bundle
import com.my.myapplication.databinding.ActivityMainBinding

class MainActivity : BaseBindingActivity<ActivityMainBinding>() {

    override fun initLayoutId(): Int = R.layout.activity_main

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mBinding.apply {
            btButton.setOnClickListener {
                this.tvText.text = "Binding!"
            }
        }
    }
}

Fragment:


import android.os.Bundle
import android.view.View
import com.my.myapplication.databinding.FragmentMyBinding

class MyBindingFragment : BaseBindingFragment<FragmentMyBinding>() {
    override fun initLayoutId(): Int = R.layout.fragment_my

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        mBinding.apply {
            btButton.setOnClickListener {
                this.tvText.text = "Binding!"
            }
        }
    }
}

优点和缺点

优点:

  • 使代码更简洁、易读、易维护。
  • 提高了编码效率,可以减少模板代码的编写。
  • 可以避免 findViewById 和手动设置值的步骤,提高了代码的可读性。
  • 支持双向数据绑定,可以自动同步视图和数据的变化。

缺点:

  • DataBinding 在生成绑定类时会增加编译时间,并且在运行时可能会增加一些开销,因为需要生成大量的代码。另外,绑定视图和数据会带来额外的内存开销,因此需要在设计时进行优化。

综上所述,DataBinding 提供了一种方便的方式来绑定布局和数据,但在实际应用中需要根据具体场景来评估其优劣并进行合理使用和优化。

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 3 天,点击查看活动详情