Jetpack(二)DataBinding從入門到靈活運用

目錄

前言

DataBinding其實并不是一個新東西,15年 Google IO 大會就開始推了,一線大廠在比較早就開始使用了,隨著Jetpack架構組件的發展,使用DataBinding來進行我們的項目開發已經是大勢所趨

那么什么是 DataBinding 呢,字面就是幫我們實現視圖數據綁定的工具,現在主流的前端框架(React、Vue)都用到了這種思想。它可以幫助我們實現,直接操作數據就可以自動改變視圖。而不是像過去那樣,首先要findViewById拿到組件的引用,當數據發生變化后,我們還需要主動調用組件的相關方法(如setText)進行賦值,才能將數據的改變體現到視圖中。DataBinding就是幫助我們實現MVVM架構的最基本的技術支持

導入DataBinding

導入DataBinding非常簡單,只需要在app下的gradle文件中配置即可(如下所示)

//build.gradle(:app)
android {
        ....
    buildFeatures {
        dataBinding = true
    }
}

可以這里有的人會好奇,導入Databinding竟然如此簡單,也不用添加任何依賴,這是為什么?

Android Studio中是依靠Gradle來管理項目的,在創建一個項目時,從開始創建一直到創建完畢,整個過程是需要執行很多個Gradle task的,這些task有很多是系統預先幫我們定義好的,比如build task,clean task等,DataBinding相關的task也是系統預先幫我們定義好的,但是默認情況下,DataBinding相關的task在task列表中是沒有的,因為我們沒有開啟dataBinding,但是一旦我們通過 buildFeatures{dataBinding= true}的方式開啟DataBinding之后,DataBinding相關的task就會出現在task列表中,每當我們執行編譯操作時,就會執行這些DataBinding Task, 這些task的作用就是檢查并生成相關DataBinding代碼

DataBinding基本使用

創建一個基本的xml文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

</LinearLayout>

使用快捷鍵option+enter可以快速生成binding layout

自動轉化后的格式,以根標記 layout 開頭,后跟 data 元素和 view 根元素

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">


    </LinearLayout>
</layout>

這里再推薦一款插件,可以簡化DataBinding的轉換操作并支持和ViewModel和與之關聯的layout文件的跳轉,可以提升開發時的效率,節省時間,感興趣的自己點下面的鏈接進一步了解

https://plugins.jetbrains.com/plugin/9271-databinding-support

現在先建立一個簡單的數據模型

public class News {
    private String title;
    private String content;

    public News(String title, String content) {
        this.title = title;
        this.content = content;
    }

    public String getTitle() {
        return title == null ? "" : title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content == null ? "" : content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

我們再寫一個簡單的布局

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <import type="com.geekholt.databinding.bean.News" />

        <variable
            name="news"
            type="com.geekholt.databinding.bean.News" />

    </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="@{news.title}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{news.content}" />
    </LinearLayout>
</layout>

這里主要有兩點需要關注:

  1. data 中的 news 變量描述了可在此布局中使用的屬性

  2. 布局中的表達式使用“@{}”語法寫入特性屬性中

數據綁定與整體更新

系統會為每個布局文件生成一個綁定類。默認情況下,類名稱基于布局文件的名稱,它會轉換為 Pascal 大小寫形式并在末尾添加 Binding 后綴。以上布局文件名為 activity_main.xml,因此生成的對應類為 ActivityMainBinding。此類包含從布局屬性(例如,news 變量)到布局視圖的所有綁定,并且知道如何為綁定表達式指定值

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ActivityMainBinding mainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);
    News news = new News("Jetpack","一起學習DataBinding");
    mainBinding.setNews(news);
}

這樣就完成了News數據和activity_main.xml的綁定,不僅省略了findViewById的操作,同時也沒有針對單個的View去進行賦值,當數據發生變化的時候,只需要重新調用mainBinding.setNews(news)就可以讓與數據模型相關的控件都進行刷新

當然,我們也可以針對單個控件進行更新,我們通過一個點擊事件,觸發news.setContent("一起學習LiveData"),去更改單個屬性

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    News news;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding mainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        news = new News("Jetpack", "一起學習DataBinding");
        mainBinding.setNews(news);
    }

    public void submit(View view) {
        news.setContent("一起學習LiveData");
    }
}

