Data Binding (中文文檔)

Data Binding 類庫(kù)

這篇文檔將教你如何運(yùn)用 Data Binding 類庫(kù)來(lái)編寫聲明試布局葱绒,并且盡量減少粘合代碼對(duì)你的應(yīng)用邏輯和布局上的綁定失球。
Data Binding 是一種靈活和廣泛兼容的類庫(kù),它是一個(gè)支持庫(kù)黔牵,因此你可以在任何 Android 2.1(API level 7+) 以上的設(shè)備 使用。
為了使用 Data Binding,Android Gradle 插件版本必須為 1.5.0-alpha1 或以上牧愁,查看 如何升級(jí)你的 Gradle 插件

構(gòu)建環(huán)境

為了獲取 Data Binding磨确,去 Android SDK manager 下載 它的支持庫(kù)。
在你的應(yīng)用 module 的 build.gradle 添加 dataBinding 來(lái)讓你的應(yīng)用支持 Data Binding邓了。
用以下代碼片段來(lái)配置 Data Binding:

android {
    ....
    dataBinding {
        enabled = true
    }
}

若你有一個(gè)應(yīng)用 module 用了一個(gè)依賴了 Data Binding 的類庫(kù)照宝,也一樣要在該 module 中配置開啟 Data Binding厕鹃。
另外,如果想使用 Data Binding汗茄,你們你的 Android Studio 版本必須等于或大于 1.3。


Data Binding 布局文件

編寫你的第一個(gè) Data Binding 表達(dá)式

Data Binding 的布局文件有一點(diǎn)不一樣瞳腌,它以 layout 標(biāo)簽作為根標(biāo)簽,并且有一個(gè)data 元素和 一個(gè) view 元素作為子標(biāo)簽挑宠,這個(gè) view 元素就是你沒有使用 Data Binding 時(shí)該有的布局文件。以下是一個(gè)例子:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"/>
   </LinearLayout>
</layout>

data 標(biāo)簽下的 variable 是你在這個(gè) Data Binding 布局文件中有可能使用到的對(duì)象碎浇。

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

布局中使用 @{} 語(yǔ)法來(lái)包裹 variable 中的對(duì)象屬性,在下面例子中苟穆,TextViewtext 屬性的值用 userfirstName 屬性來(lái)替代剖膳。

<TextView android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@{user.firstName}"/>

數(shù)據(jù)對(duì)象

現(xiàn)在讓我們假設(shè)你有一個(gè)普通的 Java 對(duì)象(POJO)User

public class User {
   public final String firstName;
   public final String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
}

這個(gè)對(duì)象屬性(final 修飾)是不可變的,如果你的數(shù)據(jù)對(duì)象只提供只讀權(quán)限并且之后不會(huì)再去修改的話仑濒,這種做法很普遍。我們也可以用 JavaBeans 對(duì)象來(lái)表示:

public class User {
   private final String firstName;
   private final String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
   public String getFirstName() {
       return this.firstName;
   }
   public String getLastName() {
       return this.lastName;
   }
}

從數(shù)據(jù)綁定的角度來(lái)看喉酌,這兩個(gè)類是等價(jià)的。TextViewandroid:text 屬性值會(huì)通過表達(dá)式 @{user.firstName} 來(lái)獲取第一個(gè)類中的 fistName 字段值相速,活著獲取第二個(gè)類中的 getFirstName() 方法返回的值。另外旺隙,如果 firstName() 方法存在的話也是可以獲取到值的。

綁定數(shù)據(jù)

默認(rèn)情況下,將根據(jù)布局文件的名稱生成一個(gè)綁定類摘昌,將其轉(zhuǎn)換為 Pascal 格式并將 Binding 作為其后綴。上面的布局文件是名稱
main_activity.xml 锦秒,因此生成的綁定類是 MainActivityBinding旅择。這個(gè)類將布局屬性(例如用戶變量)綁定到布局的視圖中,并知道如何通過表達(dá)式來(lái)賦值柱蟀。創(chuàng)建綁定類的最簡(jiǎn)單方式是在視圖 inflate 的時(shí)候:

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
   User user = new User("Test", "User");
   binding.setUser(user);
}

完成了!運(yùn)行這個(gè)應(yīng)用术瓮,你會(huì)在界面中看到測(cè)試的 User。另外撬讽,你可以通過一些方
式獲取綁定類:

MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());

如果你在 ListView 或者 RecyclerView 中使用數(shù)據(jù)綁定電話,你可以通過一些方式獲取綁定類:

ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);

事件處理

數(shù)據(jù)綁定允許你編寫表達(dá)式來(lái)處理從視圖中分派的事件(例如 onClick)烘豌。除少數(shù)例外,事件屬性名稱由偵聽器中的方法名稱來(lái)確定标锄。例如料皇,View.OnLongClickListener 有一個(gè) onLongClick()方法,所以這個(gè)事件的屬性是 android:onLongClick优质。有以下兩種方式來(lái)處理一個(gè)事件盆赤。

  • 方法引用:在表達(dá)式中,可以引用符合偵聽器方法簽名的方法淑际。 當(dāng)表達(dá)式被評(píng)估為方法引用時(shí),數(shù)據(jù)綁定將方法引用和所有者對(duì)象包裝在偵聽器中锄贼,并將該偵聽器設(shè)置在目標(biāo)視圖上。 如果表達(dá)式被評(píng)估為 null冯键,則數(shù)據(jù)綁定不會(huì)創(chuàng)建偵聽器,而是設(shè)置一個(gè)空的偵聽器改化。
  • 監(jiān)聽器綁定:當(dāng)事件發(fā)生時(shí),lambda 表達(dá)式將被評(píng)估燥爷。 數(shù)據(jù)綁定總是會(huì)在視圖上創(chuàng)建一個(gè)監(jiān)聽器前翎。 當(dāng)事件被發(fā)送時(shí),監(jiān)聽器將評(píng)估 lambda 表達(dá)式立宜。

