Android Jetpack(2):DataBinding的基本使用

總有刁民想殺寡人發表於2020-11-13

Android DataBinding 從入門到進階

DataBinding 介紹

DataBinding 是谷歌官方釋出的一個框架,顧名思義即為資料繫結,是 MVVM 模式在 Android 上的一種實現,用於降低佈局和邏輯的耦合性,使程式碼邏輯更加清晰。MVVM 相對於 MVP,其實就是將 Presenter 層替換成了 ViewModel 層。DataBinding 能夠省去我們一直以來的 findViewById() 步驟,大量減少 Activity 內的程式碼,資料能夠單向或雙向繫結到 layout 檔案中,有助於防止記憶體洩漏,而且能自動進行空檢測以避免空指標異常

DataBinding入門

1. 啟用 DataBinding

啟用 DataBinding 的方法是在對應 Model 的 build.gradle 檔案里加入以下程式碼,同步後就能引入對 DataBinding 的支援

android {
 dataBinding {
    enabled = true
 }
}

就是這麼簡單,一個簡單的databinding配置之後,就可以開始使用資料繫結了。

2. 佈局檔案:生成 DataBinding 需要的佈局規則

我們來看看佈局檔案該怎麼寫,首先佈局檔案不再是以傳統的某一個容器作為根節點,而是使用layout作為根節點,在layout節點中我們可以通過data節點來引入我們要使用的資料來源。

開啟佈局檔案,選中根佈局的 ViewGroup,按住 Alt + Enter鍵,點選 “Convert to data binding layout”,就可以生成 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_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

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

和原始佈局的區別在於多出了一個 layout 標籤將原佈局包裹了起來,data 標籤用於宣告要用到的變數以及變數型別,要實現 MVVM 的 ViewModel 就需要把資料(Model)與 UI(View)進行繫結,data 標籤的作用就像一個橋樑搭建了 View 和 Model 之間的通道。

3. 定義一個實體類

要使用資料繫結,我們得首先建立一個實體類:

data class User(val name:String,val age:Int)

4. 在佈局檔案的data 標籤裡宣告要使用到的變數名、類的全路徑,設定資料

在data中定義的variable節點,name屬性表示變數的名稱,type表示這個變數的型別,例項就是我們實體類的類的全路徑。

這裡宣告瞭一個 User 型別的變數 user,我們要做的就是使這個變數與TextView 控制元件掛鉤,通過設定 user的變數值同時使 TextView 顯示相應的文字。

通過 @{user.name} 使 TextView 引用到相關的變數,DataBinding 會將之對映到相應的 getter 方法。

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

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

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

        <TextView
            android:id="@+id/tvName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{`名字`+user.name}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <!--注意:這裡age是int型別,必須轉化為String,否則會執行時異常-->
        <TextView
            android:id="@+id/tvAge"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{String.valueOf(user.age)}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvName" />

        <TextView
            android:id="@+id/tvPhoneNum"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{user.phoneNum==null?user.phoneNum:`17817318877`}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvAge" />

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

5. Activity 中通過 DataBindingUtil 設定佈局檔案

Activity 中通過 DataBindingUtil 設定佈局檔案,省略原先 Activity 的 setContentView() 方法。

每個資料繫結佈局檔案都會生成一個繫結類,ViewDataBinding 的例項名是根據佈局檔名來生成,將之改為首字母大寫的駝峰命名法來命名,並省略佈局檔名包含的下劃線。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding=DataBindingUtil.setContentView<ActivityMainBinding>(this,R.layout.activity_main)

        binding.user= User("趙麗穎",20,"17817318859")
    }
}

一個簡單的dataBinding案例就已經完成。

其他用法

在data中定義的variable節點,name屬性表示變數的名稱,type表示這個變數的型別,例項就是我們實體類的類的全路徑。

import