這時候,發現頁面并沒有發生任何變化,這是為什么呢?

使用BaseObservable更新單個屬性

BaseObservable 是一個基類,需要我們的數據 bean 繼承這個基類,然后給屬性的 get 方法
添加@Bindable 這個注解,然后在屬性的 set 方法中添加上 更新某個字段的方法notifyPropertyChanged

public class News extends BaseObservable {
    private String title;
    private String content;

    public News(String title, String content) {
        this.title = title;
        this.content = content;
    }

    @Bindable
    public String getTitle() {
        return title == null ? "" : title;
    }

    public void setTitle(String title) {
        this.title = title;
        notifyPropertyChanged(BR.title);
    }

    @Bindable
    public String getContent() {
        return content == null ? "" : content;
    }

    public void setContent(String content) {
        this.content = content;
        notifyPropertyChanged(BR.content);
    }
}

Obserable接口有一個自動添加和移除監聽器的機制,但是通知數據更新取決于開發者。為了使開發變得簡單,谷歌創建了BaseObserable這個基礎類來集成監聽器注冊機制。通過給getter方法添加Bindable注解,通知setter方法。

使用ObservableField來更新單個屬性

ObservableFields 是一個對屬性添加 DataBinding 更新功能的代理類,針對不同的數據類型有不同類型的 ObservableFieldsObservableBooleanObservableByte ObservableCharObservableShortObservableIntObservableLongObservableFloatObservableDoubleObservableParcelable 等。

//News.java
public class News {
    public ObservableField<String> title = new ObservableField<>();
    public ObservableField<String> content = new ObservableField<>();

    public News(String title, String content) {
        this.title.set(title);
        this.content.set(content);
    }
}
//MainActivity.java
public void submit(View view) {
    news.content.set("一起學習LiveData");
}

綁定事件與方法

我們也可以在視圖中為點擊事件綁定方法

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding mainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        mainBinding.setClick(new ClickProxy());
    }

    public class ClickProxy {
        public void submit(String content) {
            //todo something
        }
    }
}

通過android:onClick="@{()->click.submit()}"進行方法綁定,且這種方式支持直接在xml中給方法傳參,如下所示

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="click"
            type="com.geekholt.databinding.MainActivity.ClickProxy" />

        <variable
            name="content"
            type="String" />

    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@={content}" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:onClick="@{()->click.submit(content)}" />


    </LinearLayout>
</layout>

這里有一個細節提一下:

前面我們進行數據綁定用的都是@{}這種形式,這種綁定方式叫做單向數據綁定;而這里我們給EditText綁定數值用到了@={},中間多了一個等號,這種綁定方式稱之為雙向數據綁定,即當數據發送變化時,頁面也發生變化,當我們在EditText中輸入文字,頁面發生變化時,所對應的數據也會跟著變化

綁定適配器

自動查找屬性所對應的方法

目前為止,我們都是對系統控件(如TextView、Button、EditText)進行數據綁定,那如果自定義控件我們如何給它設定屬性并進行數據綁定呢?

public class CustomImageView extends AppCompatImageView {
    public CustomImageView(Context context) {
        super(context);
    }

    public CustomImageView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void setImageUrl(String url) {
        Glide.with(getContext()).load(url).into(this);
    }

}

ImageView本身并不能直接設置網絡url并加載,當我們在xml中app:imageUrl="@{imageUrl}" 時,數據綁定庫會自動幫我們在CustomImageView中查找setImageUrl(string)方法,并調用執行

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

    <data>

        <variable
            name="imageUrl"
            type="String" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <com.geekholt.databinding.CustomImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:imageUrl="@{imageUrl}" />

    </LinearLayout>
</layout>

如果我們想在xml中調用方法名不是以set開頭的方法怎么辦呢?

@BindingMethods 自定義方法名稱

Databinding庫給我們提供了@BindingMethods注解,使用方式如下所示,主要包括typeattributemethod三個部分

@BindingMethods({@BindingMethod(type = AppCompatImageView.class, attribute = "imageChange", method = "imageChange")})
public class CustomImageView extends AppCompatImageView {
    public CustomImageView(Context context) {
        super(context);
    }

