Jetpack:Data Binding入門指南

又到週末好時光,開始嗨以前再抽點時間看看本文,能看到最後的都是大佬,收下個人膜拜。本文技術內容講的是關於Data Binding Library的那點事,有的同窗可能解過了,有的娃可能都不知道是什麼東東...。爲了避免落伍,和你們同樣優秀,決定寫Jetpack方面的文章。與別人不同的是,會加入本身的理解和栗子,而不是簡單的翻譯(我英文水平也不行)。若是你們發現有誤的地方,但願多加指點,在此謝過。html

About Jetpack

一年前有緣看了一下Jetpack,但並無過多的去關注,最近在看Google IO 2019相關資料,看到了Jetpack的身影,不得不陷入深思,沒法自拔。java

JetPack的官方說法:

Jetpack 是 Android 軟件組件的集合,使您能夠更輕鬆地開發出色的 Android 應用。這些組件可幫助您遵循最佳作法、讓您擺脫編寫樣板代碼的工做並簡化複雜任務,以便您將精力集中放在所需的代碼上。android

總結性

  • 加速開發:以組件的形式供咱們依賴使用。
  • 消除樣板代碼:還記得在Activity中一大堆findViewById麼?能作的不止這麼多。
  • 構建高質量應用:如今化設計、避開bug、向後兼容。

Android Jetpack 組件是庫的集合,這些庫是爲協同工做而構建的,不過也能夠單獨採用,同時利用 Kotlin 語言功能幫助提升工做效率。可所有使用,也可混合搭配!git

以上是對官網的摘錄。做爲開山之篇,先從架構方向的數據綁定庫入門開始,讓同窗感覺它的魅力。express

Data Binding Library(數據綁定庫)

藉助數據綁定庫(Data Binding Library),可使用聲明性格式(而非程序化地)將佈局中的界面組件綁定到應用中的數據源。數據綁定庫要求在Android 4.0以上,Gradle 1.5.0以上。實踐證實Android SDK和Gradle版本越高,對Data Binding的支持越好,越簡單,速度越快。數組

舉個栗子,這個栗子不重,兩隻手指能夠舉起來:bash

findViewById<TextView>(R.id.sample_text).apply {
    text = viewModel.userName
}
複製代碼

栗子中經過findViewById找到TextView組件,並將其綁定到 viewModel 變量的 userName 屬性。而下面在佈局文件中使用數據綁定庫將文本直接分配到TextView組件上,這樣就無需調用上述任何 Java 代碼。架構

<TextView  android:text="@{viewmodel.userName}" />
複製代碼

居然這麼好用,爲啥不瞭解看看呢?app

配置

在咱們的項目build.gradle文件下配置以下代碼。框架

android {
    ...
    dataBinding {
        enabled = true
    }
}
複製代碼

若是Gradle插件版本在3.1.0-alpha06以上,可使用新的Data Binding編譯器,有利於加速綁定數據文件的生成。在項目的gradle.properties文件添加以下配置。

android.databinding.enableV2=true
複製代碼

同步一下,沒什麼問題的話,配置已經成功了~

入門

  • 定義一個數據對象
data class User(var name: String, var age: Int)
複製代碼
  • 佈局綁定

咱們建立名爲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.gitcode.jetpack.User"/>
   </data>

   <LinearLayout
           android:layout_width="match_parent"
           android:layout_height="match_parent">
       //在TextView中使用
       <TextView android:layout_width="match_parent"
                 android:gravity="center"
                 android:text="@{user.name}"
                 android:layout_height="match_parent"/>
   </LinearLayout>
</layout>
複製代碼

佈局文件的根元素再也不是以往的LinearLayout、RelativeLayout等等,而是layout。在data元素內添加variable,其屬性表示聲明一個com.gitcode.jetpack.User類型的變量user。若是多個變量的話,可在data元素下添加多個varialbe元素,格式是一致的。

<data>
   <variable name="user" type="com.gitcode.jetpack.User"/>
   <variable name="time" type="com.gitcode.jetpack.Time"/>
