Android MVVM之Data Binding

Data Binding Library


https://github.com/soul-ferry/DataBindingDemo

簡介:


Data Binding Library是一個靈活壁熄、兼容的support library,可以在Android2.1(API 7)以上使用。

使用DataBing,Gradle的Android Plugin需要在1.5.0-alpha1以上。

Build Environment


首先需要在Android SDK manager中下載DataBing的library。然后在build.gradle中添加dataBindingg節(jié)點,如下:

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

如果你的應用中有其他模塊或依賴的庫用到了Data Binding胰耗,也需要在buidl.gradle中配置。

Data Binding 的布局文件


第一個DataBinding表達式

使用DataBinding后的布局文件與原來的布局文件差距很小,使用DataBinding后的布局文件的根布局是layout,并添加了data節(jié)點檬果。布局格式如下

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <!-- import and variable-->
    </data>
    <!-- 使用Data Binding前的根布局 -->   
</layout>

eg.

<?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節(jié)點中的user變量描述了可能會在布局中用到的屬性值。

DataBinding表達式寫在"@{}"中唐断。在下面的例子中选脊,TextView的text被設置為userfirstName

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

Data對象 (ViewModel)

假設你有一個User的POJO

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

這種對象的數據永不會改變。常用于應用中只讀一次永之后不改變的數據脸甘。

也可以使用JavaBeans:

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

DataBinding中上面兩個User對象是一樣的恳啥。@{user.firstName}會訪問第一個類的firstName字段第二個類的
getFirstName()方法。如果存在firstName()方法丹诀,也可能會訪問firstName()方法钝的。

綁定數據

默認情況下翁垂,Binding類會根據布局文件的名字自動生成。如main_activity.xml的布局文件會生成MainActivityBinding硝桩。一個簡單的數據綁定如下:

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

Event Handling

Data Binding提供了兩種處理事件的方法沮峡。

  • Method References(方法引用):你可以直接引用和listener方法簽名一致的方法。當一個表達式被解析為Method References亿柑,Data Binding會把該方法包裝成一個listener并設置到對應的View邢疙。如果表達式被解析為nullData Binding會設置成對應的listenernull望薄。

  • Listener Bindings:也可以使用lambda,Data Binding總是會創(chuàng)建設置到View上的listener,當事件被分發(fā)到該Viewlambda表達式會被解析疟游。

Method References

與可以在android:onClick屬性指定調用的Activity方法類似,事件可以直接綁定到Handler的方法上痕支。后者的優(yōu)勢在于實在編譯時處理颁虐,如果方法不存在或簽名不一致會得到一個編譯時錯誤。下面的例子展示了該用法:

public class MyHandlers {
    public void onClickFriend(View view) { ... }
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="handlers" type="com.example.Handlers"/>
       <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>

注意:Handler中方法必須與監(jiān)聽器中方法參數及返回值一樣卧须。如上面的例子中Handler的方法為public void onClickFriend(View view)另绩,android:onClick需要的監(jiān)聽器為OnClickListener,其方法為public void onClick(View view)

Listener Bindings

Listener Bindings只要求Handler中方法與監(jiān)聽器中的方法返回值一致即可。

public class Presenter {
    public void onSaveClick(Task task){}
}
  <?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>

在上面的例子中問我們并沒有定義傳遞給 onClick(android.view.View)方法的view參數花嘶。Listener bindings提供了兩種傳遞監(jiān)聽器參數的選擇:你可以忽略所有的參數笋籽。如果你更喜歡使用帶參數的,上面的例子也可以改為

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

或者也可以傳遞更多參數

public class Presenter {
    public void onSaveClick(View view, Task task){}
}
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}
避免監(jiān)聽器混淆

Listener表達式非常強大椭员,可以讓你寫出更易讀的代碼车海。另一方面,復雜的Listener表達式會讓你的布局變得難以讀懂隘击、管理侍芝。Listener表達式應該盡量簡單,只從UI傳遞有效數據到Callback的方法中埋同。所有的業(yè)務邏輯都應該該在Callback的方法中實現州叠。

存在一些特有的點擊事件,它們的點擊事件的屬性并不是android:onClick凶赁。下面的表格列舉了對應的屬性:

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

Layout Details(布局詳情)


Imports

可以在<data>節(jié)點使用import語法咧栗,與java相同java.lang.*也是自動導入的。

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

當有相同類名時哟冬,可以使用別名:

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

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>

在編譯期間會對variable進行類型檢查,只有實現了Observable接口或是一個observable collectionvariable才會被觀察浩峡,即當該variable的數據發(fā)生變化UI會更新。

為了適配不同的屏幕你可能會有多個布局文件错敢,Data Binding會合并這些布局文件中的variable翰灾,所以這些布局文件中的variable不能有混淆缕粹。

Data Binding會為每個variable自動生成對應的settergetter方法。variable在調用setter前都會有一個默認值:引用類型->null纸淮、int->0平斩、布爾值->false

Data Binding會自動生成一個contextvariable咽块。它來值跟布局的getContext()绘面。可以被用戶的variable覆蓋侈沪。

Includes

variable在使用了<include/>布局中的應用:

