jjzjj

ViewBinding和DataBinding的理解和区别

我的猫叫冰彬 2023-08-05 原文

之前一直把ViewBinding当成了DataBinding,直到最近的学习中才发现他们不是一个东西。于是写下这篇笔记帮助理解和区分他们俩。

一、ViewBinding

1.什么是ViewBinding

先来看看官方是怎么说的。

通过视图绑定功能,您可以更轻松地编写可与视图交互的代码。在模块中启用视图绑定之后,系统会为该模块中的每个 XML 布局文件生成一个绑定类。绑定类的实例包含对在相应布局中具有 ID 的所有视图的直接引用。

在大多数情况下,视图绑定会替代 findViewById

来源:视图绑定 | Android 开发者 | Android Developers (google.cn)

在刚接触Android的时候,获取布局里的一个组件是通过findViewById去获取的。比如获取一个Button,那么写法就是

val btn: Button = findViewById(R.id.btn)

于是当组件很多的时候,就需要大量的用findViewById来获取,这是很繁琐的。然后在学习郭霖老师的《第一行代码时》,郭神书里提到了kotlin-android-extensions这个插件。该插件能够帮我们省去findViewById,在用Kotlin写的时候可以直接通过视图组件的Id来获取。

比如视图里有一个id为btn的Button组件,那么在Acitivy中就会有一个btn变量。这个插件帮我们简化了上面的步骤。

但是这个插件很快就被Google废弃了,在AndroidStudio中引入会出现警告。

 

取而代之的则是ViewBinding。

而Viewbinding就是会给每一个xml布局生成一个对应的binding类。比如activity_main.xml布局,就会生成一个ActivityMainBinding类。

2.怎么使用ViewBinding

想要使用ViewBinding,首先需要在build.gradle里加入配置。

android {
    ...
    buildFeatures {
        viewBinding true
    }
}

然后创建一个MainActivity,并写下布局文件activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
​
    <Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
​
    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="textView"
        android:textColor="@color/black"
        android:gravity="center"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn" />
    
</androidx.constraintlayout.widget.ConstraintLayout>

然后在Activity里的使用

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }
}

ViewBinding帮我们生成了一个ActivityMainBinding类,我们通过ActivityMainBinding.inflate()来加载布局。

然后我们就可以通过binding来操作布局了。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        
        binding.btn.setOnClickListener {
            Toast.makeText(this, "click", Toast.LENGTH_SHORT).show()
        }
        binding.tv.text = "setText"
    }
}

这样就帮我们省去了FindViewById的步骤,这也是ViewBinding最大的功能。

二、DataBinding

1.什么是DataBinding

还是先看看官方的说法

数据绑定库是一种支持库,借助该库,您可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。

来源:数据绑定库 | Android 开发者 | Android Developers (google.cn)

这里官方的说话就有点看不懂了。那我就尝试来解释一下。

比如我们要修改一个TextView的text值,之前都是在代码里获取到TextView组件,然后通过textView.text去赋值。比如

val str = "setText"
binding.tv.text = str

或者当我们要获取一个TextView的文本值时,也需要通过textView.text来获取

val value = binding.tv.text.toString()

这样的操作就是程序化,要在逻辑代码里去赋值或取值。如果我们可以在xml布局文件里就声明该组件的值和哪个变量绑定,就能方便很多。

利用DataBinding就能做到这一点,比如

<?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
       <data>
           <variable name="user" type="com.example.User"/>
       </data>
       <LinearLayout
           android:orientation="vertical"
           android:layout_width="match_parent"
           android:layout_height="match_parent">
           <TextView android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:text="@{user.firstName}"/>
           <TextView android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:text="@{user.lastName}"/>
       </LinearLayout>
    </layout>
    

这样声明式的方式就将数据绑定了。具体的使用方法会在下面给出。

2.怎么使用DataBinding

2.1 基础用法

想要使用DataBinding,同样需要现在build.gradle里加入配置

android {
    ...
    buildFeatures {
        dataBinding true
    }
}

然后我们先创建一个User的实体类,用于绑定数据

