Jetpack-Compose 學習筆記(五)—— State 狀態(tài)是個啥?又是新概念课锌?

系列第五篇厨内,進入 Compose 中有關(guān) State 狀態(tài)的學習。

前面幾篇筆記講了那么多內(nèi)容渺贤,都是基于靜態(tài)界面的展示來說的雏胃,即給我一個不變的數(shù)據(jù),然后將它展示出來志鞍。如何在 Compose 中構(gòu)建一個隨數(shù)據(jù)而變化的動態(tài)界面呢瞭亮?相信看完這篇就知道了。

1固棚、基本知識

眾所周知统翩,Compose 徹底舍棄了 xml 文件,我們需要像 Flutter 一樣完全用代碼去進行界面的編碼此洲,這樣做很容易會導致一個問題:界面和數(shù)據(jù)處理邏輯耦合厂汗,從而導致 Activity 中代碼臃腫且維護性下降。

雖然提出了許多架構(gòu)思想呜师,如 MVC娶桦、MVP、MVVM 等汁汗,一定程度上解耦了界面與數(shù)據(jù)處理邏輯趟紊,但是架構(gòu)本身就具有一定的復(fù)雜性,且對于后續(xù)維護成本也相對較高碰酝,所以 Compose 一開始就將界面與數(shù)據(jù)分開來霎匈,分別稱之為 組合State 狀態(tài)秀仲。

State 狀態(tài):官方文檔上說 State 狀態(tài)是指可以隨時間變化的任何值夸盟。例如,它可能是存儲在 Room 數(shù)據(jù)庫中的值戚炫、類的變量,加速度計的當前讀數(shù)等墨吓。
怎么理解這個概念呢球匕?我覺得可以簡單理解為:我們要展示給用戶看的數(shù)據(jù)。例如帖烘,一個商品的展示頁面亮曹,其實就是根據(jù)數(shù)據(jù)的不同來展示不同的狀態(tài),數(shù)據(jù)正常秘症、數(shù)據(jù)錯誤照卦、空數(shù)據(jù)等不同的數(shù)據(jù)就是代表了不同的 State 狀態(tài)。

組合:按照文檔上的意思我覺得可以理解為展示給用戶的界面乡摹,是由多個組合項(Composable組件)組成役耕。

Event事件:指的是從應(yīng)用外部生成的輸入,用于通知程序的某部分發(fā)生了變化聪廉。如用戶的點擊瞬痘,滑動等操作。所以在 Compose 中板熊,Event 事件一般就是引起 State 狀態(tài)改變的原因框全。

2、狀態(tài)的表示

其實可以換一種說法:Compose 中數(shù)據(jù)的存儲和更新如何處理干签?目前來看的話津辩,可以用 LiveData、StateFlow筒严、Flow、Observable 等表示情萤⊙纪埽可以看出,這些都是一種可觀察數(shù)據(jù)變化的容器筋岛,被它們修飾的對象娶视,我們都可以觀察到該對象的變化,從而更新界面睁宰。沒錯肪获,都是使用的觀察者模式。

在 Compose 的文檔中柒傻,ViewModel 被推薦為 State狀態(tài)的管理對象孝赫,從而實現(xiàn)將數(shù)據(jù)與界面展示的 Activity 分離解耦的目的。

2.1 ViewModel

ViewModel 也是 Jetpack 工具庫的成員之一红符,主要用來存儲 UI 展示所需要的數(shù)據(jù)青柄,谷歌推薦的做法是將 Activity 中的數(shù)據(jù)都放到 ViewModel 里伐债,而且在 Activity、Fragment 重建時 ViewModel 中的數(shù)據(jù)是不受影響的致开。還可以通過 ViewModel 來進行 Activity 與 Fragment 之間峰锁,或者 Fragment 與 Fragment 之間的通信。

ViewModel 經(jīng)常與 LiveData 一起使用双戳,但在 Compose 中虹蒋,推薦使用 MutableState 來具體存儲數(shù)據(jù)的值。

2.2 MutableState<T>

MutableState<T> 是 Compose 中內(nèi)置的專門用于存儲 State 狀態(tài)的容器飒货,與 LiveData 一樣魄衅,它可以觀察到存儲的值的變化。如果項目不是純 Compose 代碼膏斤,建議還是用 LiveData徐绑,因為 LiveData 是通用的,而 MutableState<T> 是與 Compose 集成了莫辨,所以在 Compose 中使用 MutableState 比 LiveData 更簡單傲茄。

