一篇文章帶你入門 Android MVVM

我的更多 android 博文,關(guān)注作者~每周更新一篇 Android干貨博文
http://xuyushi.github.io/archives/

在我的上一篇博文中介紹了Android的MVP模式(http://www.reibang.com/p/f6252719b3af) MVVM 是從 MVP 的進(jìn)一步發(fā)展與規(guī)范,MVP 隔離了 M 與 V 的直接聯(lián)系后拜英,靠 Presenter 來中轉(zhuǎn)棍郎,所以使用 MVP 時(shí) P 是直接調(diào)用 View 的接口來實(shí)現(xiàn)對視圖的操作的命锄,M 與 V是隔離了霸褒,方便測試了膛檀,但代碼還不夠優(yōu)雅簡潔啊馅扣,所以 MVVM 就彌補(bǔ)了這些缺陷斟赚。

概述

MVVM模式包含了三個(gè)部分:

  • Model :基本業(yè)務(wù)邏輯
  • View :視圖內(nèi)容
    ViewModel: 將前面兩者聯(lián)系在一起的對象

當(dāng)View有用戶輸入后,ViewModel通知Model更新數(shù)據(jù)差油,同理Model數(shù)據(jù)更新后拗军,ViewModel通知View更新。

MVP MVVM區(qū)別

可以看到 ViewModel 承擔(dān)了 Presenter 中與 view和 Model 交互的職責(zé)蓄喇,與 MVP模式不同的是发侵,VM與 V 之間是通過 Datebingding 實(shí)現(xiàn)的,而 P是持有 View 的對象妆偏,直接調(diào)用 View 中的一些接口方法來實(shí)現(xiàn)刃鳄。ViewModel可以理解成是View的數(shù)據(jù)模型和Presenter的合體。**它通過雙向綁定(松耦合)解決了MVP中Presenter與View聯(lián)系比較緊密的問題钱骂。 **

環(huán)境搭建

Android 的 Gradle 插件版本不低于 1.5.0-alpha1:

classpath 'com.android.tools.build:gradle:1.5.0'

然后修改對應(yīng)模塊(Module)的 build.gradle:

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

注:Android stuido 的版本要大于1.3
Android Studio目前對binding對象沒有自動代碼提示叔锐,只會在編譯時(shí)進(jìn)行檢查。

基礎(chǔ)入門

布局問文件

相比傳統(tǒng)的 xml罐柳,根節(jié)點(diǎn)編程了layout掌腰,里面包括了data節(jié)點(diǎn) 和傳統(tǒng)的視圖。data節(jié)點(diǎn)就像是連接 View 和 Modle 的橋梁张吉。在data節(jié)點(diǎn)中聲明一個(gè)variable變量齿梁,使其可以在這個(gè)layout中使用

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="user"type="com.example.User"/>
    </data>
    <!--原先的根節(jié)點(diǎn)(Root Element)-->
    <LinearLayout>
    ....
    </LinearLayout>
</layout>

例如在 TextView 中使用

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

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

定義一個(gè) User java bean 類

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

    public String getLastName() {
        return lastName;
    }
}

layout中定義User中的對象,然后把它跟布局文件中聲明的變量進(jìn)行綁定

定義 Variable

    <data>

        <variable
            name="user"
            type="io.github.xuyushi.androidmvvmdemo.User" />
    </data>
  • 變量名為user
  • 變量類型為"io.github.xuyushi.androidmvvmdemo.User"

data也支持 import

<data>
   <import type="io.github.xuyushi.androidmvvmdemo.User"/>
   <variable
       name="user"
       type="User" />
</data>

注意坑
import 并不能和 java 一樣可以 import xx.xxx.*肮蛹,必須具體寫明每個(gè)要導(dǎo)入的類名勺择,如

<import type="io.github.xuyushi.androidmvvmdemo.User"/>
<import type="io.github.xuyushi.androidmvvmdemo.MyHandler"/>


// this is WRONG
<import type="io.github.xuyushi.androidmvvmdemo.*"/>