class User(var firstName: String, var lastName: String) {}

然后写布局activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
       <data>
           <variable name="user" type="com.example.User"/>
       </data>
       <LinearLayout
           android:orientation="vertical"
           android:layout_width="match_parent"
           android:layout_height="match_parent">
           <TextView android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:text="@{user.firstName}"/>
           <TextView android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:text="@{user.lastName}"/>
       </LinearLayout>
    </layout>

注意这里的格式, 最外层是一个Layout标签,里面包含了data标签和LinearLayout标签,这是Databinding的表示方式,LinearLayout其实就是这里的根布局。

如何形成这样的文件呢?在原本的xml文件里,将光标移动到根布局的位置,然后按alt+enter就会出现选项转换到DataBinding的格式。

 

然后再来分析布局文件的代码。

        <data>
           <variable name="user" type="com.example.User"/>
       </data>

data标签里表示的是数据源,name是名称,type是类型。这里的type就是我们前面写的User实体类。

    <LinearLayout
           android:orientation="vertical"
           android:layout_width="match_parent"
           android:layout_height="match_parent">
           <TextView android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:text="@{user.firstName}"/>
           <TextView android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:text="@{user.lastName}"/>
       </LinearLayout>

然后在TextView里的text属性,通过@{}的格式来获取data标签里声明的变量。

最后回到Activity中来绑定数据。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: ActivityMainBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.user = User("Test", "User")
    }
}

首先是定义DataBinding时和ViewBinding有所不同,DataBinding是通过DataBindingUtil.setContentView来绑定布局的。

最后一行就是执行了数据绑定。

运行后结果如下:

 

这就是databinding最基础的用法。

2.2 绑定点击事件

databinding不止能绑定数据,还能绑定一些事件,比如点击事件。

我们先创建一个Handlers,里面添加一个onClick()方法,用于点击事件回调。

class Handlers {
​
    fun myOnClick(view: View) {
        Log.d("Handlers", "onClick()")
    }
}

然后修改刚才的布局文件,添加一个Button组件,然后在data标签里声明刚才写的Handlers。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="handler" type="com.example.example.Handlers" />
        <variable name="user" type="com.example.example.User"/>
    </data>
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        
        ...
        
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{handler::onClick}"
            android:text="Button"/>
​
    </LinearLayout>
​
​
</layout>

注意Button的onClick属性。依然是通过@{}来调用变量,并且对于方法的调用是"::"。

需要注意的是,自定义的这个方法的签名必须和监听器对象方法中的签名完全一样。比如点击事件是View.OnClickListner的onClick(view: View),那么自定义的方法参数也必须保持一致:myOnClick(view: View),否则会报错。

然后运行试一下,点击按钮,查看日志。

 

成功绑定。

2.3 使用可观察的数据对象

前面的基础使用,我们只是知道了DataBinding如何声明式而非程序式的赋值。但是这样的情况下,如果我们要修改视图里的值,我们依然需要通过在代码里赋值的方式才能修改。

数据绑定,我们更希望的是让组件属性的值直接绑定到一个变量中,当变量发生改变时组件属性的值相应发生改变,而不需要我们再去进行赋值。

那么要实现这种当一个值发生改变,另一个值相应发生改变的效果,我们很容易想到观察者模式。那我们就可以将变量变成一个可观察的数据对象。

如果去实现Observable接口的话,对于一些简单的类型来说比较麻烦。所以基础类型的变量可以用以下的类来声明

  • ObservableBoolean

  • ObservableByte

  • ObservableChar

  • ObservableShort

  • ObservableInt

  • ObservableLong

  • ObservableFloat

  • ObservableDouble

  • ObservableParcelable

接下来就来实践测试一下。

我们先修改一个User实体类

class User {
    val firstName = ObservableField<String>()
    val lastName = ObservableField<String>()
    val age = ObservableInt()
}

注意这里声明变量我们使用了val,是因为要使用Observable要尽量避免开箱或封箱操作,在Java里声明也应该是pubc final属性

