1 Widget簡介

原文在此,此處只為學習

Widget與Element
Widget主要接口
Stateless Widget
Stateful Widget
State
State生命周期
狀態管理
Widget管理自身狀態
父widget管理子widget的State
混合管理
Flutter widget庫介紹
Material widget
Cupertino widget
總結

概念

在Flutter中,幾乎所有的對象都是一個Widget,與原生開發中的“控件”不同的是,Flutter中的widget的概念更廣泛,它不僅可以表示UI元素(例如class Text extends StatelessWidgetclass StatelessWidget extends Widget),也可以表示一些功能性的組件如:用于手勢檢測的 GestureDetector widget(class GestureDetector extends StatelessWidget )、用于應用主題數據傳遞的Theme(class Theme extends StatelessWidget)等等。

Widget與Element

在Flutter中,Widget的功能是“描述一個UI元素的配置數據”,它就是說,Widget其實并不是表示最終繪制在設備屏幕上的顯示元素,而只是顯示元素的一個配置數據。實際上,Flutter中真正代表屏幕上顯示元素的類是Element,也就是說Widget只是描述Element的一個配置,有關Element的詳細介紹我們將在本書后面的高級部分深入介紹,讀者現在只需要知道,Widget只是UI元素的一個配置數據,并且一個Widget可以對應多個Element,這是因為同一個Widget對象可以被添加到UI樹的不同部分,而真正渲染時,UI樹的每一個節點都會對應一個Element對象。總結一下:

  • Widget實際上就是Element的配置數據,Widget樹實際上是一個配置樹,而真正的UI渲染樹是由Element構成;不過,由于Element是通過Widget生成,所以它們之間有對應關系,所以在表述上,我們可以寬泛的認為Widget樹就是指UI控件樹或UI渲染樹。
  • 一個Widget對象可以對應多個Element對象。

主要接口

1 構造器
2 布局Element
3 診斷樹
4 復用機制

abstract class Widget extends DiagnosticableTree {
  /// 構造器
  const Widget({ this.key });
  final Key key;

 
  @protected
  Element createElement();

  /// A short, textual description of this widget.
  @override
  String toStringShort() {
    return key == null ? '$runtimeType' : '$runtimeType-$key';
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
  }


  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}
  • Widget類繼承自DiagnosticableTreeDiagnosticableTree即“診斷樹”,主要作用是提供調試信息.
  • Key: 這個key屬性類似于React/Vue中的key,主要的作用是決定是否在下一次build時復用舊的widget,決定的條件在canUpdate()方法中。
  • createElement():正如前文所述“一個Widget可以對應多個Element”;Flutter Framework在構建UI樹時,會先調用此方法生成對應節點的Element對象。此方法是Flutter Framework隱式調用的,在我們開發過程中基本不會調用到。
  • debugFillProperties(...)復寫父類的方法,主要是設置診斷樹的一些特性。
  • canUpdate(...)是一個靜態方法,它主要用于在Widget樹重新build即創建復用舊的widget,其實具體來說,應該是:是否用新的Widget對象去更新舊UI樹上所對應的Element對象的配置;通過其源碼我們可以看到,只要newWidget與oldWidget的runtimeType和key同時相等時就會用newWidget去更新Element對象的配置,否則就會創建新的Element

另外Widget類本身是一個抽象類,其中最核心的就是定義了createElement()接口,在Flutter開發中,我們一般都不用直接繼承Widget類來實現Widget,相反,我們通常會通過繼承StatelessWidgetStatefulWidget來間接繼承Widget類來實現,而StatelessWidget和StatefulWidget都是直接繼承自Widget類,而這兩個類也正是Flutter中非常重要的兩個抽象類,它們引入了兩種Widget模型,接下來我們將重點介紹一下這兩個類。

Stateless Widget

在之前的章節中,我們已經簡單介紹過StatelessWidget,StatelessWidget相對比較簡單,它繼承自Widget,重寫了createElement()方法:

abstract class StatelessWidget extends Widget {
  /// Initializes [key] for subclasses.
  const StatelessWidget({ Key key }) : super(key: key);

  @override
  StatelessElement createElement() => StatelessElement(this);

  @protected
  Widget build(BuildContext context);
}

而StatelessElement 集成關系如下所示:

class StatelessElement extends ComponentElement
abstract class ComponentElement extends Element
abstract class Element extends DiagnosticableTree implements BuildContext
abstract class Diagnosticable
abstract class BuildContext

StatelessWidget用于不需要維護狀態的場景,它通常在build方法中通過嵌套其它Widget來構建UI,在構建過程中會遞歸構建 其嵌套的Widget。我們看一個簡單的例子:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());


class MyApp extends StatelessWidget{

   @override
   Widget build(BuildContext context){

     return MaterialApp(
       title: 'Flutter Demo',
       theme: ThemeData(
         primarySwatch: Colors.purple,
       ),
       home:new Echo(text:'我就是一段文字')
     );
   }
}


class Echo extends StatelessWidget{

  final String text;
  final Color bgColor;

  /*
  按照慣例,widget的構造函數應使用命名參數(例如:Echo),
  命名參數中的必要參數要添加@required標注,這樣有利于靜態代碼分析器進行檢查,
  另外,在繼承widget時,第一個參數通常應該是Key,
  如果接受子widget的child參數,那么通常應該將它放在參數列表的最后。
  同樣是按照慣例,widget的屬性應被聲明為final,防止被意外改變。
  */
  const Echo({ 
    Key key,

    @required this.text,

    this.bgColor:Colors.blue,

    }):super(key:key);


  Widget build(BuildContext context){

    return Scaffold(

      appBar: AppBar(

        title:Text('我就是個導航'),
        backgroundColor:bgColor,
      ),
      body: Center(

        child: Column(
          children: <Widget>[
              Text(
                text,
              )
          ],
        ),
      ),
    );
  }
}

運行效果:


Simulator Screen Shot - iPhone X - 2018-12-24 at 11.18.29.png

Stateful Widget

StatelessWidget一樣,StatefulWidget也是繼承自widget類,并重寫了createElement()方法,不同的是返回的Element 對象并不相同;另外StatefulWidget類中添加了一個新的接口createState(),下面我們看看StatefulWidget的類定義:

abstract class StatefulWidget extends Widget {
  /// 構造器
  const StatefulWidget({ Key key }) : super(key: key);

  StatefulElement createElement() => StatefulElement(this);

///比Stateless Widget新增了一個接口
  @protected
  State createState();
}
  • StatefulElement間接繼承自Element類,與StatefulWidget相對應(作為其配置數據)。StatefulElement中可能會多次調用createState()來創建狀態(State)對象
  • createState() 用于創建和Stateful widget相關的狀態,它在Stateful widget的生命周期中可能會被多次調用。例如,當一個Stateful widget同時插入到widget樹的多個位置時(例如:列表中子Widget),Flutter framework就會調用該方法為每一個位置生成一個獨立的State實例,其實,本質上就是一個StatefulElement對應一個State實例

State

一個StatefulWidget類會對應一個State類,State表示與其對應的StatefulWidget要維護的狀態,State中的保存的狀態信息可以:

  • 1 在widget build時可以被同步讀取。
  • 2 在widget生命周期中可以被改變,當State被改變時,可以手動調用其setState()方法通知Flutter framework狀態發生改變,Flutter framework在收到消息后,會重新調用其build方法重新構建widget樹,從而達到更新UI的目的

State中有兩個常用屬性:

  • 1 widget,它表示與該State實例關聯的widget實例,由Flutter framework動態設置。注意,這種關聯并非永久的,因為在應用聲明周期中,UI樹上的某一個節點的widget實例在重新構建時可能會變化,但State實例只會在第一次插入到樹中時被創建,當在重新構建時,如果widget被修改了,Flutter framework會動態設置State.widget為新的widget實例。
 T get widget => _widget;
  T _widget;
  • 2 context,它是BuildContext類的一個實例,表示構建widget的上下文,它是操作widget在樹中位置的一個句柄,它包含了一些查找、遍歷當前Widget樹的一些方法。每一個widget都有一個自己的context對象。