從這里也可看出,Compose 是推薦將 State 狀態(tài)設(shè)置為可觀察的沮榜,這樣當狀態(tài)發(fā)生更改時盘榨,Compose 可以自動重組更新界面。

實際上 MutableState<T> 是個接口:

// code 1
interface MutableState<T>: State<T> {
    override var value: T
}

對 value 進行的任何更改都會自動重組用于讀取此狀態(tài)的所有 Composable 函數(shù)蟆融,也就是說草巡,value 值改變了之后,所有引用了 value 的 Composable 函數(shù)都會重新繪制更新型酥。

3山憨、一個簡單例子

先來看看效果:


圖 1

其中有兩個控件,一個是 Text弥喉,用于顯示輸入的內(nèi)容郁竟;另一個是 TextField,相當于 View 體系中的 EditText由境∨锬叮可以看出,Text 顯示的內(nèi)容可以隨著下面的 TextField 中輸入的內(nèi)容實時更新虏杰。

如果是在 View 體系中讥蟆,一般實現(xiàn)的方法是在 EditText 添加一個 TextWatcher 類用于監(jiān)聽輸入事件,然后在 onTextChanged 方法中對 TextView 設(shè)置輸入的內(nèi)容即可纺阔。

再來看一下 Compose 是如何實現(xiàn)這一小功能的 瘸彤。根據(jù)官方推薦,得先有一個 ViewModel 進行狀態(tài)數(shù)據(jù)的管理:

// code 2
class ZhuViewModel: ViewModel() {
    // 狀態(tài)數(shù)據(jù)初始化笛钝,初始化為空字符串
    var inputStr = mutableStateOf("")
    // 狀態(tài)更新方法钧栖,將新輸入的內(nèi)容賦值給 MutableState<T> 對象的 value 值
    fun onInputChange(inputContent: String) {
        inputStr.value = inputContent
    }
}

可以看出低零,ViewModel 中需要對狀態(tài)進行初始化,并且提供相應(yīng)的更新方法拯杠。同時 ViewModel 中不會出現(xiàn)任何與界面相關(guān)的對象掏婶,例如 Activity、Fragment潭陪、Context 等雄妥,為的就是解耦。

界面代碼就是 Composable 函數(shù)根據(jù) ViewModel 管理的 State 狀態(tài)進行展示:

// code 3
class ZhuStateActivity: AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val zhuViewModel by viewModels<ZhuViewModel>()
        setContent {InputShow(zhuViewModel)}
    }
}

@Composable
fun InputShow(viewModel: ZhuViewModel) {
    Column(Modifier.padding(20.dp)) {
        Text(text = viewModel.inputStr.value)
        TextField(
            value = viewModel.inputStr.value,
            onValueChange = { viewModel.onInputChange(it) }
        )
    }
}

TextField 組件相當于 EditText依溯,onValueChange 可獲取到用戶的輸入內(nèi)容老厌,在這里調(diào)用 ViewModel 中更新狀態(tài)的方法。這樣黎炉,所有引用了 ViewModel 中 MutableState 類型對象 inputStr 的組合項(Composable 函數(shù))枝秤,都會自動重繪更新,Text 組件就可以實時更新輸入的內(nèi)容了慷嗜。

4. remember 關(guān)鍵字

其實在 code 3 中的小功能使用 ViewModel 來管理 State 狀態(tài)有點小題大做了淀弹,可以用 remember 關(guān)鍵字來實現(xiàn)。這個關(guān)鍵字的作用如它的意思一樣庆械,“記住” 它所修飾的對象的值薇溃。下面的代碼就是沒有使用 ViewModel 的實現(xiàn)方法:

// code 4
class ZhuStateActivity: AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {InputShow()}
    }
}

@SuppressLint("UnrememberedMutableState")
@Composable
fun InputShow() {
    val inputStr = mutableStateOf("Hello")
    Column(Modifier.padding(20.dp)) {
        Text(text = inputStr.value)
        TextField(
            value = inputStr.value,
            onValueChange = {
                inputStr.value = it
            }
        )
    }
}