<?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布局文件都必須有user variable

不支持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>

表達式語言

共同的功能

下面的表達式和Java中的一樣

  • Mathematical + - / * %
  • String concatenation +
  • Logical && ||
  • Binary & | ^
  • Unary + - ! ~
  • Shift >> >>> <<
  • Comparison == > < >= <=
  • instanceof
  • Grouping ()
  • Literals - character, String, numeric, null
  • Cast
  • Method calls
  • Field access
  • Array access []
  • T*ernary operator ?:

eg.

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
消失的操作

在Java中可用,但在Databinding中不可用的操作

  • this
  • super
  • new
  • Explicit generic invocation
Null Coalescing Operator(非空運算)
android:text="@{user.displayName ?? user.lastName}"

等于

android:text="@{user.displayName != null ? user.displayName : user.lastName}"
Property Reference(屬性引用)

無論Data采用的是那種定義方法亭罪,POJO瘦馍、Java Beans或ObservableFields,在DataBinding的語法中引用都用下面
的方式应役。

android:text="@{user.lastName}"
Avoiding NullPointerException(自動避免空指針異常)

Data Binding生成的代碼會自動檢查null值情组,避免空指針異常。

例如@{user.name},如果user為空箩祥,user,name會被賦值為默認值(null)院崇。如果是int類型的默認值為0。

Collections (集合)

常見的集合:arrays, lists, sparse lists, and maps可以通過[]訪問袍祖。

<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List<String>"/>
    <variable name="sparse" type="SparseArray<String>"/>
    <variable name="map" type="Map<String, String>"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
    </data>
    …
    android:text="@{list[index]}"
    …
    android:text="@{sparse[index]}"
    …
    android:text="@{map[key]}"
String Literals (字符串)

如果需要在@{}表達式內使用"",布局的屬性值需要使用''

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

或者使用下面的方式

android:text="@{map[`firstName`}"
android:text="@{map['firstName']}"
Resources (資源文件)

可以使用通常的語法在表達式中訪問資源文件:

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

格式化的字符串和plurals

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

一些資源需要使用指定的引用方式

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 (數據對象)


任何一個POJO都可以用于Data Binding亚脆,但是修改了一個并POJO不會更新UI。Data Binding的real power是讓你的數據對象(Data Objects)發(fā)生數據改變時能夠發(fā)出通知盲泛。這里有3種數據改變通知機制:Observable objects, observable fields, observable collections濒持。

當一個實現了通知機制的數據對象的數據發(fā)生改變并且綁定到了UI,UI會自動更新寺滚。

Observable Objects (Observable對象)

Observableinterface有一個添加和移除listeners的機制柑营,但是發(fā)送通知取決于開發(fā)者。為了方便開發(fā),Data Binding提供了一個BaseObservable類村视,BaseObservable實現了listener注冊機制官套。實現類要負責在數據改變時發(fā)出通知,這需要通過在getter方法加上@Bindable注解并且在setter方法中發(fā)出通知蚁孔。

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類中生成一個字段奶赔,BRR類似。

ObservableFields

Data Binding還提供了另外更細粒度杠氢、更方便的創(chuàng)建Observable類的方式:ObservableFields站刑,ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable

eg.

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

如何訪問:

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

Observable Collections

一些App使用集合保存數據鼻百。Observable Collections允許使用通過來訪問數據绞旅。

ObservableArrayMap

用于引用類型摆尝,如String

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

在布局文件中的使用

<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"/>
ObservableArrayList

int

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

在布局文件中的使用

<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(生成Binding)


上文已經說過生成的Binding類名由布局文件名決定。

Creating(創(chuàng)建)

Binding類應該盡可能早地創(chuàng)建,綁定layout的方式有多種因悲,都是通過調用Binding類的靜態(tài)方法堕汞。如:

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

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

Views With IDs(帶id的View)

Data Binding會為布局文件中每個帶ID的View生成一個public final的字段。這可以讓替代findViewById以更快的方式訪問View:

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

ViewStubs

布局文件中的ViewStub經過binding之后會轉換成ViewStubProxy晃琳。

簡單用代碼說明一下讯检,xml 文件與之前的代碼一樣,根節(jié)點改為 layout卫旱,在 LinearLayout 中添加一個 ViewStub人灼,添加 ID。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <RelativeLayout
         android:layout_width="match_parent"
         android:layout_height="match_parent">
         <ViewStub
            android:id="@+id/view_stub_test"
            android:layout="@layout/stub"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
     </RelativeLayout>
</layout>

在 Java 代碼中獲取 binding 實例誊涯,為 ViewStubProy 注冊 ViewStub.OnInflateListener 事件:

binding.viewStubTest.setOnInflateListener(new ViewStub.OnInflateListener() {
    @Override
    public void onInflate(ViewStub stub, View inflated) {
        StubBinding stubBinding = DataBindingUtil.bind(inflated);
        ViewStubViewModel model = new ViewStubViewModel();
        model.setName("boco");
        stubBinding.setStubModel(model);
    }
 });
 binding.viewStubTest.getViewStub().inflate();

Advanced Binding(高級綁定)