State生命周期

理解State的生命周期對flutter開發非常重要,為了加深讀者印象,本節我們通過一個實例來演示一下State的生命周期。在接下來的示例中,我們實現一個計數器widget,點擊它可以使計數器加1,由于要保存計數器的數值狀態,所以我們應繼承StatefulWidget,代碼如下:

class CounterWidget extends StatefulWidget{

  const CounterWidget({
    Key key,
    this.initValue:0,
  }):super(key:key);

  final int  initValue;

  @override
  _CounterWidgetState createState() => new _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget>{

  int _count;

  @override
  void initState(){
    super.initState();

    _count = widget.initValue;
    print("initState");
  }

  @override
  Widget build(BuildContext context){

    print('build');
    return  Center(

      child: FlatButton(

        child: Text('$_count'),
        onPressed: () => setState(() => ++_count),
      ),
    );
  }

  @override
  void didUpdateWidget(CounterWidget oldWidget) { 
    super.didUpdateWidget(oldWidget);

    print("didUpdateWidget");

  }

  //無效
  @override
  void deactivate(){
    super.deactivate();

    print("deactivate");
  }

  @override
  void dispose() {
    super.dispose();
    print("dispose");
  }

  @override
  void reassemble() {
    super.reassemble();
    print("reassemble");
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("didChangeDependencies");
  }
}

接下來,我們創建一個新路由,在新路由中,我們只顯示一個CounterWidget:

Widget build(BuildContext context) {
  return CounterWidget();
}

我們運行應用并打開該路由頁面,在新路由頁打開后,屏幕中央就會出現一個數字0,然后控制臺日志輸出

Launching lib/main.dart on iPhone X in debug mode...
Xcode build done.                                           17.0s
flutter: initState
flutter: didChangeDependencies
flutter: build

可以看到,在StatefulWidget插入到Widget樹時首先initState方法會被調用。
然后我們點擊??按鈕熱重載,控制臺輸出日志如下:

flutter: reassemble
flutter: didUpdateWidget
flutter: build
Reloaded 0 of 419 libraries in 1,082ms.

可以看到此時initState 和didChangeDependencies都沒有被調用,而此時didUpdateWidget被調用。
接下來,我們在widget樹中移除CounterWidget,將路由build方法改為:

Widget build(BuildContext context) {
  //移除計數器 
  //return CounterWidget();
  //隨便返回一個Text()
  return Text("xxx");
}

然后熱重載,日志如下:

flutter: reassemble
flutter: deactivate
flutter: dispose
Reloaded 1 of 419 libraries in 1,085ms.

我們可以看到,在CounterWidget從widget樹中移除時,deactive和dispose會依次被調用。

下面我們來看看各個回調函數:

  • initState:當Widget第一次插入到Widget樹時會被調用,對于每一個State對象,Flutter framework只會調用一次該回調,所以,通常在該回調中做一些一次性的操作,如狀態初始化、訂閱子樹的事件通知等。不能在該回調中調用BuildContext.inheritFromWidgetOfExactType(該方法用于在Widget樹上獲取離當前widget最近的一個父級InheritFromWidget,關于InheritedWidget我們將在后面章節介紹),原因是在初始化完成后,Widget樹中的InheritFromWidget也可能會發生變化,所以正確的做法應該在在build()方法didChangeDependencies()中調用它。

  • didChangeDependencies():當State對象的依賴發生變化時會被調用;例如:在之前build() 中包含了一個InheritedWidget,然后在之后的build() 中InheritedWidget發生了變化,那么此時InheritedWidget的子widget的didChangeDependencies()回調都會被調用。典型的場景是當系統語言Locale或應用主題改變時,Flutter framework會通知widget調用此回調。