</data>
複製代碼

@{}語法中使用表達式將變量賦值給view的屬性。例如:這裏將user變量的firstName屬性賦值給TextView的text屬性。

android:text="@{user.firstName}"
複製代碼
  • 綁定數據

此時佈局聲明的user變量值仍是初始值,咱們須要爲其綁定數據。

默認狀況下,會根據目前佈局文件名稱來生成一個綁定類(binding class),例如當前佈局文件名是activity_main,那麼生成的類名就是ActivityMainBinding。

綁定類會擁有當前佈局聲明變量,並聲明getter或者setter方法,也就是說ActivityMainBinding類會帶有user屬性和getUser、setUser方法,變量的默認初始化與Java一致:引用類型爲null,int爲0,bool爲false。

在MainActivity的onCreate()方法中添加以下代碼,將數據綁定到佈局上。

val binding: ActivityMainBinding 
        = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.user = User("GitCode", 3)
複製代碼

經典代碼是這樣的:

setContentView(R.layout.activity_main)
val user=User("GitCode",3)
val tvName=findViewById<TextView>(R.id.tvName)
tvName.text = user.name
複製代碼

可有看出,使用數據綁定庫會使代碼簡潔不少,可讀性也很高。 運行一下項目,既能夠考到效果了~

運行效果

若是是在Fragment、Adapter中使用,那就要換個姿式了。

val listItemBinding = ListItemBinding
            .inflate(layoutInflater, viewGroup, false)
//或者
val listItemBinding = DataBindingUtil
            .inflate(layoutInflater, R.layout.list_item, viewGroup, false)

複製代碼

恭喜,你已經入門了

能夠選擇繼續學習,

看下文

也能夠當作瞭解

點個贊

看看其餘文章了~

佈局與綁定表達式

在一開始介紹Data Binding Libaray時,就使用了@{}語法,花括號裏面的內容稱爲綁定表達式,綁定表達式其實並不複雜,跟咱們正常使用Java和Kotlin語言的表達式沒多大區別。那咱們能夠在表達式中使用什麼類型的運算符或者關鍵字呢?

經常使用運算符

運算符 符號
算術 加、減、乘、除、求餘(+ 、 - 、* 、/、 %)
邏輯 與、或(&&、||)
一元 + 、-、 !、 ~
移位 >>、 >>>、 <<
關係 == 、> 、<、 >= 、<=(使用符號<時,要換成&lt;)

其餘經常使用的

同時也支持字符拼接+,instanceof,分組、屬性訪問、數組訪問、?:、轉型、訪問調用,基本類型等等等。 也就是說,綁定表達式語言大多數跟宿主代碼(Java or Kotlin)的表達式差很少。爲何說是大多數,由於不能使用thissupernewExplicit generic invocation(明確的泛型調用)等。

丟個栗子:

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
複製代碼

再舉丟個栗子:

android:text="@{user.displayName ?? user.lastName}"
複製代碼

若是user.displayName不爲null則使用,不然使用user.lastName.在這裏也看得出,能夠經過表達式訪問類的屬性。綁定類會自動檢查當前變量是否爲null,以免發生空指針異常。栗子:若是user變量爲null,那麼user.lastName也會是null。

集合

像數組,鏈表,Maps等常見的集合,均可以採用下標[]訪問它們的元素。

<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List&lt;String>"/>
    <variable name="sparse" type="SparseArray&lt;String>"/>
    <variable name="map" type="Map&lt;String, String>"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"
//或者
android:text="@{map.key}"
複製代碼

注意在data元素內添加了import元素,表示導入該類型的定義,這樣表達式中引用屬性可讀性高點,使用也方便。

來個容易掰的栗子:

<data>
    <import type="android.view.View"/>
</data>

<TextView
   android:text="@{user.lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
複製代碼

經過導入View類型,就可使用相關屬性,例如這裏的View.VISIBLE

有時導入的類全名太長了或者存在相同類型的類名,咱們就能夠給它取個別名,而後就可用別名進行coding~