    public CustomImageView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void setImageUrl(String url) {
        Glide.with(getContext()).load(url).into(this);
    }

    public void imageChange(String url) {
        Toast.makeText(getContext(), "imageChange:" + url, Toast.LENGTH_SHORT).show();
    }
}
<?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">

    <data>

        <variable
            name="imageUrl"
            type="String" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <com.geekholt.databinding.CustomImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:imageChange="@{imageUrl}"
            app:imageUrl="@{imageUrl}" />

    </LinearLayout>
</layout>

這樣一來,每當imageView的url發生變化的時候,就會彈出一個Toast

@BindingAdapter 自定義邏輯

接下里要說的部分,將會是本文的重點,也是將DataBinding應用于企業級項目中的一個關鍵

很多人對DataBinding的第一印象,似乎都是在xml中編寫邏輯代碼,就像下面這樣:

android:text="@{String.valueOf(index + 1)}"

android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
  
android:transitionName='@{"image_" + id}'
  
android:text="@{@string/nameFormat(firstName, lastName)}"
  
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
  
android:text="@{map[`firstName`]}"

需要注意的是這些都不代表著最佳實踐,只是說明有這樣的功能,支持這樣的用法。而且它有一個很大的弊端,相信很多剛入門DataBinding的人都遇到過找不到binding文件的錯,然后被勸退,原因無外乎就是表達式寫錯、variable的類路徑不對或者沒import相應的類(比如View)等等,都與復雜的表達式有關系,而DataBinding報錯也不太智能,有時不能準確定位,所以建議不要在xml里進行復雜的數據綁定

那我們如何來給xml “減負” 呢?

這時候就要用到@BindingAdapter注解

上文中我們編寫的setImageUrl方法,實際上不僅僅可以在自定義View中使用,我們可以通過@BindingAdapter作用于ImageView以及ImageView的所有子類,如下所示:

public class CommonBindingAdapter {

    @BindingAdapter(value = {"imageUrl", "placeHolder"})
    public static void loadUrl(ImageView view, String url, Drawable placeHolder) {
        Glide.with(view.getContext()).load(url).placeholder(placeHolder).into(view);
    }

    @BindingAdapter(value = {"visible"})
    public static void visible(View view, boolean visible) {
        view.setVisibility(visible ? View.VISIBLE : View.GONE);
    }
}

如果 ImageView 對象同時使用了 imageUrlplaceHolder,并且 imageUrl 是字符串,placeHolderDrawable,就會調用適配器。如果你希望在設置了任意屬性時調用適配器,則可以將適配器的可選 requireAll標志設置為 false

注意:數據綁定庫在匹配時會忽略自定義命名空間

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

    <data>

        <variable
            name="imageUrl"
            type="String" />

        <variable
            name="placeholder"
            type="android.graphics.drawable.Drawable" />

        <variable
            name="visible"
            type="Boolean" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <ImageView
            imageUrl="@{imageUrl}"
            placeHolder="@{placeholder}"
            visible="@{visible}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </LinearLayout>
</layout>

看到這里,是不是覺得這個@BindingAdapter非常的強大呢?將邏輯判斷盡可能的抽取到BIndingAdapter中,我相信你的代碼可維護性會提高一個層次

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,627評論 2 380

推薦閱讀更多精彩內容

  • 依賴 在使用databinding的module的build.gradle中設置 開始簡單的數據綁定 1.我們先定...
    小_馬_哥閱讀 355評論 0 1
  • 簡介 DataBinding即數據綁定,是Google為Android提供的一種MVVM實現方式,目前也是Andr...
    三雒閱讀 1,021評論 0 1
  • 本文將介紹以下databinding的基本使用方式,主要包括數據的綁定,事件的監聽,自定義監聽方法等 介紹 Dat...
    若無初見閱讀 561評論 0 2
  • 久違的晴天,家長會。 家長大會開好到教室時,離放學已經沒多少時間了。班主任說已經安排了三個家長分享經驗。 放學鈴聲...
    飄雪兒5閱讀 7,550評論 16 22
  • 今天感恩節哎,感謝一直在我身邊的親朋好友。感恩相遇!感恩不離不棄。 中午開了第一次的黨會,身份的轉變要...
    迷月閃星情閱讀 10,597評論 0 11