這里沒有使用 remember 會有紅線提醒,我先使用 SuppressLint 去掉了報錯缭乘,為的只是舉個栗子沐序,并且設(shè)置了默認展示 “Hello” 文案。運行一下堕绩,你會發(fā)現(xiàn)策幼,不管輸入什么,都只是展示 “Hello”奴紧,好像啥也沒有發(fā)生特姐。。绰寞。

這是為啥到逊?加一些 log 看看:

// code 5
@SuppressLint("UnrememberedMutableState")
@Composable
fun InputShow() {
    val inputStr = mutableStateOf("Hello")
    Log.d(TAG, "InputShow: Column inputStr = ${inputStr.value}")
    Column(Modifier.padding(20.dp)) {
        Text(text = inputStr.value)
        TextField(
            value = inputStr.value,
            onValueChange = {
                inputStr.value = it
                Log.d(TAG, "InputShow: onValueChange inputStr = $it")
            }
        )
    }
}

連續(xù)輸入字母 w铣口、o滤钱、r、l脑题、d件缸,打出來的 log 是這樣的:

圖 2

可見在每次輸入之后,都會觸發(fā) Composable 函數(shù)重新繪制叔遂,每次都會重新初始化 inputStr 這個狀態(tài)他炊,而初始值都是一樣的争剿,所以看起來就好像輸入不起作用。Composable 函數(shù)的重新繪制過程也被稱之為 重組痊末。

重組:使用新的輸入Event事件重新調(diào)用可組合項以更新 Compose 樹的過程蚕苇。這一過程會再次運行相同的 Composable 組件進行更新。

順帶說一下凿叠,Compose 首次運行渲染 Composable 組件時涩笤,會為所有被調(diào)用的 Composable 組件構(gòu)建一個樹,然后在重組期間會使用新的 Composable 組件去更新樹盒件。

再回到這個例子蹬碧,使用 remember 關(guān)鍵字就可以避免每次重組時都初始化為初始值。使用后的代碼為:

// code 6
@Composable
fun InputShow() {
    val inputStr = remember{ mutableStateOf("Hello") }
    Column(Modifier.padding(20.dp)) {
        Text(text = inputStr.value)
        TextField(
            value = inputStr.value,
            onValueChange = {
                inputStr.value = it
            }
        )
    }
}

這樣就可以正確實現(xiàn)功能了炒刁。其實 remember 關(guān)鍵字的使用是由兩部分組成:

  1. key arguments:表示這次調(diào)用使用的 “鍵”(key)恩沽,用圓括號包裹;
  2. calculation :一個 Lambda 表達式翔始,計算得出需要存儲的 “值”(value)罗心。

所以,remember 的用法如下所示:

// code 7
remember(key) { calculation: () -> T }

remember 關(guān)鍵字可以為 Composable 組件項提供一個數(shù)據(jù)存儲空間绽昏,系統(tǒng)會將由 calculation Lambda 表達式計算得出的值存儲到組合樹中协屡,只有當 remember 的 “鍵” 發(fā)生變化時,才會重新執(zhí)行 calculation 計算得出 value 并存儲起來全谤;否則還是原來的值肤晓。

當然 code 6 中并沒有設(shè)置 remember 的 key,這種情況下认然,remember 會默認該 key 沒有發(fā)生變化补憾,不會重新初始化,而是用之前的值卷员。

需要注意的點: remember 雖然會將數(shù)據(jù)或?qū)ο蟠鎯υ诮M合項中盈匾,但當調(diào)用 remember 的可組合項從組合樹中移除后,它會忘記該數(shù)據(jù)或?qū)ο蟊下狻K韵鞫灰谟刑砑踊蛞瞥?Composable 組件的情況下,使用 remember 將重要內(nèi)容存儲在 Composable 組件中未巫,因為添加和移除都會使得數(shù)據(jù)丟失窿撬。

5. 狀態(tài)提升

狀態(tài)提升的概念是對于 Composable 組件來說的,根據(jù) Composable 組件中是否含有 State 狀態(tài)可分為 有狀態(tài)可組合項無狀態(tài)可組合項叙凡。 如 code 6 中的 InputShow 組合項就是一個有狀態(tài)可組合項劈伴。

5.1 有狀態(tài)與無狀態(tài)

Flutter 中的 Widget 也是分為 StatefulWidget 和 StatelessWidget,想不到 Compose 也借用了這個設(shè)計思想握爷。