<import type="android.view.View"/>

<import type="com.gitcode.jetpack.View"
        alias="JView"/>
複製代碼

使用資源

使用下面語法:

android:padding="@{@dimen/largePadding}"
複製代碼

相關資源的的表達式引用,貼張官網截圖:

事件處理

數據綁定庫容許咱們在事件到View時候經過表達式去處理它。 在數據綁定庫中支持兩種機制:方法調用和監聽器綁定。

好想一筆帶過,由於原文看不明白~~~~(>_<)~~~~
複製代碼
方法調用

點擊事件會直接綁定處處理方法上,當一個事件發生,會直接傳給綁定的方法。相似咱們在佈局上使用android:onclick與Activity 的方法綁定。在編譯的時候已經綁定,在@{}表達式中的方法若是在Activity找不到或者方法名錯誤,就會在編譯時期報錯,方法簽名(返回類型和參數相同)一致。

丟個栗子:

定義一個接口,用於處理事件。

//定義一個處理點擊事件的類
interface  MethodHandler {
    fun onClick(view: View)
}
複製代碼

在佈局聲明瞭methodHandler變量,並在Button的onClick方法使用表達式@{methodHandler::onClick},onClick方法須要與上面接口一致,否則編譯器期報錯。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        ...
        <variable name="methodHandler"
            type="com.gitcode.jetpack.MethodHandler"/>
    </data>

    <LinearLayout
            android:layout_width="match_parent"
            android:orientation="vertical"
            android:gravity="center_horizontal"
            android:layout_height="match_parent">
         ...
        <Button android:layout_width="wrap_content"
                android:text="Method references"
                android:layout_marginTop="10dp"
                android:onClick="@{methodHandler::onClick}"
                android:layout_height="wrap_content"/>
    </LinearLayout>
</layout>
複製代碼

而後在Activity中實現MethodHandler,並賦值給綁定類的變量。

class MainActivity : AppCompatActivity(), MethodHandler{
    lateinit var binding: ActivityMainBinding

      override fun onCreate(savedInstanceState: Bundle?) {
        ...
        binding.methodHandler = this
    }
    
    override fun onClick(view: View) {
        Log.i(TAG, "Method handling")
    }
}
複製代碼

所以,當咱們點擊Button的時候,Activity的onClick方法就會被回調。

監聽器綁定

監聽器綁定與方法調用不一樣的是,監聽器再也不編譯器與處理方法綁定,而是在點擊事件傳遞到當前view時,才與處理方法綁定,並且監聽器並不要表達式方法名與處理方法同名,只要返回類型一致便可,若是有返回值得話。

來個栗子:

  • 定義接口用於處理事件
interface  ListenerHandler {
    fun onClickListener(view: View)
}
複製代碼
  • 在佈局中定義變量和表達式
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="listener" type="com.gitcode.jetpack.ListenerHandler"/>
    </data>

    <Button android:layout_width="wrap_content"
            android:text="Listener"
            android:layout_marginTop="10dp"
            android:onClick="@{(view)->listener.onClickListener(view)}"
            android:layout_height="wrap_content"/>
    </LinearLayout>
<layout>
複製代碼

注意到使用lambda表達式,所以能夠在@{}內作更多操做,如預處理數據等。

  • 處理方法 一樣在Activity實現ListenerHandler方法,並賦值給綁定類的變量。
class MainActivity : AppCompatActivity(), ListenerHandler {
    lateinit var binding: ActivityMainBinding
    
    override fun onClickListener(view: View) {
        Log.i(TAG, "Listener handling")
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        binding.listener=this
    }
}
複製代碼

點擊Button,就能看到onClickListener回調了~

不過癮的,看官網

好了,講到這裏,你們喝杯奶茶續命,休息會吧~

吃瓜啦

吃完瓜了沒?吃完了就該繼續擼文了,畢竟革命還沒有成功~

綁定類

