Data Binding 數(shù)據(jù)綁定(二)

本文建立在有一定使用 DataBinding 經驗的基礎之上,若還不熟悉 DataBinding 的用法妨退,請參考前一篇博客Data Binding 數(shù)據(jù)綁定(一)赠涮。

在學習 DataBinding 的過程中华弓,參考 Google 官方的 DataBinding 示例 Demo芭挽,自己寫了一個 DataBindingPractice Demo滑废,用于練手。整個工程采用 MVP 架構 + DataBinding袜爪,歡迎 star策严、fork 和溝通交流。

本文介紹了 DataBinding 一些稍微高級的用法饿敲,主要包括以下四部分內容:

  1. DataBinding 中的數(shù)據(jù)對象(Data Objects)
  2. DataBinding 中生成綁定類(Generated Binding)
  3. DataBinding 中的屬性設置(Attribute Setters)
  4. DataBinding 中的轉換器(Converters)

數(shù)據(jù)對象(Data Objects)

  1. 任何普通的 Java 對象(POJO)都可以被 DataBinding 所使用,但是改變 POJO 對象的屬性值并不會更新 UI 界面的顯示逛绵。DataBinding 真正強大之處在于怀各,它可以讓你的數(shù)據(jù)對象具有通知 UI 界面對象的屬性已經發(fā)生改變的能力。

  2. 有三種不同的數(shù)據(jù)變化通知機制:

  • Observable objects
  • observable fields
  • observable collection
  1. 如果這其中的一種數(shù)據(jù)對象被綁定到 UI 界面上术浪,當數(shù)據(jù)對象的屬性值發(fā)生變化時瓢对,UI 界面會自動更新。

可觀察對象(Observable Objects)

  1. 一個類如果實現(xiàn)了 Observable 接口胰苏,那么 DataBinding 則會將一個 listener 綁定到該類上硕蛹,就可以監(jiān)聽該類對象中的屬性的變化。Observable 接口具有添加和移除 listener 的機制硕并,但是否通知則取決于開發(fā)者法焰。
  2. 為了使開發(fā)更容易,DataBinding 提供了一個名為 BaseObservable 的基類倔毙,它用于實現(xiàn) listener 注冊機制埃仪。
  3. 實現(xiàn) Observable 的類負責什么時候通知該類的屬性發(fā)生了變化,只需要在類的 getter 方法上添加 Bindable 注解陕赃,并在 setter 方法中通知更新即可卵蛉。
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 注解標記過的 getter 方法會在 BR class 文件中生成一個入口,BR class 文件是在 Module 的包下傻丝,BR.classR.class 的功能類似甘有。

可觀察屬性(ObservableFields)

  1. 創(chuàng)建一個 Observable 類還是需要一些工作量的,如果開發(fā)者不想花費太多的時間和精力葡缰,或者沒有太多的屬性需要觀察監(jiān)聽的話亏掀,那么可以使用 ObservableField,或者它的子類: ObservableBoolean运准,ObservableByte幌氮,ObservableCharObservableShort胁澳, ObservableInt该互,ObservableLongObservableFloat韭畸,ObservableDoubleObservableParcelable宇智。
  2. ObservableField 是包含 Observable Object 對象的單一字段。原始版本避免了在獲取過程中做打包和解包的操作胰丁。在數(shù)據(jù)對象中使用 ObservableField随橘,需要創(chuàng)建一個 public 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();
}

可以通過 set 方法和 get 方法存取數(shù)據(jù)

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

可觀察集合(Observable Collections)

  1. 一些應用會使用動態(tài)的結構持有數(shù)據(jù)锦庸,可觀察容器類允許使用鍵值對的形式來存取數(shù)據(jù)机蔗。
  2. 當鍵值對中的鍵是應用型數(shù)據(jù)(比如:String)時,ObservableArrayMap 是非常有用的甘萧。
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);

在布局文件中萝嘁,也可以通過使用 String 類型的鍵來獲取到相應的值。

<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap<String, Object>"/>
  </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"/>
  1. 當鍵值對中的鍵是 Integer 型的扬卷,ObservableArrayList 則是非常有用的牙言。
ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);

在布局文件中,也可以通過使用 Integer 類型的鍵來獲取到相應的值怪得。