方法引用

事件可以直接綁定到處理的方法中,類似于 android:onClick 可以作為 Activity 的一個(gè)方法一樣灯帮。與 View#onClick 屬性相比,一個(gè)主要的優(yōu)點(diǎn)是表達(dá)式在編譯時(shí)被處理腻贰,因此如果方法不存在或者它的簽名不正確,就會(huì)收到編譯時(shí)錯(cuò)誤宾巍。

方法引用和監(jiān)聽器綁定的主要區(qū)別在于實(shí)際的監(jiān)聽器實(shí)現(xiàn)是在綁定數(shù)據(jù)時(shí)創(chuàng)建的,而不是在事件觸發(fā)時(shí)創(chuàng)建的选浑。

要將事件分配給其處理程序,請(qǐng)使用常規(guī)綁定表達(dá)式隧膘,其值是要調(diào)用的方法名稱疹吃。 例如歉摧,如果你的數(shù)據(jù)對(duì)象有兩個(gè)方法:

public class MyHandlers {
    public void onClickFriend(View view) { ... }
}

綁定表達(dá)式可以為 View 分配一個(gè)點(diǎn)擊監(jiān)聽器:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="handlers" type="com.example.MyHandlers"/>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"
           android:onClick="@{handlers::onClickFriend}"/>
   </LinearLayout>
</layout>

請(qǐng)注意,表達(dá)式中方法的簽名必須與監(jiān)聽器對(duì)象中方法的簽名完全匹配膝但。

監(jiān)聽器綁定

監(jiān)聽器綁定是事件發(fā)生時(shí)運(yùn)行的綁定表達(dá)式。類似于方法引用泳炉,但是允許你運(yùn)行任意的數(shù)據(jù)綁定表達(dá)式。 此功能適用于 Gradle 2.0 版及更高版本的 Android Gradle 插件刨肃。

在方法引用中,方法的參數(shù)必須與事件偵聽器的參數(shù)匹配盔然。 在監(jiān)聽器綁定中,只有你的返回值必須與監(jiān)聽器的期望返回值相匹配(除非它返回值為 void )站绪。 例如魂挂,您可以有一個(gè)具有以下方法的 Presenter 類:

public class Presenter {
    public void onSaveClick(Task task){}
}

然后你可以綁定你的點(diǎn)擊事件到你的類中锰蓬,例如:

<?xml version="1.0" encoding="utf-8"?>
  <layout xmlns:android="http://schemas.android.com/apk/res/android">
      <data>
          <variable name="task" type="com.android.example.Task" />
          <variable name="presenter" type="com.android.example.Presenter" />
      </data>
      <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
          <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
          android:onClick="@{() -> presenter.onSaveClick(task)}" />
      </LinearLayout>
  </layout>

監(jiān)聽器僅可以允許用 lambda 表達(dá)式作為根元素赦抖。 當(dāng)表達(dá)式中有回調(diào)時(shí),數(shù)據(jù)綁定會(huì)自動(dòng)為事件創(chuàng)建必要的偵聽器和注冊(cè)表要尔。 當(dāng)視圖觸發(fā)事件時(shí),數(shù)據(jù)綁定將評(píng)估給定的表達(dá)式还惠。 就像在常規(guī)的綁定表達(dá)式一樣蚕键,當(dāng)這些監(jiān)聽器表達(dá)式被評(píng)估的時(shí)候,你仍然可以獲取數(shù)據(jù)綁定的空值和保證線程安全誊爹。

請(qǐng)注意,在上面的例子中椎镣,我們沒有定義傳入 onClick(android.view.View) 的視圖參數(shù)冷守。 監(jiān)聽器綁定為監(jiān)聽器參數(shù)提供了兩個(gè)選擇:您可以忽略該方法的所有參數(shù)或?qū)⑵淙棵?如果您想要命名參數(shù),則可以在表達(dá)式中使用它們充活。 例如,上面的表達(dá)式可以寫成:

android:onClick="@{(view) -> presenter.onSaveClick(task)}"

或者如果你想使用表達(dá)式中的參數(shù),可以像下面這樣:

public class Presenter {
    public void onSaveClick(View view, Task task){}
}
 android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"

你可以在 lambda 表達(dá)式中使用多個(gè)參數(shù):

public class Presenter {
    public void onCompletedChanged(Task task, boolean completed){}
}
<CheckBox 
        android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />

如果正在偵聽的事件返回值不是 void赘淮,則表達(dá)式必須返回相同類型的值。 例如低剔,如果你想監(jiān)聽長(zhǎng)按事件,你的表達(dá)式應(yīng)該返回布爾值猜欺。