前面講的大多數是在佈局中去使用表達式,從這開始,講點代碼中的操做。在一開始入門時候,講到會根據當前佈局生成綁定類,綁定類類名由佈局名稱根據Pascal規則和添加Binding後綴生成。舉個栗子就明白了,當前佈局名稱:activity_shared.xml。生成綁定類名稱:ActivitySharedBinding。

那麼綁定類的做用是什麼?

綁定類是數據綁定庫爲讓咱們能夠訪問佈局中的變量和視圖而生成的類。

如何建立或者定製綁定類呢?

建立綁定類

  • 使用靜態inflate()方法
ActivityMainBinding.inflate(LayoutInflater.from(this))
複製代碼

重載版本

ActivityMainBinding.inflate(getLayoutInflater(), viewGroup, false)
複製代碼
  • 使用靜態bind()方法
//通常這種狀況是佈局有做其餘用途
ActivityMainBinding.bind(viewRoot)
複製代碼
  • 在Fragment,ListView,或RecyclerView的adapter使用
val listItemBinding = ListItemBinding.inflate(layoutInflater,
                        viewGroup, false)
// 或者
val listItemBinding = DataBindingUtil
                 .inflate(layoutInflater, R.layout.list_item,
                 viewGroup, false)
複製代碼

定製綁定類

經過修改data元素的class屬於達到定製不一樣名稱的綁定類,和其所存儲位置。

//生成綁定類名爲:ContactItem,存放在當前組件的綁定類包中
<data class="ContactItem">
    …
</data>

//生成綁定類名爲:ContactItem,存放在當前組件包中
<data class=".ContactItem">
    …
</data>
//生成綁定類名爲:ContactItem,存放在com.gitcode包中
<data class="com.gitcode.ContactItem">
    …
</data>
複製代碼

訪問Views

若是須要訪問佈局中Views,須要給Views添加id,數據綁定庫會盡快經過findViewById去綁定。並在Activity中經過綁定類使用。例如:

binding.tvName.text="GitCode"
複製代碼

訪問變量

數據綁定庫會爲在佈局中聲明的變量在綁定類中生成setter和getter。例如:

binding.user=User("GitCode",3)
複製代碼

綁定類官網

綁定適配器

每一個佈局表達式都對應着一個綁定適配器,用於進行設置相應屬性或監聽器所需的框架調用.通俗點說,咱們經過調用什麼方法去給屬性賦值?咱們在代碼經過setText()方法給view的text屬性賦值。講的就是下面的代碼:

binding.tvAge.text="20" //經過tvAge的setText()給TextView的android:text屬性賦值
複製代碼

好像跟咱們日常調用的沒什麼區別:

tvAge.text="20"
複製代碼

這裏講的就是這個,當數據變化時,咱們調用合適的方法(例如setText方法),去給view的屬性賦值(例如android:text的text屬性)。還不懂的話,繼續看~

給View的屬性賦值

數據綁定庫提供三種方式讓咱們去給View的屬性賦值:庫本身決定選擇調用方法;明確指定調用方法;自定義調用邏輯方法。

庫自動選擇

假如View有個屬性color,庫會嘗試去查找setColor(args)方法,參數args的類型須要和表達式的返回類型一致。例如android:color=@{"black"},由於"black"是字符串類型,因此args的參數類型就是String。命名空間android並無做強制要求,也能夠是gitcode:color=@{"black"}。庫查找方法的標準是setXXX()方法名和參數類型,這裏的XXX是指屬性名。

明確指定

雖然庫自動選擇已經很智能了,但有時view的屬性和方法名並不一致,這是就須要咱們明確指定,避免庫自動選擇找不到。例如ImageView的android:tint屬性是關聯到setImageTintList(ColorStateList)方法,而不是setTint(),這時,就須要明確指定了。

@BindingMethods(value = [
BindingMethod(
    type = android.widget.ImageView::class,
    attribute = "android:tint",
    method = "setImageTintList")])
複製代碼

BindingMethods是註解在類上的,例如Activity。能夠包含一個到多個BindingMethod註解。BindingMethod中type表示當前方法(method)匹配到到哪一個View的屬性(attribute)上。