編譯之后,插件會根據(jù) xml 的命名(activity_main)伦忠,在 output會生成ActivityMainBinding

java.lang.* 包中的類會被自動導(dǎo)入省核,可以直接使用,例如要定義一個(gè) String 類型的變量:
<variable name="firstName" type="String" />

綁定 Variable

修改MainActivity中的onCreate昆码,用 DatabindingUtil.setContentView() 來替換掉 setContentView()气忠,然后創(chuàng)建一個(gè) user 對象邻储,通過 binding.setUser(user) 與 variable 進(jìn)行綁定。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        User user = new User("testFirst", "testLast");
        binding.setUser(user);
    }
}

如果使用的 ListView 或者RecyclerView可以使用這個(gè)

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

有時(shí)候不能預(yù)先知道 Bingding 類的種類旧噪,這時(shí)候可以使用DataBindingUtil 類:

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

支持的語法

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

不支持的語法

this
super
new

ActivityMainBinding類是自動生成的吨娜,所有的 set 方法也是根據(jù) variable 名稱生成的。例如淘钟,我們定義了兩個(gè)變量宦赠。

<data>
    <variable name="firstName" type="String" />
    <variable name="lastName" type="String" />
</data>

那么會生成兩個(gè) set方法

setFirstName(String firstName);
setLastName(String lastName);

使用Variable

數(shù)據(jù)與 Variable 綁定之后,xml 的 UI 元素就可以直接使用了

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

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

綁定事件

可以直接在 xml 導(dǎo)入android.view.View.OnClickListener米母,并制定其點(diǎn)擊事件

<variable
  name="clickListener"
  type="android.view.View.OnClickListener" />
  
 ...
 android:onClick="@{clickListener}"
 ...
 holder.binding.setClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            //do something
        });

進(jìn)階用法

使用類的方法

//Error:(27, 29) cannot find method addSomeThing in class io.github.xuyushi.androidmvvmdemo.MyUtill

類的別名

如果導(dǎo)入不同的包中有相同的類名勾扭,使用import 中的 alias 屬性。

<import type="com.example.home.data.User" />
<import type="com.examle.detail.data.User" alias="DetailUser" />
<variable name="user" type="DetailUser" />

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

直接修改數(shù)據(jù)對象并不能直接更新 UI铁瞒,Android的Data Binding模塊給提供了通知機(jī)制妙色,有3種類型,分別對應(yīng)于類(Observable)精拟,字段(ObservableField)燎斩,集合類型(Observable Collections)。

Android的Data Binding模塊給提供了通知機(jī)制蜂绎,有3種類型,分別對應(yīng)于類(Observable)笋鄙,字段(ObservableField)师枣,集合類型(Observable Collections)。

Observable Objects

目前 DataBinding 暫時(shí)只支持單向綁定萧落。

要實(shí)現(xiàn) Observable Binding践美,首先得有一個(gè) implement 了接口android.databinding.Observable的類,為了方便找岖,Android 原生提供了已經(jīng)封裝好的一個(gè)類 - BaseObservable陨倡,并且實(shí)現(xiàn)了監(jiān)聽器的注冊機(jī)制

public 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(io.github.xuyushi.androidmvvmdemo.BR.firstName);
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
        notifyPropertyChanged(io.github.xuyushi.androidmvvmdemo.BR.lastName);
    }
}

The Bindable annotation should be applied to any getter accessor method of an {@link Observable} class. Bindable will generate a field in the BR class to identify the field that has changed.

Bindable注解是為了在編程的時(shí)候生成 BR 類,Bindable會在 BR 類中生成一個(gè)域變量 许布,來表明這個(gè)域有木有被改變兴革。通過代碼可以看出,當(dāng)數(shù)據(jù)發(fā)生變化時(shí)還是需要手動發(fā)出通知蜜唾。 通過調(diào)用 notifyPropertyChanged(BR.firstName) 可以通知系統(tǒng) BR.firstName 這個(gè) entry 的數(shù)據(jù)已經(jīng)發(fā)生變化杂曲,需要更新 UI。