public class Presenter {
    public boolean onLongClick(View view, Task task){}
}
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"

如果由于空對(duì)象而無(wú)法評(píng)估表達(dá)式,Data Binding 將返回該類型的默認(rèn) Java 值赋荆。 例如,引用類型為 null嫉你,int0幽污,booleanfalse 等等簸搞。

如果您需要使用帶謂詞的表達(dá)式(例如三元),則可以使用 void 作為符號(hào)。

android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"

避免復(fù)雜的監(jiān)聽器

監(jiān)聽器表達(dá)式非常強(qiáng)大闰集,可以讓你的代碼變得非常容易閱讀。 另一方面蝠检,包含復(fù)雜表達(dá)式的監(jiān)聽器也會(huì)使您的布局難以閱讀和維護(hù)饲梭。這些表達(dá)式應(yīng)該像從 UI 中傳遞可用數(shù)據(jù)到回調(diào)方法一樣簡(jiǎn)單憔涉。你應(yīng)該從偵聽器表達(dá)式調(diào)用的回調(diào)方法內(nèi)實(shí)現(xiàn)業(yè)務(wù)邏輯。
存在一些專門的單擊事件處理程序国旷,它需要除 android:onClick 之外的其他屬性以避免沖突。 已創(chuàng)建了以下屬性以避免這種沖突:

Class Listener Setter Attribute
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut

布局文件細(xì)節(jié)

Imports

數(shù)據(jù)元素內(nèi)可以使用零個(gè)或多個(gè) import 元素吧雹。 這些就像在 Java 中一樣可以輕松地引用類到你的布局文件中雄卷。

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

現(xiàn)在 View 類可以在你的綁定表達(dá)式中使用了。

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

如果類名有沖突的話揣钦,其中一個(gè)類則需起別名了。

<import type="android.view.View"/>
<import type="com.example.real.estate.View" alias="Vista"/>

現(xiàn)在宇姚,在布局文件中浑劳,Vista 被當(dāng)作 com.example.real.estate.View 引入,View 被當(dāng)作 android.view.View 引入。 導(dǎo)入的類型可以用作變量和表達(dá)式中的類型引用:

<data>
    <import type="com.example.User"/>
    <import type="java.util.List"/>
    <variable name="user" type="User"/>
    <variable name="userList" type="List&lt;User&gt;"/>
</data>

注意:Android Studio 尚未處理導(dǎo)入滓窍,因此自動(dòng)導(dǎo)入變量在你的的 IDE 中可能無(wú)法完成吏夯。 你的應(yīng)用程序仍然可以正常編譯,你可以通過在變量定義中使用完全限定的名稱來(lái)解決 IDE 的這個(gè)問題跺嗽。

<TextView
   android:text="@{((User)(user.connection)).lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

當(dāng)在表達(dá)式中引用靜態(tài)字段和方法時(shí)植兰,也可以使用導(dǎo)入的類型:

<data>
    <import type="com.example.MyStringUtils"/>
    <variable name="user" type="com.example.User"/>
</data>
…
<TextView
   android:text="@{MyStringUtils.capitalize(user.lastName)}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

就像在 Java 文件中一樣,java.lang.* 會(huì)被自動(dòng)導(dǎo)入筒繁。

Variables

data 元素內(nèi)可以使用任意的 variable。 每個(gè)變量表示可以在布局中設(shè)置的屬性呕缭,以用于布局文件中的綁定表達(dá)式。

<data>
    <import type="android.graphics.drawable.Drawable"/>
    <variable name="user"  type="com.example.User"/>
    <variable name="image" type="Drawable"/>
    <variable name="note"  type="String"/>
</data>

變量類型在編譯時(shí)被檢查佳谦,所以如果一個(gè)變量實(shí)現(xiàn)了 Observable 或者一個(gè) observable collection啥刻,那么它應(yīng)該被反映在類型中。 如果變量是沒有實(shí) Observable 接口的基類或接口映跟,那么它將不會(huì)被觀察!

當(dāng)不同的配置(例如橫向或縱向)有不同的布局文件時(shí)荸镊,變量將被合并张惹。 這些布局文件之間不得有沖突的變量定義。

生成的綁定類將為每個(gè)描述的變量設(shè)置一個(gè) settergetter 方法拧额。 變量將采用默認(rèn)的 Java 值,直到調(diào)用 setter 為止 恭垦。對(duì)于引用類型為 null,對(duì)于 int0玄柏,對(duì)于 booleanfalse 等绍坝。

自定義綁定類的名字

默認(rèn)情況下椎咧,根據(jù)布局文件的名稱生成一個(gè)綁定類,以大寫字母開頭脚牍,刪除下劃線(_)并之后的單詞首字母大寫,然后添加后綴 Binding作谚。 這個(gè)類將被放置在模塊包下的數(shù)據(jù)綁定包中三娩。 例如,布局文件 contact_item.xml 將生成 ContactItemBinding妹懒。 如果模塊包是 com.example.my.app雀监,那么它將被放置在 com.example.my.app.databinding 中。

綁定類可以通過調(diào)整 data 元素的 class 屬性來(lái)重命名或放置在不同的包中眨唬。 例如:

