资讯详情

Jetpack DataBinding

前言

??DataBinding解决它只是一种工具View绑定数据。

??官方文档:https://developer.android.google.cn/jetpack/androidx/releases/databinding

??官方Demo地址:https://github.com/googlecodelabs/android-databinding

本文代码地址

如何使用DataBinding呢?

引用官方文件:

Databinding与 Android Gradle 插件捆绑在一起。您无需声明对此库的依赖项,但必须启用它。

?:即使模块不直接使用数据绑定,数据绑定也必须用于所有依赖数据绑定库的模块。

//在gradle的android下加入,然后点击sync android {     ...     //android studio 4.0以下     dataBinding{          }     //android studio4.1以后     buildFeatures {         dataBinding true     } } 

在我们的布局文件中,选择根目录的View,按下Alt 回车键,点击Convert to data binding layout,可转换为DataBinding布局啦。

[外链图片转存失败,源站可能有防盗链机制,建议保存图片并直接上传(img-RtBN1kBH-1656641965877)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4e2c54b2aef04c9cbe77242578139dc7~tplv-k3u1fbpfcp-watermark.image?)] 然后我们的布局就会变成这样:

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

我们可以发现,最外面变成了layout元素,里面有data元素。我们将在data元素中声明这个布局中使用到的变量,以及变量的类型。

举个例子:

<data>
    <import type="com.example...."/>
    <variable name="color" type="java.lang.String" />
</data>
  • 标签内进行变量声明和导入等
  • 进行变量声明
  • 导入需要的类

class User() { 
        
    var name = "Taxze"
    var age = 18
    fun testOnclick() { 
        
        Log.d("onclick", "test")
    }
}

然后在data中声明变量,以及类名

<data>
    <!-- <variable-->
    <!-- name="user"-->
    <!-- type="com.taxze.jetpack.databinding.User" />-->
    <import type="com.taxze.jetpack.databinding.User" />
​
    <variable name="user" type="User" />
</data>

然后在布局中使用@{}语法

//伪代码,请勿直接CV
<TextView ... android:text="@{user.name}" />

在Activity中通过DataBindingUtil设置布局文件,同时省略Activity的setContentView方法

class MainActivity : AppCompatActivity() { 
        
    private lateinit var mainBinding: ActivityMainBinding
    private lateinit var mainUser: User
    override fun onCreate(savedInstanceState: Bundle?) { 
        
        super.onCreate(savedInstanceState)
        mainBinding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        mainUser = User()
        mainBinding.user = mainUser
    }
}

在Fragment中使用:

class BlankFragment : Fragment() { 
        
    private lateinit var mainFragmentBinding:FragmentBlankBinding
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View { 
        
        mainFragmentBinding = DataBindingUtil.inflate(inflater,R.layout.fragment_blank,container,false)
        return mainFragmentBinding.root
    }
}

系统会为每个布局文件都生成一个绑定类。一般默认情况下,类的名称是布局文件名称转化为Pascal大小写形式,然后在末尾添加Binding后缀,例如:名称为activity_main的布局文件,对应的类名就是ActivityMainBinding

运行之后的效果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oogYJCMc-1656641965878)(https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/485e5022d3234a8e9ee5a11237682265~tplv-k3u1fbpfcp-watermark.image?)]

注意:只有当布局文件转换为layout样式之后,databinding才会根据布局文件的名字自动生成一个对应的binding类,你也可以在build/generated/data_binding_base_source_out目录下查看生成的类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MakflB4w-1656641965879)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/25e2c1d9d09447488bde68caaa26648e~tplv-k3u1fbpfcp-watermark.image?)] 最最最基础的使用就是这样,接下来我们来讲讲如何更好的使用DataBinding

