Data Binding 詳解(四)-生成的綁定類

知是行之始抬驴,行是知之成筒占。
文章配套的 Demohttps://github.com/muyi-yang/DataBindingDemo
Demo 支持 Java 和 Kotlin 雙語言,master 分支為 Java 語言代碼淹冰,kotlin 分支為 Kotlin 語言代碼峭火。

Data Binding 生成用于訪問布局變量和視圖的綁定類,它將布局變量與布局中的視圖鏈接起來牍鞠。默認情況下,類的名稱基于布局文件的名稱评姨,將其轉(zhuǎn)換為Pascal大小寫并向其添加Binding后綴难述。比如布局文件名是 activity_main.xml 相應生成的類 ActivityMainBinding,也可以自定義綁定類的名稱和包吐句。 所有以 <data> 為根標簽的布局都會生成綁定類胁后,都繼承自 ViewDataBinding
類。這個類包含從布局屬性(例如嗦枢,聲明的變量)到布局視圖的所有綁定攀芯,并且知道如何為綁定表達式分配值,有興趣的同學可以看看生成后的類是怎么實現(xiàn)的文虏。

創(chuàng)建綁定對象

創(chuàng)建綁定類的對象有兩種方式侣诺,一種是使用 DataBindingUtil 工具類,一種是直接使用綁定類的靜態(tài)方法來獲取氧秘。

在 Activity 中我們一般使用 DataBindingUtil 工具類來獲取綁定對象年鸳,因為它有一個 setContentView 方法,里面調(diào)用了 Activity 的 setContentView 方法并返回了綁定類對象敏储,比如:

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

也可以通過 DataBindingUtil 工具類的 inflate 方法獲茸栊恰:

ActivityMainBinding binding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.activity_main, null, false);

但在其他地方(Fragment、ListView 或 RecyclerView 等)我們一般會直接使用綁定類的靜態(tài)方法來獲取已添,比如:

LayoutCelebrityItemBinding binding = LayoutCelebrityItemBinding.inflate(inflater);
// 或者
LayoutCelebrityItemBinding binding = LayoutCelebrityItemBinding.inflate(inflater, viewGroup, false);

有時你可能需要使用老方式 inflate 一個布局,你可以這樣獲取綁定對象:

View viewRoot = LayoutInflater.from(this).inflate(layoutId, parent, attachToParent)
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot)

有時無法事先知道綁定類型滥酥,布局可能是動態(tài)的更舞。 在這種情況下,可以使用 DataBindingUtil 類創(chuàng)建綁定坎吻,如下面的代碼片段所示:

View viewRoot = LayoutInflater.from(this).inflate(layoutId, parent, attachToParent)
ViewDataBinding binding = DataBindingUtil.bind(viewRoot)
// 或者
ViewDataBinding binding = DataBindingUtil.inflate(getLayoutInflater(), layoutId, parent, attachToParent);

至此我們了解了兩個獲取綁定類對象的兩種方式缆蝉,可以根據(jù)不同的場景運用不同的方式,其實綁定類中的 inflate 方法的最終也是調(diào)用 DataBindingUtil 的 inflate 方法,bind 方法也是一樣刊头,有興趣的同學可以閱讀一下生成的綁定類代碼黍瞧。

帶 ID 的 View

Data Binding 在綁定類中為每個在布局中具有 ID 的 View 創(chuàng)建不可變字段。例如原杂,Data Binding 從以下布局創(chuàng)建類型為 TextView 的 tvInfo印颤,類型為 Button 的 btnBinding 字段:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
     ...
    <android.support.constraint.ConstraintLayout 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">

        <TextView
            android:id="@+id/tv_info"
            ... />
        <Button
            android:id="@+id/btn_binding"
            ... />
        <Button
            android:id="@+id/btn_list"
            ... />
    </android.support.constraint.ConstraintLayout>
</layout>

在綁定類中已經(jīng)生成了相應的字段,我們不在需要調(diào)用 findViewById () 方法獲取穿肄,可以直接從綁定類中獲饶昃帧:

ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.tvInfo.setText("我是使用Data Binding的Demo");

變量

Data Binding 為布局中聲明的每個變量生成訪問方法。 例如咸产,下面的布局在綁定類中為 user矢否、 index 變量生成 setter 和 getter 方法:

<!--activity_user.xml-->
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:bind="http://schemas.android.com/tools">
    <data>
        <import type="com.example.databindingdemo.bean.UserInfo" />
        ...
        <variable
            name="user"
            type="UserInfo" />

        <variable
            name="index"
            type="int" />
     ...
    </data>
    ...
</layout>

在獲取了綁定類對象的地方可以通過 setter 方法設置數(shù)據(jù),通過 getter 方法獲取數(shù)據(jù):

public class UserActivity extends AppCompatActivity {