<data class="ContactItem">
    ...
</data>

這會(huì)在模塊包中的數(shù)據(jù)綁定包中生成綁定類 ContactItem会前。 如果該類應(yīng)該在模塊包中的其他包中生成,則可以用“.”作為前綴:

<data class=".ContactItem">
    ...
</data>

在這種情況下斋攀,直接在模塊包中生成了 ContactItem闺魏。 如果提供完整的包,則可以使用任意的包:

<data class="com.example.ContactItem">
    ...
</data>

Includes

通過在屬性中使用應(yīng)用程序命名空間和變量名稱胰柑,變量可以從包含的布局中傳遞到包含的布局的綁定中:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </LinearLayout>
</layout>

在這里,name.xmlcontact.xml 布局文件中都必須有一個(gè) user 變量嘁扼。

數(shù)據(jù)綁定不支持 include 作為 merge 元素的直接子元素。 例如栅哀,不支持以下布局:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <merge>
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </merge>
</layout>

表達(dá)式語(yǔ)言

共同特征

表達(dá)式語(yǔ)言看起來(lái)很像 Java 表達(dá)式。 這些是一樣的:

  • Mathematical + - / * %
  • String concatenation +
  • Logical && ||
  • Binary & | ^
  • Unary + - ! ~
  • Shift >> >>> <<
  • Comparison == > < >= <=
  • instanceof
  • Grouping ()
  • Literals - character, String, numeric, null
  • Cast
  • Method calls
  • Field access
  • Array access []
  • Ternary operator ?:
    例如:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

缺少的操作

你在 Java 中使用的一些表達(dá)式語(yǔ)法并不支持綁定操作。

  • this
  • super
  • new
  • 明確的泛型調(diào)用

空的合并運(yùn)算符

空合并運(yùn)算符 ?? 會(huì)選擇左邊的運(yùn)算結(jié)果(如果它不是 null 的話)或右邊的運(yùn)算結(jié)果(如果它是 null 的話)。

android:text="@{user.displayName ?? user.lastName}"

這在功能上等同于:

android:text="@{user.displayName != null ? user.displayName : user.lastName}"

屬性引用

當(dāng)一個(gè)表達(dá)式引用一個(gè)類的屬性時(shí)焊刹,它對(duì)字段错忱,setterObservableFields 使用相同的格式。

android:text="@{user.lastName}"

避免空指針異常

生成的數(shù)據(jù)綁定代碼會(huì)自動(dòng)檢查空值并避免空指針異常死遭。 例如睡蟋,在表達(dá)式 @ {user.name} 中傍菇,如果 user 為 null,則 user.name 將被分配其默認(rèn)值(null)。 如果引用 user.age,其中age是一個(gè) int失息,那么它將默認(rèn)為0。

集合

通用的集合:數(shù)組,列表诗轻,SparseArray 养晋,map,可以使用 [] 運(yùn)算符來(lái)方便地訪問钩述。

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

字符串文本

在屬性值兩邊使用單引號(hào)時(shí)颓屑,則表達(dá)式中使用雙引號(hào):

android:text='@{map["firstName"]}'

也可以使用雙引號(hào)來(lái)包圍屬性值。 這樣做時(shí),字符串文字應(yīng)該使用單引號(hào) ' 或者反引號(hào)(`)指蚁。

android:text="@{map[`firstName`}"
android:text="@{map['firstName']}"

資源

使用正常的語(yǔ)法可以將資源作為表達(dá)式的一部分進(jìn)行訪問:

android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

格式字符串和復(fù)數(shù)可以通過提供參數(shù)來(lái)評(píng)估:

android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"

當(dāng)一個(gè)復(fù)數(shù)有多個(gè)參數(shù)時(shí)傍衡,所有參數(shù)都應(yīng)該傳遞:

 Have an orange
 Have %d oranges
 android:text="@{@plurals/orange(orangeCount, orangeCount)}"

一些資源需要明確的類型評(píng)估:

Type Normal Reference Expression Reference
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
color int @color @color
ColorStateList @color @colorStateList

Data Objects

任何普通的舊 Java 對(duì)象(POJO)都可以用于數(shù)據(jù)綁定宏怔,但修改 POJO 不會(huì)導(dǎo)致 UI
更新。 數(shù)據(jù)綁定的真正威力在于通過給你的數(shù)據(jù)對(duì)象在數(shù)據(jù)改變時(shí)提供通知泼橘。 有三種不同的數(shù)據(jù)更改通知機(jī)制椎木,Observable objects, observable fields, observable collections.

當(dāng)這些可觀察的數(shù)據(jù)對(duì)象被綁定到 UI鲤脏,并且數(shù)據(jù)對(duì)象的屬性改變時(shí)刨秆,UI 將被自動(dòng)更新。

Observable Objects

實(shí)現(xiàn) Observable 接口的類將允許綁定單個(gè)偵聽器附加到綁定對(duì)象姊氓,以偵聽該對(duì)象上所有屬性的更改瘦锹。