定製邏輯方法

雖然上面二者已經知足了大多數狀況,但一些特殊狀況仍是須要本身處理邏輯的。例如,view的android:paddingLeft屬性,沒有setPaddingLeft(int)方法,但提供了setPadding(left, top, right, bottom)方法。這時候就須要咱們自定義邏輯了。

@BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, padding: Int) {
    view.setPadding(padding,
                view.getPaddingTop(),
                view.getPaddingRight(),
                view.getPaddingBottom())
}
複製代碼

BindingAdapter註解容許定製屬性的setter邏輯。setPaddingLeft方法的第一個參數必須是咱們要處理屬性的邏輯的View,後面的參數是根據BindingAdapter註解的屬性來定位的。例如這裏BindingAdapter註解只聲明瞭android:paddingLeft屬性,那麼參數padding就是paddigLeft對應的值。設置多個屬性是這樣子的:

@BindingAdapter("imageUrl", "error")
fun loadImage(view: ImageView, url: String, error: Drawable) {
    Picasso.get().load(url).error(error).into(view)
}

<ImageView app:imageUrl="@{venue.imageUrl}"
        app:error="@{@drawable/venueError}" />
複製代碼

從這裏能夠看出,庫對命名空間並無做要求。註解的值imageUrl和error類型必須對應方法參數url和error的類型String和Drawable,只有ImageView同時匹配到兩個屬性,上述方法纔會生效。爲此,能夠經過設置requireAll = false,匹配一個值也會生效。

@BindingAdapter(value = ["imageUrl", "placeholder"], requireAll = false)
fun setImageUrl(imageView: ImageView, url: String, placeHolder: Drawable) {
    if (url == null) {
        imageView.setImageDrawable(placeholder);
    } else {
        MyImageLoader.loadInto(imageView, url, placeholder);
    }
}
複製代碼

類型轉換

在綁定表達式返回一個對象時,庫會選擇一個方法來設置屬性的值,而該對象會轉型爲方法參數的類型。這種機制能夠方便使用ObservableMap來存儲數據。

<TextView
   android:text='@{userMap["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content" />
複製代碼

綁定表達式的userMap["lastName"]會返回值,該值會查找setText(CharSequence) 方法中自動轉型爲字符串並設置給TextView的text屬性。但參數類型不肯定的時候,就須要進行強制類型轉換了,以代表類型。

有時候,綁定表達式返回的類型與設置屬性方法的參數類型並不一致。例如:android:background屬性期待的是Drawable(setBackground(drawable),但設置color值時確實一個Int。

<View
   android:background="@{@color/red}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
複製代碼

這時候咱們須要使用BindingConversion註解將返回值類型Int轉換成期待的類型Drawable。

@BindingConversion
fun convertColorToDrawable(color: Int) = ColorDrawable(color)
複製代碼

總結

寫本文的時候,參考官網,看英文文檔,對一個英語剛過四級的人...詞我都認識,但組成句子,我就一臉懵逼了...

寫到一半的時候,想放棄,或者想一筆帶過...但,說過,要打造高質量文章,和對讀者負責,因此熬了幾個夜...夜太黑,沒人擔憂明天會不會後悔~

看了一下別人的文章,基本都是支持參考官網翻譯的,並無加入我的理解和篩選。而本文是在屢次參考閱讀官網文章之下加入我的理解,讓本文更加通俗易懂,更清晰表達官網的意圖。

能看到結尾的同窗也是很牛逼,須要很大的耐心,給你點個👍。那能不能舉個爪,讓我看看大家的👐。

Data Binding還有其餘知識點,我發現的英語水平已經不夠用,你們能夠看看原汁原味的官網,或者等到後面我再把它寫完...

堅持初心,寫優質好文章

開文有益,點贊支持好文

本文是Jetpack系列文章第一篇

第二篇: Jetpack:你如何管理Activity和Fragment的生命週期?

第三篇: Jetpack:在數據變化時如何優雅更新Views數據

相關文章
相關標籤/搜索