如果 User 型別要多處用到,也可以直接將之 import 進來,這樣就不用每次都指明整個包名路徑了,而 java.lang.* 包中的類會被自動匯入,所以可以直接使用

  <data>
        
        <import type="com.example.jetpack.bean.User"/>

        <variable
            name="user"
            type="User" />
    </data>

alias

先使用import節點將User匯入,然後直接使用即可。但是如果這樣的話又會有另外一個問題,假如我有兩個類都是User,這兩個UserBean分屬於不同的包中,又該如何?這時候就要用到alias了。

在import節點中還有一個屬性叫做alias,這個屬性表示我可以給該類取一個別名,我給User這個實體類取一個別名叫做Lenve,這樣我就可以在variable節點中直接寫Lenve了。

    <data>
        
        <import type="com.example.jetpack.bean.User" alias="Lenve"/>

        <variable
            name="user"
            type="Lenve" />
    </data>

設定預設值

由於 TextView在佈局檔案中並沒有明確的值,所以在預覽檢視中什麼都不會顯示,不便於觀察文字的大小和字型顏色等屬性,此時可以為之設定預設值(文字內容或者是字型大小等屬性都適用),預設值將只在預覽檢視中顯示,且預設值不能包含引號。

        <TextView
            android:id="@+id/tvName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{`名字`+user.name,default=morenzhi}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

自定義 ViewDataBinding 的例項名

可以通過如下方式自定義 ViewDataBinding 的例項名:

<data class="CustomBinding">

</data>

字串拼接

如activity_main中如下屬性

  android:text="@{`名字`+user.name}"

三目運算

       <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{user.phone==null?user.phone:`147522444`}"
            android:textSize="18sp" />

程式碼中使用控制元件

使用binding物件可獲取佈局檔案中的各個物件,根據控制元件設定的id來獲取。如

        <Button
            android:id="@+id/btn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="按鈕"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvPhoneNum" />
binding.btn.setOnClickListener {
       Toast.makeText(this,"點選了按鈕",Toast.LENGTH_SHORT).show()
}

BindingAdapter

dataBinding 提供了 BindingAdapter 這個註解用於支援自定義屬性,或者是修改原有屬性。註解值可以是已有的 xml 屬性,例如 android:src、android:text等,也可以自定義屬性然後在 xml 中使用。

對於一個 ImageView ,我們希望在某個變數值發生變化時,可以動態改變顯示的圖片,此時就可以通過 BindingAdapter 來實現。

通過 BindingAdapter 來實現載入圖片

在java中使用:

在java中使用dataBinding展示圖片很簡單,只需要配置一個靜態的BindingAdapter就可以了。

需要先定義一個靜態方法,為之新增 BindingAdapter 註解,註解值是為 ImageView 控制元件自定義的屬性名,而該靜態方法的兩個引數可以這樣來理解:

當 ImageView 控制元件的 url 屬性值發生變化時,dataBinding 就會將 ImageView 例項以及新的 url 值傳遞給 loadImage() 方法,從而可以在此動態改變 ImageView 的相關屬性。

public class DataBindingUtils {

    @BindingAdapter("imageUrl")  //imageUrl:控制元件的屬性名
    public static void loadImage(ImageView imageView, String url) {
        Glide.with(imageView.getContext())
                .load(url)
                .placeholder(R.mipmap.ic_launcher)
                .error(R.mipmap.ic_launcher)
                .into(imageView);
    }
}

在kotlin中使用:

首先:kotlin中沒有static關鍵字,但是提供了companion object{}程式碼塊和使用object關鍵字。

object關鍵字宣告一種特殊的類,這個類只有一個例項,因此看起來整個類就好像是一個物件一樣,這裡把類宣告時的class關鍵字改成了object,這個類裡面的成員預設都是static的。

@JvmStatic註解:與伴生物件搭配使用,將變數和函式宣告為真正的JVM靜態成員。

要加上kapt外掛:

apply plugin: 'kotlin-kapt'

第一種方式:使用 companion object

class DataBindingUtils {