Dynamic Variables(動態(tài)設置Variables)

有時候挡毅,我們并不知道具體的Binding類型,如在RecyclerView.Adapter中可能會有多個layout,我們必須在onBindViewHolder(VH, int)進行賦值暴构。

public static class BindingHolder extends RecyclerView.ViewHolder {
    private ViewDataBinding binding;
    public BindingHolder(View itemView) {
        super(itemView);
    }
    public ViewDataBinding getBinding() {
        return binding;
    }
    public void setBinding(ViewDataBinding binding) {
        this.binding = binding;
    }
}
@Override
public BindingHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
    ViewDataBinding binding = DataBindingUtil.inflate(
            LayoutInflater.from(viewGroup.getContext()),
            R.layout.list_item,
            viewGroup,
            false);
    BindingHolder holder = new BindingHolder(binding.getRoot());
    holder.setBinding(binding);
    return holder;
}
@Override
public void onBindViewHolder(BindingHolder holder, int position) {
    User user = users.get(position);
    holder.getBinding().setVariable(BR.user, user);
    holder.getBinding().executePendingBindings();
}
Immediate Binding(立即綁定)

當一個variable或者observable發(fā)生了改變跪呈,數據綁定會在下一幀之前發(fā)生但不是立刻發(fā)生,如果你希望數據綁定立刻發(fā)生需要使用executePendingBindings() 方法,eg.

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

Background Thread

你可以在后臺線程中改變不是集合的數據取逾,Data binding will localize each variable / field while evaluating to avoid any concurrency issues.

Attribute Setters (屬性賦值)


自動賦值

有了 Data Binding耗绿,即使屬性沒有在 declare-styleable 中定義,我們也可以通過 xml 進行賦值操作砾隅。

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

在上面的例子中app:drawerListener="@{fragment.drawerListener}會調用DrawerLayout的setDrawerListener(DrawerListener listener)方法具體的方法參數類型和@{}中的參數類型一致误阻。

重命名

  <Button
       android:id="@+id/email_sign_in_button"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       app:myText="@{login.name}"
       android:onClick="@{loginHandler.onClick}"
       />

在上面的xml對應的ViewModel中加入如下注解,app:myText="@{login.name}便會調用Button的setText方法晴埂。

@BindingMethods({
        @BindingMethod(type = android.widget.Button.class,
                attribute = "myText",
                method = "setText"),
})

自定義

ViewModel中的方法
@BindingAdapter({"app:myText"})
 public static void showText(Button button,String newV,String oldV){
     button.setText(newV);
     Log.w(TAG,"new="+newV+"--oldV="+oldV);
 }
 or
 @BindingAdapter({"app:myText"})
 public static void showText(Button button,String newV){
     button.setText(newV);
     Log.w(TAG,"new="+newV);
 } 

對應得xml代碼:

  <Button
       android:id="@+id/email_sign_in_button"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       app:myText="@{login.name}"
       android:onClick="@{loginHandler.onClick}"
       />

轉換器 (Converters)

在 xml 中為屬性賦值時究反,如果變量的類型與屬性不一致,通過 DataBinding 可以進行轉換儒洛。
例如精耐,下面代碼中如果要為屬性 android:background 賦值一個 int 型的 color 變量:

<View
    android:background="@{isError.get() ? @color/red : @color/white}"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_height="@{height}" />

只需要定義一個標記了 @BindingConversion 的靜態(tài)方法即可(方法的定義位置可以隨意):

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
    return new ColorDrawable(color);
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市琅锻,隨后出現的幾起案子卦停,更是在濱河造成了極大的恐慌,老刑警劉巖恼蓬,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惊完,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門皂股,熙熙樓的掌柜王于貴愁眉苦臉地迎上來躯护,“玉大人本股,你說我怎么就攤上這事攀痊⊥╇纾” “怎么了拄显?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵案站,是天一觀的道長。 經常有香客問我蟆盐,道長,這世上最難降的妖魔是什么石挂? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任博助,我火速辦了婚禮痹愚,結果婚禮上富岳,老公的妹妹穿的比我還像新娘拯腮。我一直安慰自己窖式,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布动壤。 她就那樣靜靜地躺著,像睡著了一般琼懊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上启妹,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天削祈,我揣著相機與錄音,去河邊找鬼髓抑。 笑死,一個胖子當著我的面吹牛吨拍,可吹牛的內容都是我干的。 我是一名探鬼主播伊滋,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼笑旺!你這毒婦竟也來了?” 一聲冷哼從身側響起关噪,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤乌妙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后藤韵,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡欲险,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年悉盆,在試婚紗的時候發(fā)現自己被綠了盯荤。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片焕盟。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡脚翘,死狀恐怖灼卢,靈堂內的尸體忽然破棺而出来农,到底是詐尸還是另有隱情,我是刑警寧澤涩咖,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布繁莹,位于F島的核電站,受9級特大地震影響咨演,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一饵较、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧横辆,春花似錦、人聲如沸龄糊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽阿浓。三九已至蹋绽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間退敦,已是汗流浹背蚣抗。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留翰铡,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓例证,卻偏偏與公主長得像迷捧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子漠秋,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內容