    private ActivityUserBinding binding;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_user);
        ...
        binding.setUser(info);
        binding.setIndex(1);
        ...
    }
}

ViewStub的使用

ViewStub 與普通 View 不同脑溢,它開始時是一個不可見的 View僵朗。 當設置可見或者調(diào)用 inflate() 方法時,它們會在布局中通過 inflate 另一個布局來替換自己屑彻。

因為 ViewStub 中的布局實際上在 View 層次結(jié)構(gòu)中并沒有加載衣迷,所以在綁定對象中也不能直接創(chuàng)建 ViewStub 中的布局對象,必須要在使用的時候再創(chuàng)建酱酬,所以綁定類中使用 ViewStubProxy 對象取代了 ViewStub壶谒,你可以使用它來訪問 ViewStub,當 ViewStub 被創(chuàng)建和加載后你可以通過它來訪問具體的布局結(jié)構(gòu)膳沽。以下為布局:

<!--activity_binding.xml-->
<?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">
    ...
        <ViewStub
            android:id="@+id/vs_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout="@layout/layout_bar" />
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="@{()->activity.showViewStub()}"
            android:text="@string/show_stub" />
    ...
</layout>

<!--layout_bar.xml-->
<?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="resId" type="int" />
        <variable name="name" type="String" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <ImageView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_margin="10dp"
            android:src="@drawable/default_mini_avatar"
            app:image="@{resId}" />
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_margin="10dp"
            android:gravity="center"
            android:text="@{name, default=@string/default_name}"
            android:textSize="20sp" />
    </LinearLayout>
</layout>

這里在布局中聲明了一個 ViewStub汗菜,并為它設置 layout_bar.xml 布局,同時聲明了一個 Button 并為它設置了點擊事件(調(diào)用 showViewStub() 方法)挑社。

當你想使用 ViewStub 中的布局時陨界,你需要獲取到里面的綁定類對象,你可以向 ViewStubProxy 設置一個 OnInflateListener 監(jiān)聽器痛阻,然后在監(jiān)聽器回調(diào)中獲取綁定類菌瘪。比如:

public class BindingClassActivity extends AppCompatActivity {
    private ActivityBindingBinding binding;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_binding);
        binding.vsBar.setOnInflateListener(new ViewStub.OnInflateListener() {
            @Override
            public void onInflate(ViewStub stub, View inflated) {
                LayoutBarBinding vsBarBinding = DataBindingUtil.bind(inflated);
                vsBarBinding.setName("木易");
                vsBarBinding.setResId(R.drawable.head);
            }
        });
       ...
    }
    ...
    public void showViewStub() {
        ViewStub viewStub = binding.vsBar.getViewStub();
        if (viewStub != null) {
            viewStub.inflate();
        }
    }
    ...
}

可以看到 showViewStub() 方法中調(diào)用 ViewStub 的 inflate() 方法,當 Button 被點擊時就會觸發(fā)布局的加載阱当,加載完成后會觸發(fā) OnInflateListener 的回調(diào)俏扩,然后在回調(diào)方法中通過 DataBindingUtil.bind(inflated)獲取到了 ViewStub 中的布局綁定類,繼而進行數(shù)據(jù)綁定弊添。

立即綁定

當變量或 observable 對象數(shù)據(jù)發(fā)生變化時录淡,數(shù)據(jù)綁定將在 View 的下一幀刷新之前更改。 但是油坝,有時必須立即執(zhí)行數(shù)據(jù)綁定嫉戚。 若要強制執(zhí)行刨裆,可以使用 executePendingBindings ()方法。一般情況不需要這么做彬檀。

動態(tài)變量

有時候你的布局文件是動態(tài)的帆啃,比如在一個 RecyclerView 中展現(xiàn)一系列新聞內(nèi)容,有些內(nèi)容是帶圖片的窍帝,有些是純文本努潘,這時我們可以采用多個 item 布局文件來呈現(xiàn)不一樣的 UI 效果。比如:

public class NewsAdapter extends RecyclerView.Adapter {
    ...
    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
        ViewDataBinding binding;
        if (viewType == VIEW_TYPE_TEXT) {
            binding = LayoutNewsItemTextBinding.inflate(inflater, viewGroup, false);
        } else {
            binding = LayoutNewsItemPictureBinding.inflate(inflater, viewGroup, false);
        }
        return new NewsViewHolder(binding.getRoot(), binding);
    }
    ...
}

這里通過類型判斷盯桦,分別使用了 layout_news_item_text.xmllayout_news_item_picture.xml 布局文件慈俯。這兩個布局文件 UI 展現(xiàn)不一樣,但需要的數(shù)據(jù)類型都是 NewsInfo:

<!--layout_news_item_picture.xml-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable
            name="info"
            type="com.example.databindingdemo.bean.NewsInfo" />
    </data>
    ...
</layout>