ObservableFields

具體到成員變量,這種方式無需繼承 BaseObservable
如果變量比較少袁余,都是簡單的數(shù)據(jù)類型是時(shí)擎勘,可以用ObservableFieldsObservableFields 自包含具有單個(gè)字段的observable對象颖榜。它有所有基本類型和一個(gè)是引用類型棚饵。要使用它需要在data對象中創(chuàng)建public final字段:

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

注意

  • 可以在 java bean 中定義煤裙,也可以在 activity 中 或者bind 出定義
  • 使用ObservableFields 在 Model 中的 @Bindable get set 方法都可以去掉
  • 當(dāng)firstNamelastName變化時(shí)噪漾,UI 會得到通知积暖,使用的賦值語句為user.firstName.set("Google");

Observable 集合

一些app使用更多的動態(tài)結(jié)構(gòu)來保存數(shù)據(jù)。Observable集合允許鍵控訪問這些data對象怪与。ObservableArrayMap用于鍵是引用類型,如String夺刑。

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

在layout文件中,通過String鍵可以訪問map

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

當(dāng) key 是 inter 是分别, ObservableArrayList 比較適用

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

binding 生成

binding 類連接了 layout中的variables與Views遍愿。,所生成的Binding類都擴(kuò)展了android.databinding.ViewDataBinding

創(chuàng)建

Binding應(yīng)在inflation之后就立馬創(chuàng)建耘斩,以確保View層次結(jié)構(gòu)沒被改變沼填。

首先 inflate

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

帶 ID 的 View

同步 bind 我們可以不需要 view 實(shí)例,但是玩意需要也可以有

<TextView
    android:id="@+id/firstName"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

上面代碼中定義了一個(gè) ID 為 firstName 的 TextView括授,那么它對應(yīng)的變量就是
public final TextView firstName;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_view_with_ids);
    }

    public void showMyName(View view) {
        binding.firstName.setText("liang");
        binding.lastName.setText("fei");
    }

這樣就免去了些 findViewById

ViewStubs

ViewStubs跟正常的Views略有不同坞笙。他們開始時(shí)是不可見的,當(dāng)他們要么設(shè)置為可見或被明確告知要載入時(shí)荚虚,它們通過載入另外一個(gè)layout取代了自己薛夜。

當(dāng)載入另一個(gè)layout,為新的布局必需創(chuàng)建一個(gè)Binding版述。因此梯澜,ViewStubProxy必需監(jiān)聽ViewStub的OnInflateListener監(jiān)聽器并在那個(gè)時(shí)候建立Binding。因?yàn)橹挥幸粋€(gè)可以存在渴析,ViewStubProxy允許開發(fā)者在其上設(shè)置一個(gè)OnInflateListener它會在建立Binding后調(diào)用晚伙。

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <LinearLayout
        ...>
        <ViewStub
            android:id="@+id/view_stub"
            android:layout="@layout/view_stub"
            ... />
    </LinearLayout>
</layout>

在 Java 代碼中獲取 binding 實(shí)例,ViewStubProy 注冊ViewStub.OnInflateListener 事件:

binding = DataBindingUtil.setContentView(this, R.layout.activity_view_stub);
binding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
    @Override
    public void onInflate(ViewStub stub, View inflated) {
        ViewStubBinding binding = DataBindingUtil.bind(inflated);
        User user = new User("fee", "lang");
        binding.setUser(user);
    }
});

動態(tài) Variables

有時(shí)候不止具體綁定的對象俭茧,以 RecyclerView 為例咆疗,Adapter 的 DataBinding 需要動態(tài)生成,因此我們可以在 onCreateViewHolder 的時(shí)候創(chuàng)建這個(gè) DataBinding母债,然后在 onBindViewHolder 中獲取這個(gè) DataBinding午磁。

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

屬性 Setter

每當(dāng)綁定值的變化,生成的Binding類必須調(diào)用setter方法??场斑。Data Binding框架有可以自定義賦值的方法漓踢。

自動Setters