<data>
    <import type="android.databinding.ObservableList"/>
    <import type="com.example.my.app.Fields"/>
    <variable name="user" type="ObservableList<Object>"/>
  </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"/>
  ```


## 生成綁定類(Generated Binding)
1. 生成的綁定類通過布局文件中的 Views 和布局文件中的變量聯(lián)系起來咱枉。
2. 如之前所討論的那樣,綁定類的名稱和所在的位置都是可以自定義的徒恋。
3. 生成的所有的綁定類都是 `ViewDataBinding` 的子類蚕断。

### 構建(Creating)
1. 綁定類在 View Inflate 之后立即被創(chuàng)建,以確保在布局中的表達式被綁定到視圖之前入挣,View 的層次結構不會被打亂基括。
2. 有幾種方式綁定布局文件,最常用的是使用 Binding 類中的靜態(tài)方法來綁定類财岔。`inflate` 方法調用一次就可以 Inflate View 并將 View 綁定到 Binding 類上风皿。
3. 還有一個更加簡單的方法河爹,只需要一個 `LayoutInflater` 對象和一個 `viewGroup` 對象。
``` Java
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
  1. 如果布局使用另外不同的機制來 inflate桐款,則可以單獨綁定:
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
  1. 有時候咸这,Binding 類的名字不得而知,在這種情況下魔眨,則可以使用 DataBindingUtil 生成該 Binding 類:
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
    parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);

帶 ID 的 View(Views with IDs)

  1. 使用 DataBinding 庫的布局文件媳维,其中的每個帶 ID 的 View,編譯以后遏暴,都會在該布局文件對應的 Binding 類中生成一個被 public final 修飾的屬性侄刽,Data Binding 會做一個簡單的賦值,在 Binding 類中保存對應 ID 的 View朋凉。
  2. 通過這種機制獲取控件比通過 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>

在生成的 Binding 類中會有對應的字段:

public final TextView firstName;
public final TextView lastName;

如果使用 DataBinding 庫的話,在布局文件中為控件設置 Id 不是必須的杂彭,但是在某些情況下墓毒,在代碼中通過 Id 得到控件還是有必要的。

變量(Variables)

布局文件中的每個變量都會生成對應的存取方法亲怠,如:

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

在該布局文件對應的 Binding 類中所计,都會生成對應的存取方法,如下所示:

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

  1. ViewStub 和普通的 View 相比是不一樣的团秽。它們最開始是不可見的主胧,當它們被設置為可見的或者調用 inflate 方法時,ViewStub 會被替換為另外一個控件或布局习勤。
  2. 因為最開始的時候讥裤,ViewStub 在布局層級中不可見,Binding 類中對應的控件也應該被移除姻报,以便回收。
  3. 因為在 Binding 類中间螟,所有 View 對應的屬性都是被 final 字段修飾的吴旋,所以一個 ViewStubProxy 對象代替該 ViewStub,當 ViewStub 被設置為可見的或調用 inflate 方法之后厢破,開發(fā)者可以通過此代理類 ViewStubProxy 得到對應的 ViewStub荣瑟。
  4. inflate 一個新的布局時,必須為新的布局創(chuàng)建新的 Binding 類摩泪。所以 ViewStubProxy 必須監(jiān)聽 ViewStub 的 ViewStub.OnInflateListener笆焰,當 ViewStub 被 inflate 的時候,則建立一個新的 Binding 類见坑。
  5. 因為 ViewStub 只能設置一個 OnInflateListener嚷掠,開發(fā)者可以為 ViewStubProxy 設置一個 OnInflateListener捏检,在 Binding 類被建立以后,OnInflateListener 就會被觸發(fā)不皆。
    代碼如下所示:
<layout>
  ...
  <ViewStub
      android:id="@+id/viewStub"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout="@layout/layout_view_stub"/>
  ...
</layout>
mBinding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
            @Override
            public void onInflate(ViewStub stub, View inflated) {
                LayoutViewStubBinding mStubBinding = DataBindingUtil.findBinding(inflated);
                mStubBinding.tvViewStub.setOnClickListener((view1 -> showClickToast()));
            }
          });

高級綁定(Advanced Binding)

動態(tài)變量(Dynamic Variables)

  1. 有時候贯城,一些 Binding 類不為人所知。比如霹娄,在 RecyclerView.Adapter 中可以用來處理不同的布局能犯,此時便不知道該 Binding 類具體是什么類型的。而在 onBindViewHolder(VH, int) 方法中犬耻,ViewHolder 中的 Binding 類又必須被賦值踩晶。
  2. 在這個例子中,所有 RecyclerView 涉及到的布局中枕磁,都有一個 item 的變量渡蜻。
  3. Adapter 所使用的 ViewHolder 中有一個 getBinding 的方法得到一個 ViewDataBinding 的 Binding 類。如下所示:
public void onBindViewHolder(BindingHolder holder, int position) {
   final T item = mItems.get(position);
   holder.getBinding().setVariable(BR.item, item);
   holder.getBinding().executePendingBindings();
}

立即綁定(Immediate Binding)