    companion object {
        @BindingAdapter("imageUrl")
        @JvmStatic
        fun loadImage(view: ImageView, url: String) {
            Glide.with(view.context).load(url).into(view)
        }
    }
}

第二種方式:使用object

object DataBindingUtils {

    @BindingAdapter("imageUrl")
    @JvmStatic
    fun loadImage(view: ImageView, url: String) {
        Glide.with(view.context).load(url).into(view)
    }
}
val url="https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3796319594,2802761532&fm=26&gp=0.jpg"
binding.user= User("趙麗穎",20,"17817318859",url)

在 xml 檔案中關聯變數值,當中, app 這個名稱可以自定義:

       <ImageView
            android:id="@+id/ivNet"
            android:layout_width="match_parent"
            android:layout_height="300dp"
            app:imageUrl="@{user.url}"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/btn" />

BindingAdapter 更為強大的一點是可以覆蓋 Android 原先的控制元件屬性

例如,可以設定每一個 Button 的文字都要加上字尾:“-Button”

    @BindingAdapter("android:text")
    public static void setText(TextView view, String text) {
        view.setText(text + "趙麗穎");
    }
     <TextView
            android:id="@+id/tvName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{`名字`+user.name,default=morenzhi}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

這樣,整個工程中使用到了 “android:text” 這個屬性的控制元件,其顯示的文字就會多出一個字尾-趙麗穎。

可以用這個屬性來載入本地圖片:

public class ImageViewAdapter {

    @BindingAdapter("android:src")
    public static void setSrc(ImageView view, Bitmap bitmap) {
        view.setImageBitmap(bitmap);
    }
 
    @BindingAdapter("android:src")
    public static void setSrc(ImageView view, int resId) {
        view.setImageResource(resId);
    }
 
 
    @BindingAdapter("imageUrl")
    public static void setSrc(ImageView imageView, String url) {
        Glide.with(imageView.getContext()).load(url)
                .placeholder(R.mipmap.ic_launcher)
                .into(imageView);
    }
 
 
    @BindingAdapter({"app:imageUrl", "app:placeHolder", "app:error"})
    public static void loadImage(ImageView imageView, String url, Drawable holderDrawable, Drawable errorDrawable) {
        Glide.with(imageView.getContext())
                .load(url)
                .placeholder(holderDrawable)
                .error(errorDrawable)
                .into(imageView);
    }
 
 
}

Databinding 同樣是支援在 Fragment

class DemoFrgament : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val binding = DataBindingUtil.inflate<FragmentDemoBinding>(
            inflater,
            R.layout.fragment_demo,
            container,
            false
        )
        return binding.root
    }
}

DataBinding事件繫結

嚴格意義上來說,事件繫結也是一種變數繫結,只不過設定的變數是回撥介面而已,事件繫結可用於以下多種回撥事件:

  • android:onClick
  • android:onLongClick
  • android:afterTextChanged
  • android:onTextChanged

Databinding事件繫結,分兩種方式:方法引用和監聽繫結,下面分別用案例介紹兩種事件繫結的異同。

方式1:直接獲取控制元件設定點選事件

binding.btn1.setOnClickListener {
            Toast.makeText(this,"點選了按鈕1",Toast.LENGTH_SHORT).show()
}

方式2:方法引用

傳入OnClickListener的變數:

<variable
        name="listener"
        type="android.view.View.OnClickListener" />

在Button中給android:onClick設定listener的變數:

<Button
            android:id="@+id/btn1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{listener}"
            android:text="按鈕"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvPhoneNum" />

        <Button
            android:id="@+id/btn2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{listener}"
            android:text="按鈕"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/btn1" />

通過setListener傳入點選監聽給listener物件:

    binding.setListener {
            when (it.id) {
                R.id.btn1 -> Toast.makeText(this, "點選了按鈕1", Toast.LENGTH_SHORT).show()
                R.id.btn2 -> Toast.makeText(this, "點選了按鈕2", Toast.LENGTH_SHORT).show()

            }
        }