對于一個(gè)屬性,Data Binding試圖找到setAttribute方法漏隐。與該屬性的namespace并不什么關(guān)系喧半,僅僅與屬性本身名稱有關(guān)

例如青责,有關(guān)TextView的android:text屬性的表達(dá)式會尋找一個(gè)setText(String)的方法挺据。如果表達(dá)式中的參量是一個(gè)int取具,Data Binding會搜索的setText(int)方法。注意:要表達(dá)式返回正確的類型扁耐,如果需要的話使用轉(zhuǎn)型暇检。Data Binding仍會運(yùn)行即使沒有給定名稱的屬性存在。然后婉称,您可以通過Data Binding輕松地為任何setter“創(chuàng)造”屬性块仆。例如,DrawerLayout沒有任何屬性王暗,但可以有很多的setters悔据。您可以使用其中的一個(gè)setters。

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

這里我們增加了一個(gè)命名空間app俗壹,并且注意DrawerLayoutapp:scrimColor屬性科汗,這里和我們自定義view時(shí)自定義的屬性一樣,但是這里并不需要我們?nèi)ブ貙?code>DrawerLayout,此時(shí)绷雏,我們可以自己定義setTcrimColor头滔、setDrawerListener的方法

重命名的Setters

一些有setters的屬性按名稱并不匹配。對于這些方法涎显,屬性可以通過BindingMethods注解相關(guān)聯(lián)坤检。這必須與一個(gè)包含BindingMethod注解的類相關(guān)聯(lián),每一個(gè)用于一個(gè)重命名的方法棺禾。例如缀蹄,android:tint屬性與setImageTintList相關(guān)聯(lián),而不與setTint相關(guān)膘婶。

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

自定義Setters

有些屬性需要自定義綁定邏輯。例如蛀醉,對于android:paddingLeft屬性并沒有相關(guān)setter悬襟。相反,setPadding(left, top, right, bottom)是存在在拯刁。一個(gè)帶有BindingAdapter注解的靜態(tài)綁定適配器方法允許開發(fā)者自定義setter如何對于一個(gè)屬性的調(diào)用脊岳。

Android的屬性已經(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());
}

Binding適配器對其他定制類型非常有用割捅。例如,自定義loader可以用來異步載入圖像帚桩。
當(dāng)有沖突時(shí)亿驾,開發(fā)人員創(chuàng)建的Binding適配器將覆蓋Data Binding默認(rèn)適配器。
您也可以創(chuàng)建可以接收多個(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);
}

loadImage 可以放在任意類中莫瞬,該類中只有一個(gè)靜態(tài)的方法imageLoader儡蔓,該方法有3個(gè)參數(shù),一個(gè)是需要設(shè)置數(shù)據(jù)的view疼邀, 一個(gè)是我們需要的url喂江、有個(gè)個(gè)是錯誤加載的圖像,值得注意的是那個(gè)BindingAdapter注解旁振,看看他的參數(shù)获询,是一個(gè)數(shù)組,內(nèi)容只有一個(gè)bind:imageUrl拐袜,僅僅幾行代碼吉嚣,我們不需要 手工調(diào)用 (類 xxxxxxx)中的loadImage,也不需要知道loadImage方法定義到哪了阻肿,一個(gè)網(wǎng)絡(luò)圖片加載就搞定了這里面起關(guān)鍵作用的就是BindingAdapter 注解瓦戚,這里要遵循一定的規(guī)則,、

以bind:開頭丛塌,接著書寫你在控件中使用的自定義屬性名稱较解。

轉(zhuǎn)換器

在 xml 中為屬性賦值時(shí),如果變量的類型與屬性不一致赴邻,通過 DataBinding 可以進(jìn)行轉(zhuǎn)換

例如印衔,下面代碼中如果要為屬性 android:background 賦值一個(gè) 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}" />

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

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

再舉個(gè)栗子 ,假如你的控件需要一個(gè)格式化好的時(shí)間姥敛,但是你只有一個(gè)Date類型的變量奸焙。可以轉(zhuǎn)化完成后在設(shè)置彤敛,此時(shí)更適合使用 conver

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data class=".Custom">
        <variable
            name="time"
            type="java.util.Date" />
    </data>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@{time}"/>
