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阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335