  • build():此回調讀者現在應該已經相當熟悉了,它主要是用于構建Widget子樹的,會在如下場景被調用:

1 在調用initState()之后。
2 在調用didUpdateWidget()之后。
3 在調用setState()之后。
4 在調用didChangeDependencies()之后。
5 在State對象從樹中一個位置移除后(會調用deactivate)又重新插入到樹的其它位置之后。

  • reassemble():此回調是專門為了開發調試而提供的,在熱重載(hot reload)時會被調用,此回調在Release模式下永遠不會被調用。
  • didUpdateWidget():在widget重新構建時,Flutter framework會調用Widget.canUpdate來檢測Widget樹中同一位置的新舊節點,然后決定是否需要更新,如果Widget.canUpdate返回true則會調用此回調。正如之前所述,Widget.canUpdate會在新舊widget的key和runtimeType同時相等時會返回true,也就是說在在新舊widget的key和runtimeType同時相等時didUpdateWidget()就會被調用。
  • deactivate():當State對象從樹中被移除時,會調用此回調。在一些場景下,Flutter framework會將State對象重新插到樹中,如包含此State對象的子樹在樹的一個位置移動到另一個位置時(可以通過GlobalKey來實現)。如果移除后沒有重新插入到樹中則緊接著會調用dispose()方法。
  • dispose():當State對象從樹中被永久移除時調用;通常在此回調中釋放資源。

狀態管理

響應式的編程框架中都會有一個永恒的主題——“狀態管理”,無論是在React/Vue(兩者都是支持響應式編程的web開發框架)還是Flutter,他們討論的問題和解決的思想都是一致的。所以,如果你對React/Vue的狀態管理有了解,可以跳過本節。言歸正傳,我們想一個問題,stateful widget的狀態應該被誰管理?widget本身?父widget?都會?還是另一個對象?答案是取決于實際情況!以下是管理狀態的最常見的方法:

  • Widget管理自己的state。
  • 父widget管理子widget狀態。
  • 混合管理(父widget和子widget都管理狀態)。

如何決定使用哪種管理方法?以下原則可以幫助你決定:

  • 如果狀態是用戶數據,如復選框的選中狀態、滑塊的位置,則該狀態最好由父widget管理。
  • 如果狀態是有關界面外觀效果的,例如顏色、動畫,那么狀態最好由widget本身來管理。
  • 如果某一個狀態是不同widget共享的則最好由它們共同的父widget管理。

接下來,我們將通過創建三個簡單示例TapboxA、TapboxB和TapboxC來說明管理狀態的不同方式。 這些例子功能是相似的 ——創建一個盒子,當點擊它時,盒子背景會在綠色與灰色之間切換。狀態 _active確定顏色:綠色為true ,灰色為false。


未命名.png

Widget管理自身狀態

_TapboxAState 類:

  • 管理TapboxA的狀態。
  • 定義_active:確定盒子的當前顏色的布爾值。
  • 定義_handleTap()函數,該函數在點擊該盒子時更新_active,并調用setState()更新UI。
  • 實現widget的所有交互式行為。
class TapBoxA extends StatefulWidget{

  @override
  _TapBoxAState createState() => _TapBoxAState();
}
class _TapBoxAState extends State<TapBoxA>{

  bool _active = false;

  void _handleTap(){
    setState(() {
          _active = !_active;
        });
  }
  @override
  Widget build(BuildContext context){

    return new GestureDetector(

      onTap: _handleTap,

      child: new Container(
        width: 200,
        height: 200,
        decoration: new BoxDecoration(
          color: _active?Colors.lightGreen[700]:Colors.green[600],
        ),
        child: new Center(
          
          child: new Text(
            _active ?"Active":"Inactive",
            style: new TextStyle(
              fontSize: 32,
              color: Colors.white,
            ),
          ),
        ),
      ),
    );
  }
}

父widget管理子widget的State

對于父widget來說,管理狀態并告訴其子widget何時更新通常是比較好的方式。 例如,IconButton是一個圖片按鈕,但它是一個無狀態的widget,因為我們認為父widget需要知道該按鈕是否被點擊來采取相應的處理。

在以下示例中,TapboxB通過回調將其狀態導出到其父項。由于TapboxB不管理任何狀態,因此它的父類為StatelessWidget

ParentWidgetState 類:

  • 為TapboxB 管理_active狀態.
  • 實現_handleTapboxChanged(),當盒子被點擊時調用的方法.
  • 當狀態改變時,調用setState()更新UI.

TapboxB 類:

  • 繼承StatelessWidget類,因為所有狀態都由其父widget處理。
  • 當檢測到點擊時,它會通知父widget。
//父widget管理子widget的state
class ParentWidget extends StatefulWidget{

   @override
  _ParentWidgetState createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget>{

  bool _active = false;

  void _handleTapBoxChanged(bool newActive){

    setState(() {
          _active =  newActive;
        });
  }
  @override
  Widget build(BuildContext context) {
    return new Container(
      child: new TapBoxB(
        active: _active,
        onChanged: _handleTapBoxChanged,
      ),
    );
  }
}

class TapBoxB extends  StatelessWidget{

  const TapBoxB({
    Key key,
    this.active:false,
    @required this.onChanged,
  }):super(key:key);

  final bool active;
  final ValueChanged<bool> onChanged;

  void _handleTap() {
    onChanged(!active);
  }
  
  Widget build(BuildContext context){

    return new GestureDetector(

      onTap: _handleTap,
      child: new Container(
        width: 200,
        height: 200,
        decoration: new BoxDecoration(
          color: active?Colors.lightGreen[700]:Colors.red[600],
        ),
        child: new Center(
          
          child:  new Text(
            active?"Active":"Inactive",

            style: new TextStyle(
              fontSize: 32,
              color: Colors.red
            ),
          ),
        ),
      ),
    );
  }
}

混合管理

注意頁面build都是放在管理State中的

對于一些widget來說,混和管理的方式非常有用。在這種情況下,widget自身管理一些內部狀態,而父widget管理一些其他外部狀態
在下面TapboxC示例中,點擊時,盒子的周圍會出現一個深綠色的邊框。點擊時,邊框消失,盒子的顏色改變。 TapboxC將其_active狀態導出到其父widget中,但在內部管理其_highlight狀態。這個例子有兩個狀態對象_ParentWidgetState和_TapboxCState。

_ParentWidgetStateC 對象:

  • 管理_active 狀態。
  • 實現 _handleTapboxChanged() ,當盒子被點擊時調用。
  • 當點擊盒子并且_active狀態改變時調用setState()更新UI。
    _TapboxCState 對象:

管理_highlight state。

  • GestureDetector監聽所有tap事件。當用戶點下時,它添加高亮(深綠色邊框);當用戶釋放時,會移除高亮。
  • 當按下、抬起、或者取消點擊時更新_highlight狀態,調用setState()更新UI。
  • 當點擊時,將狀態的改變傳遞給父widget.
class ParentWidgetC extends StatefulWidget{

   @override
  _ParentWidgetCState createState() => _ParentWidgetCState();
}

class _ParentWidgetCState extends State<ParentWidgetC>{

  bool _active = false;

  void _handleTapBoxChanged(bool newActive){

    setState(() {
          _active =  newActive;
        });
  }
  @override
  Widget build(BuildContext context) {
    return new Container(
      child: new TapBoxC(
        active: _active,
        onChanged: _handleTapBoxChanged,
      ),
    );
  }
}

class TapBoxC extends  StatefulWidget{

  const TapBoxC({
    Key key,
    this.active:false,
    @required this.onChanged,
  }):super(key:key);

  final bool active;
  final ValueChanged<bool> onChanged;

   @override
  _TapBoxCState createState() => _TapBoxCState();
}

class _TapBoxCState extends State<TapBoxC>{

  bool _hightlight = false;

    void _handleTapDown(TapDownDetails details) {
    setState(() {
      _hightlight = true;
    });
  }

  void _handleTapUp(TapUpDetails details) {
    setState(() {
      _hightlight = false;
    });
  }