Observable 接口具有添加和刪除偵聽器的功能蝙斜,但通知是由開發(fā)者決定的猴贰。 為了簡(jiǎn)化開發(fā)交汤,創(chuàng)建了基類 BaseObservable圈浇,以實(shí)現(xiàn)偵聽器注冊(cè)機(jī)制庶弃。 數(shù)據(jù)類實(shí)現(xiàn)者仍然負(fù)責(zé)通知屬性的更改麻裁。 這是通過給 getter 分配一個(gè) Bindable 注解并通知 setter 來(lái)完成的诈悍。

private static class User extends BaseObservable {
   private String firstName;
   private String lastName;
   @Bindable
   public String getFirstName() {
       return this.firstName;
   }
   @Bindable
   public String getLastName() {
       return this.lastName;
   }
   public void setFirstName(String firstName) {
       this.firstName = firstName;
       notifyPropertyChanged(BR.firstName);
   }
   public void setLastName(String lastName) {
       this.lastName = lastName;
       notifyPropertyChanged(BR.lastName);
   }
}

Bindable 注解在編譯期間在 BR 類中生成一個(gè)條目祸轮。 BR 類文件將在模塊包中生成。 如果數(shù)據(jù)類的基類沒有改變侥钳,Observable 接口可以使用方便的 PropertyChangeRegistry 來(lái)實(shí)現(xiàn)适袜,以有效地存儲(chǔ)和通知監(jiān)聽器。

ObservableFields

創(chuàng)建 Observable 類需要做一點(diǎn)工作舷夺,所以想要節(jié)省時(shí)間或擁有很少屬性的開發(fā)人員可以使用 ObservableField 及其同胞 ObservableBoolean苦酱,ObservableByteObservableChar给猾,ObservableShort疫萤,ObservableIntObservableLong耙册,ObservableFloat给僵,ObservableDoublObservableParcelableObservableFields 是具有單個(gè)字段的獨(dú)立的可觀察對(duì)象详拙。 原始版本在訪問操作期間避免裝箱和取消裝箱。 要使用蔓同,請(qǐng)?jiān)跀?shù)據(jù)類中創(chuàng)建一個(gè)公共 final 字段:

private static class User {
   public final ObservableField<String> firstName =
       new ObservableField<>();
   public final ObservableField<String> lastName =
       new ObservableField<>();
   public final ObservableInt age = new ObservableInt();
}

就是這樣饶辙!要訪問該值,請(qǐng)使用 set 和 get 方法訪問:

user.firstName.set("Google");
int age = user.age.get();

Observable Collections

一些應(yīng)用程序使用更多的動(dòng)態(tài)結(jié)構(gòu)來(lái)保存數(shù)據(jù)斑粱,觀察集合允許對(duì)這些數(shù)據(jù)對(duì)象進(jìn)行鍵值訪問弃揽。當(dāng)鍵是引用類型(如 String)時(shí),ObservableArrayMap 非常有用则北。

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);

在布局文件中矿微,map 通過字符串鍵來(lái)訪問:

<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap&lt;String, Object&gt;"/>
</data>
…
<TextView
   android:text='@{user["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
<TextView
   android:text='@{String.valueOf(1 + (Integer)user["age"])}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

當(dāng)鍵是整形是,可以使用 ObservableArrayList:

ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);

在布局中尚揣,列表可以通過索引來(lái)訪問:

<data>
    <import type="android.databinding.ObservableList"/>
    <import type="com.example.my.app.Fields"/>
    <variable name="user" type="ObservableList&lt;Object&gt;"/>
</data>
…
<TextView
   android:text='@{user[Fields.LAST_NAME]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
<TextView
   android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

生成綁定類

生成的綁定類將布局變量與布局中的視圖鏈接起來(lái)涌矢。 如前所述,綁定的名稱和包可能是自定義的快骗。 生成的綁定類都擴(kuò)展了 ViewDataBinding娜庇。

創(chuàng)建

應(yīng)該在 inflate 之后立即創(chuàng)建綁定塔次,以確保 View 層次結(jié)構(gòu)不受干擾。 有幾種方法可以綁定到布局名秀。 最常見的是在綁定類中使用靜態(tài)方法励负。inflate 方法 inflate View 層次結(jié)構(gòu),一步到位匕得。 有一個(gè)更簡(jiǎn)單的版本继榆,只需要一個(gè) LayoutInflater 和一個(gè) ViewGroup

MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);

如果布局使用不同的機(jī)制 inflate,它可能會(huì)被分開綁定:

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

有時(shí)綁定不能預(yù)先知道汁掠。 在這種情況下裕照,可以使用 DataBindingUtil 類創(chuàng)建綁定:

ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId, parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);

Views With IDs

將在布局中為每個(gè)視圖生成一個(gè)公開的 final 字段。 該綁定在視圖層次結(jié)構(gòu)上執(zhí)行單個(gè)傳遞调塌,提取帶有 ID 的視圖晋南。 這個(gè)機(jī)制可以比調(diào)用多個(gè)視圖的 findViewById 更快。 例如:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"
   android:id="@+id/firstName"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"
  android:id="@+id/lastName"/>
   </LinearLayout>
</layout>

會(huì)生成帶有一下字段的綁定類:

public final TextView firstName;
public final TextView lastName;

IDs 不像沒有數(shù)據(jù)綁定那樣必要羔砾,但是仍然有一些情況下代碼需要訪問視圖负间。

變量

每個(gè)變量將被賦予訪問器方法。