方式3:方法引用

   <variable
            name="handlers"
            type="com.example.jetpack.MainActivity" />

呼叫語法可以是@{handlers::onClickFriend}或者@{handlers.onClickFriend}:

  <Button
            android:id="@+id/btn1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{handlers::onClickFriend}"
            android:text="按鈕"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvPhoneNum" />

        <Button
            android:id="@+id/btn2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{handlers::onClickFriend}"
            android:text="按鈕"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/btn1" />
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding =
            DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)

        val url =
            "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3796319594,2802761532&fm=26&gp=0.jpg"
        binding.user = User("趙麗穎", 20, "17817318859", url)

        binding.handlers = this

    }
    
    fun onClickFriend(view: View) {
        when (view.id) {
            R.id.btn1 -> Toast.makeText(this, "點選了按鈕1", Toast.LENGTH_SHORT).show()
            R.id.btn2 -> Toast.makeText(this, "點選了按鈕2", Toast.LENGTH_SHORT).show()

        }
    }
}

方式4:方法引用

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

        <variable
            name="handlers"
            type="com.example.jetpack.MainActivity.MyClickHandlers" />
    </data>

   <Button
            android:id="@+id/btn1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="改變name屬性"
            android:onClick="@{handlers.onClickChangeName}"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvPhoneNum" />

        <Button
            android:id="@+id/btn2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{handlers::onClickChangAage}"
            android:text="改變age屬性"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/btn1" />

在 Activity 內部新建一個類來宣告 onClickChangeName() 和 onClickChangAage() 事件相應的回撥方法:

class MainActivity : AppCompatActivity() {

    lateinit var user: User

    lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)

        val url =
            "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3796319594,2802761532&fm=26&gp=0.jpg"

        user = User("趙麗穎", 20, "17817318859", url)
        binding.user = user

        binding.handlers = MyClickHandlers()

    }

    inner class MyClickHandlers {
        fun onClickChangeName(v: View?) {
            user.name = "趙麗穎2"
            binding.user=user
        }

        fun onClickChangAage(v: View?) {
            user.age = 18
            binding.user=user
        }
    }
}

方式5:方法引用

把回撥方法單獨寫到一個介面。

  <variable
            name="handlers"
            type="com.example.jetpack.UserClickListener" />

 <Button
            android:id="@+id/btn1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="按鈕"
            android:onClick="@{handlers.userClicked}"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvPhoneNum" />

        <Button
            android:id="@+id/btn2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{handlers::userClicked}"
            android:text="按鈕"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/btn1" />

interface UserClickListener {

    fun userClicked(view: View?)
}
class MainActivity : AppCompatActivity(), UserClickListener {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

       val  binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)

        val url =
            "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3796319594,2802761532&fm=26&gp=0.jpg"

        val user = User("趙麗穎", 20, "17817318859", url)
        binding.user = user

        binding.handlers = this

    }

    override fun userClicked(view: View?) {
        Toast.makeText(this, "方法引用",Toast.LENGTH_SHORT).show();
    }
}

方式6:監聽繫結(重要)

將物件直接傳回點選方法中。

   <variable
            name="handlers"
            type="com.example.jetpack.MainActivity.MyClickHandlers" />
    <Button
            android:id="@+id/btn1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{()->handlers.showUser(user)}"
            android:text="按鈕"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvPhoneNum" />
class MainActivity : AppCompatActivity() {


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding =
            DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)

        val url =
            "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3796319594,2802761532&fm=26&gp=0.jpg"
        var user = User("趙麗穎", 20, "17817318859", url)
        binding.user = user

        binding.handlers = MyClickHandlers()

    }

    inner class MyClickHandlers {
        fun showUser(user: User) {
            Toast.makeText(this@MainActivity, user.name, Toast.LENGTH_SHORT).show()
        }

    }
}

相關文章