<!--layout_news_item_text.xml-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable
            name="info"
            type="com.example.databindingdemo.bean.NewsInfo" />
    </data>
    ...
</layout>

這種情況下我們在 RecyclerView.Adapter 的 onBindViewHolder() 方法中就無法準確的知道綁定類類型拥峦,但是我們?nèi)稳灰獮槠浣壎〝?shù)據(jù)贴膘,我們可以這樣做:

public class NewsAdapter extends RecyclerView.Adapter {
    private List<NewsInfo> data = new ArrayList<>();
    ...
    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
        NewsViewHolder holder = (NewsViewHolder) viewHolder;
        ViewDataBinding binding = holder.binding;
        binding.setVariable(BR.info, data.get(position));
    }
    ...
}

在這里我們并沒有獲取具體的綁定類,而是獲取了 ViewDataBinding 類略号,它是一個抽象類刑峡,是所有綁定類的父類。它提供了一個 setVariable(int variableId, Object value) 方法(第一個參數(shù)為布局中聲明的綁定變量 ID玄柠,第二個參數(shù)為要綁定的數(shù)據(jù))突梦,通過這個方法我們可以動態(tài)的為布局中的變量綁定相應的數(shù)據(jù)。

BR 是 Data Binding 自動生成的資源 ID 文件羽利,它包含所有的數(shù)據(jù)綁定變量的 ID宫患,類似于 Android 的 R 文件。在上面例子中这弧,RB.info 是布局中 info 變量的 ID娃闲。

后臺線程

你可以在后臺線程中更改數(shù)據(jù),只要它不是一個集合匾浪。Data Binding 會在計算時將每個變量/字段在各個線程中做一份數(shù)據(jù)拷貝皇帮,以避免同步問題。

自定義綁定類名稱

默認情況下 Data Binding 將根據(jù)布局文件的名稱生成綁定類蛋辈,以大寫字母開頭属拾,刪除下劃線,駝峰格式命名冷溶,并添加 Binding 后綴渐白。該類放在模塊包下的 databinding 包中。例如挂洛,布局文件 layout_custom.xml 生成 LayoutCustomBinding類礼预,放在 com.example.databindingdemo.databinding包中。

通過調(diào)整 data 標簽的 class 屬性虏劲,可以重命名綁定類或?qū)⒔壎惙旁诓煌陌型兴帷@纾韵虏季衷诋斍澳K的 databinding 包中生成綁定類 MyCustom:

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

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>
</layout>

效果圖:
image.png

你可以通過在類名前加一個句點來使生成綁定類在模塊包中:

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

效果圖:
image.png

你還可以在類名前使用完整的包名柒巫。下面的示例在 com.custom 包中創(chuàng)建 MyCustom 綁定類:

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

效果圖:
image.png

此篇到這里就結(jié)束了励堡,可以查看下一篇 Data Binding 詳解(五)-綁定適配器

如果你覺得文章有幫助到你堡掏,記得點個喜歡以表支持应结,同時歡迎你的指正和建議。十分感謝泉唁!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鹅龄,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子亭畜,更是在濱河造成了極大的恐慌扮休,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拴鸵,死亡現(xiàn)場離奇詭異玷坠,居然都是意外死亡,警方通過查閱死者的電腦和手機劲藐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門八堡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人聘芜,你說我怎么就攤上這事兄渺。” “怎么了汰现?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵挂谍,是天一觀的道長。 經(jīng)常有香客問我服鹅,道長凳兵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任企软,我火速辦了婚禮庐扫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘仗哨。我一直安慰自己形庭,他們只是感情好,可當我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布厌漂。 她就那樣靜靜地躺著萨醒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪苇倡。 梳的紋絲不亂的頭發(fā)上富纸,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天囤踩,我揣著相機與錄音,去河邊找鬼晓褪。 笑死堵漱,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的涣仿。 我是一名探鬼主播勤庐,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼好港!你這毒婦竟也來了愉镰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤钧汹,失蹤者是張志新(化名)和其女友劉穎丈探,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體崭孤,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡类嗤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了辨宠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片遗锣。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖嗤形,靈堂內(nèi)的尸體忽然破棺而出精偿,到底是詐尸還是另有隱情,我是刑警寧澤赋兵,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布笔咽,位于F島的核電站,受9級特大地震影響霹期,放射性物質(zhì)發(fā)生泄漏叶组。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一历造、第九天 我趴在偏房一處隱蔽的房頂上張望甩十。 院中可真熱鬧,春花似錦吭产、人聲如沸侣监。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽橄霉。三九已至,卻和暖如春邑蒋,著一層夾襖步出監(jiān)牢的瞬間姓蜂,已是汗流浹背按厘。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留覆糟,地道東北人刻剥。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓遮咖,卻偏偏與公主長得像滩字,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子御吞,可洞房花燭夜當晚...
    茶點故事閱讀 45,691評論 2 361