private static class User {
        public final ObservableField<String> firstName = new ObservableField<>();
        public final ObservableField<String> lastName = new ObservableField<>();
        public final ObservableInt age = new ObservableInt();
    }

这里有人可能会有疑问,用val定义了我怎么修改变量的值?其实Observable的实现类里都提供了get()和set()函数来修改具体的值。

接着在布局里新增一个TextView来展示age变量。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="handler" type="com.example.example.Handlers" />
        <variable name="user" type="com.example.example.User"/>
    </data>
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.firstName}"/>
        <TextView android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.lastName}"/>
        <TextView android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(user.age)}"/>
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{handler::onClick}"
            android:text="Change"/>
    </LinearLayout>
</layout>
​

然后修改一下Handlers的点击事件逻辑,点击按钮修改user的变量值。

class Handlers(private val user: User) {
​
    fun onClick(view: View) {
        user.firstName.set("Luka")
        user.lastName.set("Dončić")
        user.age.set(23)
        Log.d("Handlers", "onClick()")
    }
}

最后在MainActivity里绑定

package com.example.example
​
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.databinding.DataBindingUtil
import com.example.example.databinding.ActivityMainBinding
​
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: ActivityMainBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_main)
        val user = User()
        user.firstName.set("Stephen")
        user.lastName.set("Curry")
        user.age.set(34)
        binding.user = user
        binding.handler = Handlers(user)
    }
}

最终的效果如下

 

这样我们就不用再去给组件属性做赋值操作,只需要修改绑定的变量的值即可。

三、区别

看到这里,相信你对ViewBinding和DataBinding都有了一定的了解。接下来就总结一下他们的区别。

  1. 目的不同。ViewBinding的出现仅仅是为了帮开发人员省去写findViewById的步骤;而DataBinding是用于绑定数据的,能够把视图的数据和代码变量绑定起来,并且实现自动更新。这个特性使得DataBinding能和MVVM框架进行很好的配合。

  2. 初始化方式不同。ViewBinding通过生成的Binding类的inflate方法来加载布局,然后还需要用Activity的setContentView()方法来绑定。而Databinding则是通过DataBindingUtil.setContentView()来绑定的。

  3. 包含关系。DataBinding也有ViewBinding的功能,也可以省去findViewById()方法。

四、总结

本文从使用方面理解了什么是ViewBinding和DataBinding,并对他们做了区别分析,加深对他们俩的理解。

本文主要参考资料为Android 开发者 | Android Developers (google.cn)