有狀態(tài)可組合項是一種具有可隨時間變化狀態(tài)的 Composable 組件跛璧。再說具體一點严里,就是 Composable 組件里有類似于 remember 存儲的狀態(tài),而且該組件會在內(nèi)部保持和改變自己的狀態(tài)追城。調(diào)用方不需要控制狀態(tài)刹碾。缺點是,具有內(nèi)部狀態(tài)的可組合項復(fù)用性往往不高座柱,也更難以測試教硫。

無狀態(tài)可組合項就是指無法直接更改任何狀態(tài)的 Composable 組件。因為不包含任何狀態(tài)數(shù)據(jù)辆布,所以它更容易測試瞬矩,復(fù)用性也更高。

如果需要將有狀態(tài)組合項轉(zhuǎn)變?yōu)闊o狀態(tài)組合項锋玲,則需要 狀態(tài)提升景用。

5.2 狀態(tài)提升怎么做?

Compose 中的狀態(tài)提升是一種將狀態(tài)移至可組合項的調(diào)用方以使可組合項無狀態(tài)的模式惭蹂。常規(guī)的狀態(tài)提升模式是將狀態(tài)變量替換為兩個參數(shù):

  1. value: T:要顯示的當前值伞插;
  2. onValueChange: (T) -> Unit:請求更改值的事件,其中的 T 是新值

這種方式提升的狀態(tài)具有一些重要的屬性:

  1. 單一可信來源: 狀態(tài)提升并不是將狀態(tài)復(fù)制盾碗,而是將狀態(tài)移動到上層的可組合項中媚污,這樣可確保只有一個可信來源,減少數(shù)據(jù)不一致所導致的 bug廷雅;
  2. 封裝: 只有有狀態(tài)可組合項可以修改其狀態(tài)耗美,可以理解為是內(nèi)部“自治”的;
  3. 可共享: 提升后的狀態(tài)可以與多個可組合項共享航缀;
  4. 可攔截: 無狀態(tài)可組合項的調(diào)用方可以在更改狀態(tài)之前決定忽略或者修改事件商架;
  5. 解耦: 無狀態(tài)可組合項的狀態(tài)可以存儲在任何位置,如 ViewModel 中芥玉。

具體怎么做可以看下面的一個小栗子蛇摸。

5.3 狀態(tài)提升小栗子

根據(jù)上述所說,很容易就可以得知 code 6 的 InputShow Composable 組件是一個有狀態(tài)的可組合項灿巧,它包含一個狀態(tài)變量 inputStr赶袄,所以,我們要將這個 MutableState 用兩個參數(shù)進行替換抠藕,一個是要顯示的當前值饿肺;另一個是 Lambda 表達式,用于請求更改值的事件幢痘,就可以將其改寫為一個無狀態(tài)可組合項唬格。如下 code 8 所示:

// code 8 無狀態(tài)可組合項 InputShow
@Composable
fun InputShow(inputText: String, onInputChange: (String)-> Unit) {
    Column(Modifier.padding(20.dp)) {
        Text(text = inputText)
        TextField(
            value = inputText,
            onValueChange = onInputChange
        )
    }
}

那狀態(tài)提升到哪里去了呢家破?通常會提升到它的父組件中颜说,那么父組件就是一個有狀態(tài)的可組合項了购岗,這個例子中 InputShow 的父組件這里定義為 InputShowContainer:

// code 9
@Composable
fun InputShowContainer() {
    val (inputStr, setInput) = remember{ mutableStateOf("") }
    InputShow(inputStr, setInput)
}

嗯?MutableState 的聲明與之前的不太一樣了门粪,多出來的這個 setInput 也是一個 Lambda 表達式喊积,用于更新值。其實玄妈,聲明 MutableState 對象的方法總共有三種:

  1. val mutableState = remember{ mutableStateOf(default) }
  2. val value by remember{ mutableStateOf(default) }
  3. val (value, setValue) = remember{ mutableStateOf(default) }

所以這里用的是第三種聲明方法乾吻。這樣,InputShow 組合項就經(jīng)過狀態(tài)提升變?yōu)榱藷o狀態(tài)的可組合項了拟蜻。官方在這里還特意說明绎签,在 Composable 組件中創(chuàng)建 State<T>(或其他有狀態(tài)對象)時,務(wù)必對其執(zhí)行 remember 操作酝锅,否則它會在每次重組時重新初始化诡必。

6. 狀態(tài)存儲的其他方式