當變量或者 observable 變量發(fā)生變化時透典,會在下一幀才觸發(fā) Binding晴楔,但是有時候需要立即 Binding,可以通過 executePendingBindings() 方法立即觸發(fā) Binding峭咒。

后臺線程

只要不是集合類型的數(shù)據(jù)税弃,你可以在后臺線程中更改數(shù)據(jù)。Data Binding 會在計算時將每個變量/字段在各個線程中做一份數(shù)據(jù)拷貝凑队,以避免同步問題则果。

屬性設置(Attribute Setters)

當一個屬性值發(fā)生變化時,生成的 Binding 類必須調用該控件對應 data binding 表達式的 setter 方法漩氨。Data Binding 框架允許自定義調用何種方法改變值西壮。

自動設置屬性(Automatic Setters)

  1. 對于一個屬性 attribute,Data Binding 會嘗試著去找 setAttribute 方法叫惊。屬性的命名空間是什么并沒有什么關系款青,只和屬性本身的名稱有關。例如霍狰,為 TextView 的屬性 android:text 設置了一個 binding 表達式抡草,則 Data Binding 庫會去尋找 setText(String) 的方法。
  2. 如果 data binding 表達式返回了一個 int 型數(shù)據(jù)蔗坯,Data Binding 則會去尋找 setText(int) 的方法康震。對于 data binding 表達式的返回值一定要小心處理,如果必要的話宾濒,需要做類型強制裝換腿短。
  3. 需要注意的是,就算給定名稱的屬性不存在,Data Binding也會生效橘忱。正是因為如此赴魁,使用 Data Binding 則可以方便地自定義屬性。例如鹦付,DrawerLayout 控件并沒有什么屬性尚粘,但是卻有很多的 setters 方法,就可以方便地使用自動設置屬性給 DrawerLayout 設置屬性敲长。
<android.support.v4.widget.DrawerLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:scrimColor="@{@color/scrim}"
    app:drawerListener="@{fragment.drawerListener}"/>

重命名屬性設置(Renamed Setters)

有些屬性有其對應的 setter 方法郎嫁,但是該 setter 方法和其屬性名稱并不是那么相匹配。對于這些方法祈噪,可以使用 BindingMethods 注解將該屬性與對應的方法關聯(lián)起來泽铛。例如:屬性 android:tint 真正是和 setImageTintList(ColorStateList) 關聯(lián)起來的,而不是和 setTint 方法關聯(lián):

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

在 Android 框架中實現(xiàn)的屬性的 setter 方法已經不錯辑鲤,所以不需要開發(fā)者重命名屬性設置了盔腔。

自定義屬性設置(Custom Setters)

  1. 一些屬性需要自定義邏輯。例如月褥,沒有一個 setter 方法和屬性 android:paddingLeft 相關聯(lián)弛随,但是卻存在 setPadding(left, top, right, bottom) 方法。被 BindingAdapter 注解修飾的靜態(tài) binding adapter 方法允許開發(fā)者自定義一個屬性的 setter 方法如何被調用宁赤。
    Android 已經內置了一些 BindingAdapters舀透。如下是一個與屬性 paddingLeft 相關聯(lián)的 setter 方法。
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
   view.setPadding(padding,
                   view.getPaddingTop(),
                   view.getPaddingRight(),
                   view.getPaddingBottom());
}
  1. Binding adapters 在其他自定義類型上也非常好用决左。
    當開發(fā)者自定義的 binding adapters 與默認的 adapters 沖突時愕够,開發(fā)者自定義的會覆蓋默認的。
    當然也可以自定義接收多個參數(shù)的 adapters佛猛,一個在非主線程中加載圖片的 Loader 如下所示:
@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}"/>
  ```
如果在一個 ImageView 中 `imageUrl` 屬性和 `error` 屬性同時被使用惑芭,并且 `imageUrl` 是 String 類型的,`error` 屬性是 Drawable 類型的继找,則這個 `adapter` 將會被調用遂跟。
  * 在匹配的過程中,自定義的命名空間將會被忽略
  * 也可以為 android 命名空間編寫 adapter

3. `binding adapter` 中的方法可以獲取舊值婴渡,只需要將舊值放置在前幻锁,而新值放置在后,如下所示:
``` Java
@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());
 }
}
  1. 事件處理 handlers 中缩搅,只可用于只擁有一個抽象方法的接口或抽象類,如下所示:
@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);
        }
    }
}
  1. 當一個 listener 中有多個方法時触幼,它必須拆分成多個 listener硼瓣。例如:View.OnAttachStateChangeListener 有兩個方法:onViewAttachedToWindow()onViewDetachedFromWindow()。則必須為這兩個方法設置不同的屬性,分別處理其響應事件堂鲤。
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
    void onViewDetachedFromWindow(View v);
}
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
    void onViewAttachedToWindow(View v);
}