如何在xml布局中更好的使用DataBinding

  • 加入我们传入了一个集合books,我们可以通过以下方式使用:

    • 获取集合的值
    • android:text="@{books.get(0).name}"
      android:text="@{books.[0].name}"
      
    • 添加默认值(⚡默认值无需加引号,且只在预览视图显示)
    • android:text="@{books.pages,default=330}"
      
    • 通过??或?:来实现
    • android:text="@{books.pages != null ? book.pages : book.defaultPages}"
      
    • android:text="@{books.pages ?? book.defaultPages}"
      

  • map类型的结构也可以通过get和[]两种方式获取

    • //需要注意单双引号
      android:text="@{books.get('name')}"
      android:text="@{books['name']}"
      

  • 因为DataBinding不会自动做类型转换,所有我们需要手动转换,例如在text标签内使用String.valueOf()转换为String类型,在rating标签内我们可以使用Float.valueOf()进行转换

    • android:text="@{String.valueOf(book.pages)}"
      
    • android:rating="@{Float.valueOf(books.rating),default=2.0}"
      

  • 如果我们导入的包名有冲突,我们可以通过alias为它设置一个别名

    • //伪代码,请勿直接CV
      <data>
            <import type="com.xxx.a.Book" alias="aBook"/>
            <import type="com.xxx.B.Book" alias="bBook"/>
            ...
      </data>
      

  • 在一个view上引用其他view的属性

    • //伪代码,请勿直接CV
      <import type="android.view.View"/>
      ...
      <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
          <CheckBox android:id="@+id/checkOne" .../>
          <ImageView android:visibility="@{checkOne.checked ? View.VISIBLE : View.GONE}" .../>
      </LinearLayout>
      
  • **includeViewStub**标签

    • includemerge标签的作用是实现布局文件的重用。就是说,为了高效复用及整合布局,使布局轻便化,我们可以使用includemerge标签将一个布局嵌入到另一个布局中,或者说将多个布局中的相同元素抽取出来,独立管理,再复用到各个布局中,便于统一的调整。 比如,一个应用中的多个页面都要用到统一样式的标题栏或底部导航栏,这时就可以将标题栏或底部导航栏的布局抽取出来,再以include标签形式嵌入到需要的布局中,而不是多次copy代码,这样在修改时只需修改一处即可。而我们同样可以通过DataBinding来进行数据绑定。
    • 例如:
    • //layout_title.xml
      <?xml version="1.0" encoding="utf-8"?>
      <layout xmlns:android="http://schemas.android.com/apk/res/android">
          <data>
              <import type="com.xxx.User" />
              <variable name="userInfo" type="User" />
          </data>
          <android.support.constraint.ConstraintLayout ... >
              <TextView ... android:text="@{userInfo.name}" />
          </android.support.constraint.ConstraintLayout>
      </layout>
      
    • 使用该布局,并传值
    • <include
          layout="@layout/layout_title"
          bind:test="@{userInfo}"/>
      
    • ViewStub也是类似的用法,这里就不说了。
    • DataBinding不支持merge标签

  • //伪代码,请勿直接CV
    οnclick="@{()->user.testOnclick}"
    οnclick="@{(v)->user.testOnclick(v)}"
    οnclick="@{()->user.testOnclick(context)}"
    οnclick="@{BindHelp::staticClick}"
    οnclick="@{callback}"
    ​
    //例如:
    <Button android:layout_width="match_parent" android:layout_height="match_parent" android:onClick="@{()->user.testOnclick}" />
    

💡文章到这里讲的都是DataBinding如何设置数据,以及通过DataBinding在xml中的一些基础使用。如果只是使用DataBinding这个功能,那就有点大材小用了。它还有一个很强大的功能我们还没有讲,那就是数据更新时自动刷新UI。

实现数据变化时自动更新UI

一个普通的实体类或者ViewModel被更新后,并不会让UI自动更新。而我们希望,当数据变更后UI要自动更新,那么要实现数据变化时自动更新UI,有三种方法可以使用,分别是BaseObservableObservableFieldObservableCollection