由前述所說,remember 關(guān)鍵字可存儲組合項中的狀態(tài)搔扁,但是一旦組合項被移動爸舒,這些狀態(tài)就丟失了,那如果涉及到橫豎屏切換等 Activity 重建的應(yīng)用場景稿蹲,該怎么辦呢扭勉?雖然保存在 ViewModel 中可以解決問題,但總有點小題大做了苛聘。下面是狀態(tài)存儲的一些其他的方式涂炎。

6.1 rememberSaveable

這個與 remember 類似,主要用于 Activity 或進程重建時设哗,恢復(fù)界面狀態(tài)璧尸。還是上面 code 6 的栗子,可以試試橫豎屏切換或其他配置項更改熬拒,會發(fā)現(xiàn)使用 remember 關(guān)鍵字時爷光,切換后就回到初始空白值了。改為 rememberSaveable 后切換后輸入的內(nèi)容可以保存下來而不會被重置澎粟。

這么看的話蛀序,rememberSaveable 有點像是 override fun onSaveInstanceState(outState: Bundle)
方法了,確實是這樣的活烙,任何可以存儲在 Bundle 對象中的數(shù)據(jù)都可以通過 rememberSaveable 進行保存徐裸。無法用 Bundle 進行保存的數(shù)據(jù),可以用下面的方式進行存儲啸盏。

6.2 Parcelize

最簡單的解決方法就是在對象上添加 @Parcelize 注解重贺,對象就可以轉(zhuǎn)化為可打包狀態(tài)且可以捆綁。還記得 Java 中的 Serializable 接口嗎?是一樣的作用气笙,都是將實例對象編碼成字節(jié)流進行存儲次企。

在日常 Android 開發(fā)中如果不涉及到本地化存儲或者網(wǎng)絡(luò)傳輸?shù)那闆r,推薦使用 Parcelable潜圃,因為相比于 Serializable 它不會產(chǎn)生大量臨時對象缸棵,沒有使用反射,效率更高谭期。但很多時候不想寫 Parcelable 接口的模板代碼堵第,那么就可以使用這個注解!下面是樣例及使用步驟:

// code 10
// app/build.gradle
plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-parcelize'    // 第一步:添加此插件
}

@Parcelize    // 第二步:添加注解及 Parcelable 接口
data class City(val name: String, val country: String) : Parcelable

// 這樣就可以將其保存到狀態(tài)中
val cityBean = rememberSaveable{ mutableStateOf(City("0112","西京"))}

終于隧出,Parcelable 和 Serializable 接口一樣好用了踏志!

6.3 MapSaver

Compose 還考慮到有些情況下 Parcelize 不適用的場景,那么還可以使用 MapSaver 來定義自己的存儲和恢復(fù)規(guī)則胀瞪,規(guī)定如何把對象轉(zhuǎn)為可保存到 Bundle 中的值狰贯。

// code 11
data class Book(val name: String, val author: String)

val BookSaver = run {
    val nameKey = "Name"
    val authorKey = "Author"
    mapSaver(
        save = { mapOf(nameKey to it.name, authorKey to it.author) },
        restore = { Book(it[nameKey] as String, it[authorKey] as String) }
    )
}

val chosenBook = rememberSaveable( stateSaver = BookSaver ) {
    mutableStateOf(Book("三體","劉慈欣"))
}

核心在 BookSaver 這個 Saver 對象,通過 save 這個 lambda 可以將 Book 對象轉(zhuǎn)化為一個 Map 進行存儲赏廓;要使用的時候就通過 restore 這個 lambda 將 Map 又恢復(fù)為一個 Book 對象涵紊。

6.4 ListSaver

MapSaver 需要自己去定義 Key 值,但使用 ListSaver 就可以不用自己定義 Key幔摸,本質(zhì)上是把對象放在一個 List 中存儲摸柄,所以它是使用索引作為 Key。

// code 12
val BookListSaver = listSaver<Book, Any>(
    save = { listOf(it.name, it.author) },
    restore = { Book(it[0] as String, it[1] as String) }
)

使用起來與 MapSaver 一樣既忆,只不過存儲的數(shù)據(jù)結(jié)構(gòu)不一樣罷了驱负。實際上,MapSaver 底層也是用 ListSaver 實現(xiàn)的患雇。

總結(jié)