</layout>
binding.setTime(new Date());

看看 conver

public class ConvertUtil {
    @BindingConversion
    public static String convertDate(Date date) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        return sdf.format(date);
    }
}

:convert 可以放在任意包中与帆,只要寫明注解,已經(jīng)被轉(zhuǎn)換和轉(zhuǎn)換成的類型墨榄,所以注意不要重復(fù)定義類型相同的 convert玄糟,使用 Converter 一定要保證它不會影響到其他的屬性。舉個(gè)栗子袄秩,int -> int 的 convert 就影響到了android:visibility

Android stuido 的預(yù)覽支持

類似于 tools:text ,代碼如下

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

舉個(gè)例子

工程分為兩個(gè)部分

  • 第一個(gè)簡單的例子阵翎,點(diǎn)擊事件產(chǎn)生后,直接改變了之剧,user 的數(shù)據(jù)郭卫,并沒有對 view 操作的邏輯,但是 databinding 已經(jīng)幫我們完成了一切
  • 第二個(gè)例子是一個(gè) recycleVIew 的例子背稼,點(diǎn)擊每個(gè) cardview 增加一點(diǎn)數(shù)據(jù)

代碼不貼了贰军,放 github了
https://github.com/xuyushi/AndroidMVVMDemo

我的更多 android 博文
http://xuyushi.github.io/archives/
關(guān)注作者~每周更新一篇 Android干貨博文

參考

https://www.zhihu.com/question/30976423
https://developer.android.com/intl/zh-cn/tools/data-binding/guide.html#generated_binding
https://segmentfault.com/a/1190000002876984
http://tech.vg.no/2015/07/17/android-databinding-goodbye-presenter-hello-viewmodel/
http://www.reibang.com/p/4e3220a580f6
https://github.com/LyndonChin/MasteringAndroidDataBinding
http://www.cnblogs.com/dxy1982/p/3793895.html
https://realm.io/cn/news/data-binding-android-boyar-mount/?utm_source=tuicool&utm_medium=referral
https://www.aswifter.com/2015/07/04/android-data-binding-1/
http://blog.csdn.net/qibin0506/article/details/47720125

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市雇庙,隨后出現(xiàn)的幾起案子谓形,更是在濱河造成了極大的恐慌灶伊,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寒跳,死亡現(xiàn)場離奇詭異聘萨,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)童太,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門米辐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人书释,你說我怎么就攤上這事翘贮。” “怎么了爆惧?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵狸页,是天一觀的道長。 經(jīng)常有香客問我扯再,道長芍耘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任熄阻,我火速辦了婚禮斋竞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘秃殉。我一直安慰自己坝初,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布钾军。 她就那樣靜靜地躺著鳄袍,像睡著了一般哪痰。 火紅的嫁衣襯著肌膚如雪忘衍。 梳的紋絲不亂的頭發(fā)上趋距,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天壶熏,我揣著相機(jī)與錄音,去河邊找鬼凯砍。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的唇礁。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼惨篱,長吁一口氣:“原來是場噩夢啊……” “哼盏筐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起砸讳,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤琢融,失蹤者是張志新(化名)和其女友劉穎界牡,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體漾抬,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宿亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了纳令。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挽荠。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖平绩,靈堂內(nèi)的尸體忽然破棺而出圈匆,到底是詐尸還是另有隱情,我是刑警寧澤捏雌,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布跃赚,位于F島的核電站,受9級特大地震影響性湿,放射性物質(zhì)發(fā)生泄漏纬傲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一窘奏、第九天 我趴在偏房一處隱蔽的房頂上張望嘹锁。 院中可真熱鬧,春花似錦着裹、人聲如沸领猾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽摔竿。三九已至,卻和暖如春少孝,著一層夾襖步出監(jiān)牢的瞬間继低,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工稍走, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留袁翁,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓婿脸,卻偏偏與公主長得像粱胜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子狐树,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

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