💡

  • BaseObservable提供了两个刷新UI的方法,分别是 notifyPropertyChanged() 和 notifyChange() 。

    • 第一步:修改实体类

      将我们的实体类继承与BaseObservable。需要响应变化的字段,就在对应变量的get函数上加 @Bindable 。然后set中notifyChange是kotlin的写法,免去了java的getter setter的方式。成员属性需要响应变化的,就在其set函数中,notify一下属性变化,那么set的时候,databinding就会感知到。

      import androidx.databinding.BaseObservable
      import androidx.databinding.Bindable
      import androidx.databinding.library.baseAdapters.BR
      ​
      class User() : BaseObservable() { 
                  
          constructor(name: String, age: Int) : this() { 
                  
              this.name = name
              this.age = age
          }
          //这是单独在set上@bindable,name可以为声明private
          var name: String = ""
              set(value) { 
                  
                  field = value
                  notifyPropertyChanged(BR.name)
              }
              @Bindable
              get() = field
      ​
          //这是在整个变量上声明@bindable,所以必须是public的
          @Bindable
          var age:Int = 18
              set(value) { 
                  
                  field = value
                  notifyPropertyChanged(BR.age)
              }
              get() = field
      }
      
    • 第二步:在Activity中使用

      class MainActivity : AppCompatActivity() { 
                  
          private val TAG = "MainActivity"
          private lateinit var mainBinding: ActivityMainBinding
          private lateinit var mainUser: User
          override fun onCreate(savedInstanceState: Bundle?) { 
                  
              super.onCreate(savedInstanceState)
              mainBinding =
                  DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
              mainUser = User("Taxze", 18)
              mainUser.addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() { 
                  
                  override fun onPropertyChanged(sender: Observable, propertyId: Int) { 
                  
                      when { 
                  
                          BR.user == propertyId -> { 
                  
                              Log.d(TAG, "BR.user")
                          }
                          BR.age == propertyId -> { 
                  
                              Log.d(TAG, "BR.age")
                          }
                      }
                  }
              })
              mainBinding.user = mainUser
              mainBinding.onClickPresenter = OnClickPresenter()
          }
      ​
          inner class OnClickPresenter { 
                  
              fun changeName() { 
                  
                  mainUser.name = "Taxze2222222"
              }
          }
      }
      
    • 需要注意的点

      • 官方网站只是提示了开启DataBinding只需要在build.gradle中加入下面这行代码

      • buildFeatures {
            dataBinding true
        }
        

        但是,如果你想更好的使用DataBinding这是不够的,你还需要添加这些配置:

      • compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
        kotlinOptions {
            jvmTarget = '1.8'
        }
        
      • 🔥重点:在使用DataBinding得时候,BR对象,发现调用不了,生成也会报错,运行,需要在咱们build.gradle中进行一下配置:

        apply plugin: 'kotlin-kapt'
        kapt {
            generateStubs = true
        }
        

        然后重新运行一遍代码,你就会发现,BR文件自动生成啦!

  • 讲解了BaseObservable后,现在来将建最简单也是最常用的。只需要将实体类变化成这样即可:

    //注意observable的属性需要public权限,否则dataBinding则无法通过反射处理数据响应
    class User() : BaseObservable() { 
              
        var name: ObservableField<String> = ObservableField("Taxze")
        var age:ObservableInt = ObservableInt(18)
    }
    
  • dataBinding 也提供了包装类用于替代原生的 List 和 Map,分别是 ObservableList 和 ObservableMap

    实体类修改:

    //伪代码,请勿直接cv
    class User(){
        var userMap = ObservableArrayMap<String,String>()
    }
    //使用时:
    mainUser.userMap["name"] = "Taxze"
    mainUser.userMap["age"] = "18"
    

    使用ObservableCollection后,xml与上面的略有不同,主要是数据的获取,需要指定Key

    //伪代码,请勿直接cv
    ...
    <import type="android.databinding.ObservableMap" />
    <variable name="userMap" type="ObservableMap<String, String>" />
    ​
    //使用时:
    <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:hint="Name" android:text="@{userMap[`userName`]}" />
    

💡

  • 只需要在之前的单向绑定的基础上,将布局文件@{}变为@={},用于针对属性的数据改变的同时监听用户的更新

DataBinding在RecyclerView中的使用

在RecyclerView中使用DataBinding稍有变化,我们在ViewHolder中进行binding对象的产生,以及数据对象的绑定。

我们通过一个非常简单的例子来讲解如何在RecyclerView中使用DataBinding。

效果图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uVREYUf6-1656641965880)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/22292b1c841a46e39d61666aa2262e8a~tplv-k3u1fbpfcp-watermark.image?)]

 锐单商城 - 一站式电子元器件采购平台  

 深圳锐单电子有限公司