最后來個總結(jié)吧跃脊。

  • Compose 為了實現(xiàn)解耦將界面和數(shù)據(jù)分離開來,分別稱之為 組合 與 State 狀態(tài)苛吱。為了達到狀態(tài)改變自動重組界面的目的酪术,引入了 MutableState<T> 來存儲 State 狀態(tài)的容器。
  • MutableState<T> 的 value 一旦改變翠储,所有引用它的 Composable 組件都會重組绘雁,從而保證了數(shù)據(jù)與顯示的一致性。此外援所,為了保證每次重組時 State 狀態(tài)不會被初始化為初值庐舟,Compose 引入 remember 關(guān)鍵字來將數(shù)據(jù)存儲在相應(yīng)的 Composable 組件中。
  • remember 關(guān)鍵字是根據(jù)傳入的鍵是否改變來返回相應(yīng)的值住拭。鍵改變了則返回初值挪略;鍵未變則返回上次存儲的值历帚。不設(shè)置鍵,則默認鍵始終不變杠娱,即始終取上次的值挽牢。
  • 為了解決 remember 關(guān)鍵字不能在 Activity 重建等場景下保存數(shù)據(jù)而引入了 rememberSaveable、MapSaver墨辛、ListSaver 等狀態(tài)保存及恢復(fù)的方法。
  • Compose 把 Composable 組件分為有狀態(tài)與無狀態(tài)兩類趴俘,內(nèi)部含有 State 狀態(tài)的就為有狀態(tài)可組合項睹簇;反之則為無狀態(tài)組合項。無狀態(tài)組合項復(fù)用性更高寥闪,而有狀態(tài)組合項可以自己管理State狀態(tài)太惠。通過狀態(tài)提升可以將有狀態(tài)組合項轉(zhuǎn)化為無狀態(tài)組合項。
  • Compose 推薦使用 ViewModel 來管理狀態(tài)疲憋,包括狀態(tài)的更新以及存儲等凿渊。

參考文獻

  1. 官方文檔——在Jetpack Compose 中使用狀態(tài) https://developer.android.google.cn/codelabs/jetpack-compose-state?
  2. Compose 狀態(tài)與組合 新小夢 https://juejin.cn/post/6937560914254102565
  3. 【背上Jetpack之ViewModel】即使您不使用MVVM也要了解ViewModel ——ViewModel 的職能邊界 Flywith24 https://juejin.cn/post/6844904100493017095
  4. Jetpack Compose學習之mutableStateOf與remember是什么 柚子君下 https://blog.csdn.net/weixin_43662090/article/details/116120540
  5. 官方文檔——狀態(tài)和 Jetpack Compose https://developer.android.google.cn/jetpack/compose/state

更多內(nèi)容,歡迎關(guān)注我的同名公眾號留言交流~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缚柳,一起剝皮案震驚了整個濱河市埃脏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌秋忙,老刑警劉巖彩掐,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異灰追,居然都是意外死亡堵幽,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進店門弹澎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來朴下,“玉大人,你說我怎么就攤上這事苦蒿∨闺剩” “怎么了?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵佩迟,是天一觀的道長溃肪。 經(jīng)常有香客問我,道長音五,這世上最難降的妖魔是什么惫撰? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮躺涝,結(jié)果婚禮上厨钻,老公的妹妹穿的比我還像新娘扼雏。我一直安慰自己,他們只是感情好夯膀,可當我...
    茶點故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布诗充。 她就那樣靜靜地躺著,像睡著了一般诱建。 火紅的嫁衣襯著肌膚如雪蝴蜓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天俺猿,我揣著相機與錄音茎匠,去河邊找鬼。 笑死押袍,一個胖子當著我的面吹牛诵冒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播谊惭,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼汽馋,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了圈盔?” 一聲冷哼從身側(cè)響起豹芯,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎驱敲,沒想到半個月后告组,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡癌佩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年木缝,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片围辙。...
    茶點故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡我碟,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出姚建,到底是詐尸還是另有隱情矫俺,我是刑警寧澤,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布掸冤,位于F島的核電站厘托,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏稿湿。R本人自食惡果不足惜铅匹,卻給世界環(huán)境...
    茶點故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望饺藤。 院中可真熱鬧包斑,春花似錦流礁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至萌抵,卻和暖如春找御,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背绍填。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工霎桅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人沐兰。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓哆档,卻偏偏與公主長得像蔽挠,于是被迫代替她去往敵國和親住闯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,876評論 2 361

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