因為改變一個 listener 必將會影響到另一個亿傅,所以我們必須有三個不同binding adapters,包括修改一個屬性和修改兩個屬性的瘟栖,如下所示:

@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);
        }
    }
}

上面這個例子比正常情況下要更復雜一些葵擎,因為 View 是通過在代碼使用 add/remove 方法添加和移除 View.OnAttachStateChangeListener,而不是通過 setter 方法設置監(jiān)聽器的半哟。android.databinding.adapters.ListenerUtil 可以用來跟蹤之前的 listener酬滤,并可以在 Binding Adaper 中移除監(jiān)聽器 listener
通過向 OnViewDetachedFromWindowOnViewAttachedToWindow 接口添加 @TargetApi(VERSION_CODES.HONEYCOMB_MR1) 注解寓涨,Data Binding 代碼生成器知道監(jiān)聽器只在 Honeycomb MR1 設備或更新版本的設備中使用盯串。

轉換器(Converters)

對象轉換(Object Conversions)

當 binding 表達式返回一個對象時,一個 setter 方法(自動 Setter戒良,重命名 Setter体捏,自定義 Setter),并將返回的對象強制轉換成所選擇的 setter 方法所需要的類型糯崎。
以下是一個使用 ObservableMaps 持有數(shù)據(jù)并轉換的例子:

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

userMap 返回一個對象几缭,并且這個對象會被自動地轉換為 setter setText(CharSequence) 所需要的類型。當參數(shù)類型選擇存在疑惑時沃呢,需要開發(fā)者手動地將數(shù)據(jù)類型進行轉換年栓。

自定義類型轉換器(Custom Conversions)

有時候,屬性的值需要在特定類型之間自動轉換樟插。例如韵洋,在設置背景的時候:

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

在這里,背景需要 Drawable 類型的黄锤,但是顏色卻是 Integer 類型的搪缨。當需要一個 Drawable,binding 表達式返回的卻是 Integer 的鸵熟,所以此 int 型數(shù)據(jù)應該轉換成 ColorDrawable副编,此轉換可以通過一個被 BindingConversion 注解修飾的靜態(tài)方法完成。

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

需要注意的是流强,此轉換只能在 setter 階段完成痹届,所以它不允許如下面這樣混合類型的:

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

DataBinding 第二篇文章也介紹完成。至此打月,關于 DataBinding 的文章到此就告一段落队腐。如果有什么問題歡迎指出。我的工作郵箱:jiankunli24@gmail.com


參考資料:

DataBInding 官方文檔

深入Android Data Binding(一):使用詳解 -- YamLee

Android Data Binding 系列(一) -- 詳細介紹與使用 -- ConnorLin

DataBinding(一)-初識 -- sakasa

(譯)Data Binding 指南 -- 楊輝

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末奏篙,一起剝皮案震驚了整個濱河市柴淘,隨后出現(xiàn)的幾起案子迫淹,更是在濱河造成了極大的恐慌,老刑警劉巖为严,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敛熬,死亡現(xiàn)場離奇詭異,居然都是意外死亡第股,警方通過查閱死者的電腦和手機应民,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來夕吻,“玉大人诲锹,你說我怎么就攤上這事∷蠊冢” “怎么了辕狰?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長控漠。 經常有香客問我蔓倍,道長,這世上最難降的妖魔是什么盐捷? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任偶翅,我火速辦了婚禮,結果婚禮上碉渡,老公的妹妹穿的比我還像新娘聚谁。我一直安慰自己,他們只是感情好滞诺,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布形导。 她就那樣靜靜地躺著,像睡著了一般习霹。 火紅的嫁衣襯著肌膚如雪朵耕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天淋叶,我揣著相機與錄音阎曹,去河邊找鬼。 笑死煞檩,一個胖子當著我的面吹牛处嫌,可吹牛的內容都是我干的。 我是一名探鬼主播斟湃,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼熏迹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了凝赛?” 一聲冷哼從身側響起注暗,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤厨剪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后友存,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡陶衅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年屡立,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搀军。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡膨俐,死狀恐怖,靈堂內的尸體忽然破棺而出罩句,到底是詐尸還是另有隱情焚刺,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布门烂,位于F島的核電站乳愉,受9級特大地震影響,放射性物質發(fā)生泄漏屯远。R本人自食惡果不足惜蔓姚,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望慨丐。 院中可真熱鬧坡脐,春花似錦、人聲如沸房揭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捅暴。三九已至恬砂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間伶唯,已是汗流浹背觉既。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留乳幸,地道東北人瞪讼。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像粹断,于是被迫代替她去往敵國和親符欠。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內容