<data>
    <import type="android.graphics.drawable.Drawable"/>
    <variable name="user"  type="com.example.User"/>
    <variable name="image" type="Drawable"/>
    <variable name="note"  type="String"/>
</data>

會(huì)在綁定類中生成 setter 和 getter 方法:

public abstract com.example.User getUser();
public abstract void setUser(com.example.User user);
public abstract Drawable getImage();
public abstract void setImage(Drawable image);
public abstract String getNote();
public abstract void setNote(String note);

ViewStubs

ViewStub 與普通視圖有點(diǎn)不同姜凄。 他們從不可見的時(shí)候開始政溃,當(dāng)他們要么變得可見時(shí),要么被明確告知 inflate 時(shí)态秧,他們通過 inflate 另一個(gè)布局來(lái)取代布局董虱。

由于 ViewStub 本質(zhì)上從視圖層次中消失,所以綁定對(duì)象中的視圖也必須消失以允許收集申鱼。 因?yàn)橐晥D是 final 的愤诱,所以 ViewStubProxy 對(duì)象代替了ViewStub,當(dāng) ViewStub 存在時(shí)捐友,開發(fā)人員可以訪問 ViewStub淫半,并且在 ViewStub被 inflate 時(shí)也可以訪問被 inflate 的視圖。

當(dāng) inflate 另一個(gè)布局時(shí)匣砖,必須為新的布局建立綁定科吭。因此,ViewStubProxy 必須偵聽 ViewStubViewStub.OnInflateListener 并在此時(shí)建立綁定猴鲫。由于只有一個(gè)可以存在对人,ViewStubProxy 允許開發(fā)者在建立綁定之后設(shè)置一個(gè) OnInflateListener 對(duì)象。

高級(jí)綁定

動(dòng)態(tài)變量

有時(shí)拂共,特定的綁定類將不被知道牺弄。 例如,針對(duì)任意布局的 RecyclerView.Adapter 將不知道具體的綁定類匣缘。 它仍然必須在 onBindViewHolder(VH,int) 期間分配綁定值猖闪。

在這個(gè)例子中鲜棠,RecyclerView 綁定的所有布局都有一個(gè) item 變量。BindingHolder 有一個(gè)返回 ViewDataBinding 基類的 getBinding 方法培慌。

public void onBindViewHolder(BindingHolder holder, int position) {
   final T item = mItems.get(position);
   holder.getBinding().setVariable(BR.item, item);
   holder.getBinding().executePendingBindings();
}

立即綁定

當(dāng)變量或 observable 變化時(shí)豁陆,綁定將被安排在下一幀之前改變。但有時(shí)候吵护,綁定必須立即執(zhí)行盒音。要強(qiáng)制執(zhí)行,請(qǐng)使用 executePendingBindings() 方法馅而。

后臺(tái)線程

只要不是集合祥诽,就可以在后臺(tái)線程中更改數(shù)據(jù)模型。數(shù)據(jù)綁定將在評(píng)估時(shí)本地化每個(gè)變量/字段瓮恭,以避免任何并發(fā)問題雄坪。


屬性設(shè)置

每當(dāng)綁定值發(fā)生變化時(shí),生成的綁定類必須使用綁定表達(dá)式在視圖上調(diào)用setter方法屯蹦。 數(shù)據(jù)綁定框架可以自定義調(diào)用哪個(gè)方法來(lái)設(shè)置值维哈。

自動(dòng)的設(shè)置器

對(duì)于一個(gè)屬性,數(shù)據(jù)綁定將試圖找到設(shè)置屬性的方法登澜。屬性的命名空間并不重要阔挠,只有屬性名稱本身才重要。例如脑蠕,與 TextView 的屬性 android:text 相關(guān)聯(lián)的表達(dá)式將查找 setText(String)购撼。 如果表達(dá)式返回 int,那么數(shù)據(jù)綁定將搜索一個(gè) setText(int) 方法谴仙。請(qǐng)注意讓表達(dá)式返回正確的類型迂求,如果需要的話就進(jìn)行轉(zhuǎn)換。即使給定名稱不存在任何屬性狞甚,數(shù)據(jù)綁定也可以工作锁摔。 然后,您可以使用數(shù)據(jù)綁定輕松地為任何 setter 創(chuàng)建屬性哼审。 例如,support 庫(kù)中的 DrawerLayout 沒有任何屬性孕豹,但是有很多 setter涩盾。 您可以使用自動(dòng)設(shè)置器來(lái)使用其中的一個(gè)。

<android.support.v4.widget.DrawerLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:scrimColor="@{@color/scrim}"
    app:drawerListener="@{fragment.drawerListener}"/>

重命名設(shè)置器

一些屬性的設(shè)置器會(huì)與名稱不匹配励背。 對(duì)于這些方法春霍,一個(gè)屬性可能通過 BindingMethods 注解與設(shè)置器關(guān)聯(lián)。 這必須與一個(gè)類相關(guān)聯(lián)叶眉,每個(gè)重命名的方法一個(gè)包含一個(gè) BindingMethod 注解址儒。例如芹枷,android:tint 屬性確實(shí)與 setImageTintList(ColorStateList) 關(guān)聯(lián),而不是 setTint莲趣。