  void _handleTapCancel() {
    setState(() {
      _hightlight = false;
    });
  }

  void _handleTap() {
    widget.onChanged(!widget.active);
  }
  Widget build(BuildContext context){

    return new GestureDetector(

      onTap: _handleTap,
      onTapDown: _handleTapDown,
      onTapUp: _handleTapUp,
      onTapCancel: _handleTapCancel,

      child: new Container(
        width: 200,
        height: 200,
        decoration: new BoxDecoration(
          color: widget.active?Colors.lightGreen[700]:Colors.red[600],
          border: _hightlight
          ? new Border.all(
            color: Colors.teal[700],
            width: 10
          )
          : null
        ),
        child: new Center(
          
          child:  new Text(
            widget.active?"Active":"Inactive",

            style: new TextStyle(
              fontSize: 32,
              color: Colors.red
            ),
          ),
        ),
      ),
    );
  }

}

全局狀態管理

當應用中包括一些跨widget(甚至跨路由)的狀態需要同步時,上面介紹的方法很難勝任了。比如,我們有一個設置頁,里面可以設置應用語言,但是我們為了讓設置實時生效,我們期望在語言狀態發生改變時,我們的APP Widget能夠重新build一下,但我們的APP Widget和設置頁并不在一起。正確的做法是通過一個全局狀態管理器來處理這種“相距較遠”的widget之間的通信。目前主要有兩種辦法:

  • 實現一個全局的事件總線,將語言狀態改變對應為一個事件,然后在APP Widget所在的父widgetinitState 方法中訂閱語言改變的事件,當用戶在設置頁切換語言后,我們觸發語言改變事件,然后APP Widget那邊就會收到通知,然后重新build一下即可。
  • 使用redux這樣的全局狀態包,讀者可以在pub上查看其詳細信息。

Flutter widget庫介紹

Flutter提供了一套豐富、強大的基礎widget,在基礎widget庫之上Flutter又提供了一套Material風格(Android默認的視覺風格)和一套Cupertino風格(iOS視覺風格)的widget庫。要使用基礎widget庫,需要先導入:

import 'package:flutter/material.dart';

Material widget

Flutter提供了一套豐富的Material widget,可幫助您構建遵循Material Design的應用程序。Material應用程序以MaterialApp widget開始, 該widget在應用程序的根部創建了一些有用的widget,比如一個Theme,它配置了應用的主題。 是否使用MaterialApp完全是可選的,但是使用它是一個很好的做法。在之前的示例中,我們已經使用過多個Material widget了,如:ScaffoldAppBarFlatButton等。要使用Material widget,需要先引入它:

import 'package:flutter/material.dart';

Cupertino widget

Flutter也提供了一套豐富的Cupertino風格的widget,盡管目前還沒有Material widget那么豐富,但也在不斷的完善中。值得一提的是在Material widget庫中,有一些widget可以根據實際運行平臺來切換表現風格,比如MaterialPageRoute,在路由切換時,如果是Android系統,它將會使用Android系統默認的頁面切換動畫(從底向上),如果是iOS系統時,它會使用iOS系統默認的頁面切換動畫(從右向左)。由于在前面的示例中還沒有Cupertino widget的示例,我們實現一個簡單的Cupertino頁面:


//導入cupertino widget庫
import 'package:flutter/cupertino.dart';

class CupertinoTestRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: Text("Cupertino Demo"),
      ),
      child: Center(
        child: CupertinoButton(
            color: CupertinoColors.activeBlue,
            child: Text("Press"),
            onPressed: () {}
        ),
      ),
    );
  }
}

總結

Flutter提供了豐富的widget,在實際的開發中你可以隨意使用它們,不要怕引入過多widget庫會讓你的應用安裝包變大,這不是web開發,dart在編譯時只會編譯你使用了的代碼。由于Material和Cupertino都是在基礎widget庫之上的,所以如果你的應用中引入了這兩者之一,則不需要再引入flutter/widgets.dart了,因為它們內部已經引入過了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374