有关ViewBinding和DataBinding的理解和区别的更多相关文章

  1. ruby - 触发器 ruby​​ 中 3 点范围运算符和 2 点范围运算符的区别 - 2

    请帮助我理解范围运算符...和..之间的区别,作为Ruby中使用的“触发器”。这是PragmaticProgrammersguidetoRuby中的一个示例:a=(11..20).collect{|i|(i%4==0)..(i%3==0)?i:nil}返回:[nil,12,nil,nil,nil,16,17,18,nil,20]还有:a=(11..20).collect{|i|(i%4==0)...(i%3==0)?i:nil}返回:[nil,12,13,14,15,16,17,18,nil,20] 最佳答案 触发器(又名f/f)是

  2. ruby-on-rails - `a ||= b` 和 `a = b if a.nil 之间的区别? - 2

    我正在检查一个Rails项目。在ERubyHTML模板页面上,我看到了这样几行:我不明白为什么不这样写:在这种情况下,||=和ifnil?有什么区别? 最佳答案 在这种特殊情况下没有区别,但可能是出于习惯。每当我看到nil?被使用时,它几乎总是使用不当。在Ruby中,很少有东西在逻辑上是假的,只有文字false和nil是。这意味着像if(!x.nil?)这样的代码几乎总是更好地表示为if(x)除非期望x可能是文字false。我会将其切换为||=false,因为它具有相同的结果,但这在很大程度上取决于偏好。唯一的缺点是赋值会在每次运行

  3. ruby - 这两个 Ruby 类初始化定义有什么区别? - 2

    我正在阅读一本关于Ruby的书,作者在编写类初始化定义时使用的形式与他在本书前几节中使用的形式略有不同。它看起来像这样:classTicketattr_accessor:venue,:datedefinitialize(venue,date)self.venue=venueself.date=dateendend在本书的前几节中,它的定义如下:classTicketattr_accessor:venue,:datedefinitialize(venue,date)@venue=venue@date=dateendend在第一个示例中使用setter方法与在第二个示例中使用实例变量之间是

  4. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

  5. TimeSformer:抛弃CNN的Transformer视频理解框架 - 2

    Transformers开始在视频识别领域的“猪突猛进”,各种改进和魔改层出不穷。由此作者将开启VideoTransformer系列的讲解,本篇主要介绍了FBAI团队的TimeSformer,这也是第一篇使用纯Transformer结构在视频识别上的文章。如果觉得有用,就请点赞、收藏、关注!paper:https://arxiv.org/abs/2102.05095code(offical):https://github.com/facebookresearch/TimeSformeraccept:ICML2021author:FacebookAI一、前言Transformers(VIT)在图

  6. spring.profiles.active和spring.profiles.include的使用及区别说明 - 2

    转自:spring.profiles.active和spring.profiles.include的使用及区别说明下文笔者讲述spring.profiles.active和spring.profiles.include的区别简介说明,如下所示我们都知道,在日常开发中,开发|测试|生产环境都拥有不同的配置信息如:jdbc地址、ip、端口等此时为了避免每次都修改全部信息,我们则可以采用以上的属性处理此类异常spring.profiles.active属性例:配置文件,可使用以下方式定义application-${profile}.properties开发环境配置文件:application-dev

  7. ruby - 这两段代码有什么区别? - 2

    打印1:defsum(i)i=i+[2]end$x=[1]sum($x)print$x打印12:defsum(i)i.push(2)end$x=[1]sum($x)print$x后者是修改全局变量$x。为什么它在第二个例子中被修改而不是在第一个例子中?类Array的任何方法(不仅是push)都会发生这种情况吗? 最佳答案 变量范围在这里无关紧要。在第一段代码中,您仅使用赋值运算符=为变量i赋值,而在第二段代码中,您正在修改$x(也称为i)使用破坏性方法push。赋值从不修改任何对象。它只是提供一个名称来引用一个对象。方法要么是破坏性

  8. ruby - Ruby 中 .next 和 .succ 的区别 - 2

    Ruby中的Fixnum方法.next和.succ有什么区别?看起来它的工作原理是一样的:1.next=>21.succ=>2如果有什么不同,为什么有两种方法做同样的事情? 最佳答案 它们是等价的。Fixnum#succ只是Fixnum#next的同义词。他们甚至在thereferencemanual中共享同一block. 关于ruby-Ruby中.next和.succ的区别,我们在StackOverflow上找到一个类似的问题: https://stacko

  9. ruby - 在参数为 `yield self` 的方法中使用 `&block` 和在没有参数 `yield self` 的方法中使用 `&block` 有什么区别吗? - 2

    我明白了defa(&block)block.call(self)end和defa()yieldselfend导致相同的结果,如果我假设有这样一个blocka{}。我的问题是-因为我偶然发现了一些这样的代码,它是否有任何区别或者是否有任何优势(如果我不使用变量/引用block):defa(&block)yieldselfend这是一个我不理解&block用法的具体案例:defrule(code,name,&block)@rules=[]if@rules.nil?@rules 最佳答案 我能想到的唯一优点就是自省(introspecti

  10. ruby - 易于初学者理解的 Ruby 库 - 2

    关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。关闭3年前。Improvethisquestion我正处于学习Ruby的阶段,我想查看一些小型库的源代码以了解它们是如何构建的。我不知道什么是小型图书馆,但希望SO能推荐一些易于理解的图书馆来学习。因此,如果有人知道一两个非常小的库,这是新手Rubyists学习的好例子,请推荐!我想使用Manveru'sInnatelib,因为它试图保持在2000LOC以下,但我还不熟悉其中经常使用的Ruby速记。也许大约100-5

随机推荐