@BindingMethods({
       @BindingMethod(type = "android.widget.ImageView",
                      attribute = "android:tint",
                      method = "setImageTintList"),
})

開發(fā)人員不太可能需要重命名設(shè)置器鸳慈, 安卓框架已經(jīng)為這些屬性實(shí)現(xiàn)了。

自定義設(shè)置器

一些屬性需要自定義綁定邏輯喧伞。 例如走芋,android:paddingLeft 屬性沒有關(guān)聯(lián)的設(shè)置器。 相反潘鲫,setPadding(eft, top, right, bottom) 存在翁逞。 使用 BindingAdapter 注釋的靜態(tài)綁定適配器方法允許開發(fā)人員自定義如何調(diào)用屬性的設(shè)置器。

安卓屬性已經(jīng)創(chuàng)建了 BindingAdapters溉仑。 例如挖函,這里是 paddingLeft

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
   view.setPadding(padding,
                   view.getPaddingTop(),
                   view.getPaddingRight(),
                   view.getPaddingBottom());
}

綁定適配器對(duì)其他類型的自定義非常有用。 例如浊竟,一個(gè)自定義的加載器可以被調(diào)用脫機(jī)線程來(lái)加載一個(gè)圖像怨喘。當(dāng)發(fā)生沖突時(shí),開發(fā)人員創(chuàng)建的綁定適配器將覆蓋數(shù)據(jù)綁定默認(rèn)適配器逐沙。您也可以讓適配器接收多個(gè)參數(shù)哲思。

@BindingAdapter({"bind:imageUrl", "bind:error"})
public static void loadImage(ImageView view, String url, Drawable error) {
   Picasso.with(view.getContext()).load(url).error(error).into(view);
}
<ImageView app:imageUrl="@{venue.imageUrl}"
app:error="@{@drawable/venueError}"/>

如果 imageUrl 和 error 都用于 ImageView 且 imageUrl 是字符串,并且 error 是 drawable吩案,則將調(diào)用此適配器棚赔。
自定義名稱空間在匹配過程中被忽略。
也可以為 android 命名空間編寫適配器徘郭。
綁定適配器方法可以選擇在其處理程序中使用舊值靠益。 采用新舊值的方法,應(yīng)該把屬性的所有舊的值放在第一位残揉,然后是新的值:

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int oldPadding, int newPadding) {
   if (oldPadding != newPadding) {
       view.setPadding(newPadding,
                       view.getPaddingTop(),
                       view.getPaddingRight(),
                       view.getPaddingBottom());
   }
}

事件處理器只能用于只有一個(gè)抽象方法的接口或抽象類胧后。例如:

@BindingAdapter("android:onLayoutChange")
public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,
       View.OnLayoutChangeListener newValue) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        if (oldValue != null) {
            view.removeOnLayoutChangeListener(oldValue);
        }
        if (newValue != null) {
            view.addOnLayoutChangeListener(newValue);
        }
    }
}

當(dāng)一個(gè)監(jiān)聽器有多個(gè)方法時(shí),它必須被分成多個(gè)監(jiān)聽器抱环。例如壳快,View.OnAttachStateChangeListener 有兩個(gè)方法:onViewAttachedToWindow()onViewDetachedFromWindow()。然后我們必須創(chuàng)建兩個(gè)接口來(lái)區(qū)分它們的屬性和處理器镇草。

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
    void onViewDetachedFromWindow(View v);
}

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
    void onViewAttachedToWindow(View v);
}

因?yàn)楦囊粋€(gè)偵聽器也會(huì)影響另一個(gè)偵聽器眶痰,所以我們必須有三個(gè)不同的綁定適配器,一個(gè)用于每個(gè)屬性梯啤,另一個(gè)用于兩個(gè)竖伯,它們都應(yīng)該被設(shè)置。

@BindingAdapter("android:onViewAttachedToWindow")
public static void setListener(View view, OnViewAttachedToWindow attached) {
    setListener(view, null, attached);
}

@BindingAdapter("android:onViewDetachedFromWindow")
public static void setListener(View view, OnViewDetachedFromWindow detached) {
    setListener(view, detached, null);
}

@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"})
public static void setListener(View view, final OnViewDetachedFromWindow detach,
        final OnViewAttachedToWindow attach) {
    if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
        final OnAttachStateChangeListener newListener;
        if (detach == null && attach == null) {
            newListener = null;
        } else {
            newListener = new OnAttachStateChangeListener() {
                @Override
                public void onViewAttachedToWindow(View v) {
                    if (attach != null) {
                        attach.onViewAttachedToWindow(v);
                    }
                }

                @Override
                public void onViewDetachedFromWindow(View v) {
                    if (detach != null) {
                        detach.onViewDetachedFromWindow(v);
                    }
                }
            };
        }
        final OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view,
                newListener, R.id.onAttachStateChangeListener);
        if (oldListener != null) {
            view.removeOnAttachStateChangeListener(oldListener);
        }
        if (newListener != null) {
            view.addOnAttachStateChangeListener(newListener);
        }
    }
}

上面的例子比正常情況稍微復(fù)雜一點(diǎn),因?yàn)橐晥D對(duì)偵聽器使添加和刪除七婴,而不是對(duì) View.OnAttachStateChangeListener 使用set方法祟偷。 android.databinding.adapters.ListenerUtil 類有助于跟蹤以前的監(jiān)聽器,以便它們可以在綁定適配器中被移除打厘。通過使用 @TargetApi(VERSION_CODES.HONEYCOMB_MR1) 注解接口 OnViewDetachedFromWindowOnViewAttachedToWindow修肠,數(shù)據(jù)綁定代碼生成器知道只應(yīng)在 API 12 或以上的設(shè)備上調(diào)用 addOnAttachStateChangeListener(View.OnAttachStateChangeListener) 來(lái)運(yùn)行運(yùn)行偵聽器。


轉(zhuǎn)換器

對(duì)象轉(zhuǎn)換

從綁定表達(dá)式返回一個(gè)對(duì)象時(shí)婚惫,將從自動(dòng)氛赐,重命名和自定義的設(shè)置器中選擇一個(gè)設(shè)置器。 該對(duì)象將被轉(zhuǎn)換為所選設(shè)置器的參數(shù)類型先舷。
這對(duì)于那些使用 ObservableMaps 來(lái)保存數(shù)據(jù)的開發(fā)者來(lái)說是很方便的艰管。例如:

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

userMap 返回一個(gè)對(duì)象,該對(duì)象將被自動(dòng)轉(zhuǎn)換為在 setText(CharSequence) 中找到的參數(shù)類型蒋川。 當(dāng)參數(shù)類型可能混淆時(shí)牲芋,開發(fā)者需要在表達(dá)式中輸入。

自定義轉(zhuǎn)換

有時(shí)轉(zhuǎn)換應(yīng)該在特定類型之間自動(dòng)進(jìn)行捺球。 例如缸浦,設(shè)置 background 時(shí):

<View
   android:background="@{isError ? @color/red : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

在這里,背景需要一個(gè) Drawable氮兵,但是顏色是一個(gè)整數(shù)裂逐。每當(dāng)一個(gè) Drawable 被判斷該返回一個(gè)整數(shù)時(shí),該整形應(yīng)該被轉(zhuǎn)換成一個(gè) ColorDrawable泣栈。 這個(gè)轉(zhuǎn)換是通過一個(gè)帶有 BindingConversion 注解的靜態(tài)方法完成的:

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
   return new ColorDrawable(color);
}

請(qǐng)注意卜高,轉(zhuǎn)換只發(fā)生在設(shè)置器級(jí)別,所以不允許混合類型南片,如下所示:

<View
   android:background="@{isError ? @drawable/error : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

Android Studio 支持?jǐn)?shù)據(jù)綁定

Android Studio 支持?jǐn)?shù)據(jù)綁定代碼的許多代碼編輯功能掺涛。例如,它支持?jǐn)?shù)據(jù)綁定表達(dá)式的以下功能:

  • 語(yǔ)法高亮
  • 表達(dá)式語(yǔ)法錯(cuò)誤的提示
  • XML代碼完成
  • 包括導(dǎo)航(如導(dǎo)航到聲明)和快速文檔的參考

注意:如果沒有錯(cuò)誤疼进,則數(shù)組和一般類型(如 Observable 類)可能會(huì)顯示錯(cuò)誤薪缆。

預(yù)覽窗格顯示數(shù)據(jù)綁定表達(dá)式的默認(rèn)值(如果提供的話)。在以下示例摘錄布局XML文件中的元素時(shí)伞广,預(yù)覽窗格將在 TextView 中顯示 PLACEHOLDER 默認(rèn)文本值拣帽。

<TextView android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="@{user.firstName, default=PLACEHOLDER}"/>

如果需要在項(xiàng)目設(shè)計(jì)階段顯示默認(rèn)值,則還可以使用工具屬性而不是默認(rèn)表達(dá)式值嚼锄,如 Design Time Layout Attributes 中所述诞外。


原文地址:https://developer.android.google.cn/topic/libraries/data-binding/index.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌趾撵,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件既们,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡正什,警方通過查閱死者的電腦和手機(jī)啥纸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)婴氮,“玉大人斯棒,你說我怎么就攤上這事≈骶” “怎么了荣暮?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)罩驻。 經(jīng)常有香客問我穗酥,道長(zhǎng),這世上最難降的妖魔是什么惠遏? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任砾跃,我火速辦了婚禮,結(jié)果婚禮上节吮,老公的妹妹穿的比我還像新娘抽高。我一直安慰自己,他們只是感情好透绩,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布翘骂。 她就那樣靜靜地躺著,像睡著了一般渺贤。 火紅的嫁衣襯著肌膚如雪雏胃。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天志鞍,我揣著相機(jī)與錄音瞭亮,去河邊找鬼。 笑死固棚,一個(gè)胖子當(dāng)著我的面吹牛统翩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播此洲,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼厂汗,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了呜师?” 一聲冷哼從身側(cè)響起娶桦,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后衷畦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體栗涂,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年祈争,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了斤程。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡菩混,死狀恐怖忿墅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情沮峡,我是刑警寧澤疚脐,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站帖烘,受9級(jí)特大地震影響亮曹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜秘症,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一照卦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧乡摹,春花似錦役耕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至板熊,卻和暖如春框全,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背干签。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工津辩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人容劳。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓喘沿,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親竭贩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蚜印,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容