從GestureDetector組件看Flutter 觸摸事件處理機(jī)制

GestureDetector

Flutter里有個(gè)神奇的組件——GestureDetector帝簇,學(xué)習(xí)過flutter的朋友應(yīng)該知道澈蚌,flutter的組件機(jī)制還是跟Android原生有很多不同的地方,flutter講究一切皆組件,這方面很解耦,套起來也方便淑翼,比如Text組件,在android里只要是控件就可以設(shè)置setOnclickListener品追,或者重寫View的onTouch事件去處理點(diǎn)擊玄括,但flutter不是這樣,它大部分組件并沒有點(diǎn)擊的屬性肉瓦,需要套一個(gè)GestureDetector組件來實(shí)現(xiàn)遭京,關(guān)于源碼中對(duì)GestureDetector是這么描述的:

///  * [GestureDetector], a less flexible but much simpler widget that does the same thing.

意思就是一個(gè)不太靈活但更簡(jiǎn)單更方便使用的一個(gè)組件,也就是封裝過的泞莉,看源碼也確實(shí)是RawGestureDetector的封裝哪雕,暫且不談,下面我們來看看這個(gè)組件有哪些屬性:

   GestureDetector({

   Key key, //唯一標(biāo)識(shí)

   this.child,//起作用的子組件

   this.onTapDown,//觸摸屏幕按下(子組件區(qū)域)

   this.onTapUp,//觸摸抬起(子組件區(qū)域)

   this.onTap,//發(fā)生了點(diǎn)擊事件戒财,觸發(fā)順序是onTapDown->onTapUp->onTap热监,中斷則不觸發(fā),事件傳遞樹的最末端

   this.onTapCancel,//按下在onTapUp事件發(fā)生前饮寞,手指離開組件區(qū)域觸發(fā)

   this.onDoubleTap,//雙擊孝扛,當(dāng)設(shè)有onTap單擊事件時(shí),不響應(yīng)

   this.onLongPress,//長(zhǎng)按幽崩,當(dāng)響應(yīng)長(zhǎng)按事件苦始,onTap回調(diào)cancel事件

   this.onLongPressStart,//長(zhǎng)按開始

   this.onLongPressEnd,//長(zhǎng)按結(jié)束

   this.onLongPressUp,//長(zhǎng)按手抬起

   this.onVerticalDragDown,//設(shè)置有長(zhǎng)按事件或者無拖動(dòng)抬起,則會(huì)回調(diào)cancel事件慌申,

   this.onVerticalDragStart,//手指觸摸屏幕并開始垂直拖動(dòng)陌选,同時(shí)onTap回調(diào)cancel事件,onVerticalDrag和onHorizontalDrag只可響應(yīng)一個(gè)蹄溉,誰先誰占有事件

   this.onVerticalDragUpdate,//手指觸摸屏幕并垂直拖動(dòng)中

   this.onVerticalDragEnd,//垂直拖動(dòng)結(jié)束

   this.onVerticalDragCancel,//垂直拖動(dòng)事件取消咨油,參考o(jì)nVerticalDragDown說明

   this.onHorizontalDragDown,//設(shè)置有長(zhǎng)按事件或者無拖動(dòng)抬起,則會(huì)回調(diào)cancel事件柒爵,
   
   this.onHorizontalDragStart,//手指觸摸屏幕并開始水平拖動(dòng)役电,同時(shí)onTap回調(diào)cancel事件,onVerticalDrag和onHorizontalDrag只可響應(yīng)一個(gè)棉胀,誰先誰占有事件

   this.onHorizontalDragUpdate,//水平拖動(dòng)中

   this.onHorizontalDragEnd,//水平拖動(dòng)結(jié)束

   this.onHorizontalDragCancel,//垂直拖動(dòng)事件取消法瑟,參考o(jì)nHorizontalDragDown說明

   // onPan可以取代onVerticalDrag或者onHorizontalDrag,三者不能并存
   this.onPanDown,//手指接觸屏幕

   this.onPanStart,//手指在屏幕上開始移動(dòng)

   this.onPanUpdate,//手指在屏幕上一直移動(dòng)

   this.onPanEnd,//手指離開屏幕

   this.onPanCancel,//當(dāng)設(shè)有LongPress事件時(shí)唁奢,會(huì)直接走此事件回調(diào)
   
   //手指屏幕按壓霎挟,需要壓力傳感器,模擬器無法觸發(fā)
   this.onForcePressStart, //手指按壓屏幕麻掸,當(dāng)力量達(dá)到ForcePressGestureRecognizer.startPressure觸發(fā)

   this.onForcePressPeak,//手指按壓屏幕酥夭,當(dāng)力量達(dá)到ForcePressGestureRecognizer.peakPressure觸發(fā)

   this.onForcePressUpdate,//手指按壓屏幕,按壓力量變化時(shí)觸發(fā)

   this.onForcePressEnd,//手指離開屏幕

   //兩指觸摸屏幕
   this.onSecondaryTapDown,//兩指觸摸屏幕

   this.onSecondaryTapUp,//兩指離開屏幕

   this.onSecondaryTapCancel, //當(dāng)設(shè)有LongPress事件時(shí),會(huì)直接走此事件回調(diào)
   
   //    onScale可以取代onVerticalDrag或者onHorizontalDrag熬北,三者不能并存千所,不能與onPan并存,會(huì)報(bào)錯(cuò)
   this.onScaleStart,//縮放拖動(dòng)

   this.onScaleUpdate,//縮放比例變化

   this.onScaleEnd,//縮放手指抬起

   this.behavior, //點(diǎn)擊測(cè)試行為規(guī)則,有三個(gè)值分別是:HitTestBehavior.deferToChild,HitTestBehavior.opaque,HitTestBehavior.translucent,文章后面會(huì)介紹

   this.excludeFromSemantics = false蒜埋,//是否排除手勢(shì)

   })

GestureDetector是檢測(cè)手勢(shì)的widget,同樣能響應(yīng)touch事件的還有Ink,InkWell和InkResponse組件,其中Ink沒有點(diǎn)擊的回調(diào)事件淫痰。

Flutter控件分類(交互的維度)

Flutter里控件按交互維度區(qū)分,分為自帶交互的控件和不帶交互的控件

  • 自帶交互的控件
    自帶交互的控件整份,如RaisedButton,OutlineButton等待错,內(nèi)部會(huì)有點(diǎn)擊事件的回調(diào),如
Center(
       child: OutlineButton(onPressed: () {
       print("點(diǎn)擊了button");
}, child: Text("點(diǎn)擊")),

上面代碼就可以搞定OutlineButton的點(diǎn)擊了

  • 不帶交互的控件
    不帶交互的控件烈评,如最基本的Text火俄,這類widget本身并沒有手勢(shì)點(diǎn)擊之類的監(jiān)聽屬性
Text(
    this.data, {
    Key key,
    this.style,
    this.strutStyle,
    this.textAlign,
    this.textDirection,
    this.locale,
    this.softWrap,
    this.overflow,
    this.textScaleFactor,
    this.maxLines,
    this.semanticsLabel,
    this.textWidthBasis,
    this.textHeightBehavior,
  })

如果你想實(shí)現(xiàn)單擊、雙擊讲冠、滑動(dòng)監(jiān)聽之類的交互瓜客,可以通過在外層套用GestureDetector來實(shí)現(xiàn)

 GestureDetector(
            child: Text(
              "我是一個(gè)小小小小鳥",
              style: TextStyle(color: _color, fontSize: 18),
            ),
        onTap:(){
            print("點(diǎn)擊一下");
        }
         ...
)

InkWell和InkResponse組件也能實(shí)現(xiàn),區(qū)別在于GestureDetector提供了最全的手勢(shì)監(jiān)聽竿开,如單擊谱仪、雙擊、長(zhǎng)按否彩、垂直橫向滑動(dòng)疯攒、縮放這些手勢(shì),還有命中測(cè)試行為列荔,而InkWell和InkResponse提供部分手勢(shì)和高亮觸摸效果敬尺,自帶水波紋動(dòng)畫

InkWell({
    Key key,
    Widget child,
    GestureTapCallback onTap,
    GestureTapCallback onDoubleTap,
    GestureLongPressCallback onLongPress,
    GestureTapDownCallback onTapDown,
    GestureTapCancelCallback onTapCancel,
    ValueChanged<bool> onHighlightChanged,
    ValueChanged<bool> onHover,
    Color focusColor,
    Color hoverColor,
    Color highlightColor,
    Color splashColor,
    InteractiveInkFeatureFactory splashFactory,
    double radius,
    BorderRadius borderRadius,
    ShapeBorder customBorder,
    bool enableFeedback = true,
    bool excludeFromSemantics = false,
    FocusNode focusNode,
    bool canRequestFocus = true,
    ValueChanged<bool> onFocusChange,
    bool autofocus = false,
  }

InkResponse({
    Key key,
    this.child,
    this.onTap,
    this.onTapDown,
    this.onTapCancel,
    this.onDoubleTap,
    this.onLongPress,
    this.onHighlightChanged,
    this.onHover,
    this.containedInkWell = false,
    this.highlightShape = BoxShape.circle,
    this.radius,
    this.borderRadius,
    this.customBorder,
    this.focusColor,
    this.hoverColor,
    this.highlightColor,
    this.splashColor,
    this.splashFactory,
    this.enableFeedback = true,
    this.excludeFromSemantics = false,
    this.focusNode,
    this.canRequestFocus = true,
    this.onFocusChange,
    this.autofocus = false,
  })

手勢(shì)的事件傳遞起源

無論是原生android還是flutter,UI事件傳遞的起始點(diǎn)都跟window脫不了關(guān)系

//   android 中 Activity.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
           onUserInteraction();
     }
    if (getWindow().superDispatchTouchEvent(ev)) {//getWindow()就是window實(shí)例
       return true;
     }
   return onTouchEvent(ev);
}

//flutter GestureBinding 中的initInstances方法
 @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    //事件由ui.window.onPointerDataPacket產(chǎn)生贴浙,把事件傳給GestureBinding._handlePointerDataPacket方法
    window.onPointerDataPacket = _handlePointerDataPacket;
  }

從上面代碼中很容易就可以看出砂吞,android和flutter的起點(diǎn)都來自于window,當(dāng)然崎溃,雖然起點(diǎn)一致蜻直,但傳遞思路卻并不相同。

首先笨奠,我們來看一下android是怎樣設(shè)計(jì)的袭蝗,話不多說唤殴,直接上圖


android 事件傳遞圖.jpg

從圖可以看出般婆,android傳遞的線路上Activity、PhoneWindow朵逝、DecorView蔚袍、ContentView,自上而下,逐層傳遞啤咽,很像我們?nèi)粘9ぷ鞯臅r(shí)候的場(chǎng)景:一天晋辆,大領(lǐng)導(dǎo)(Activity)分派一個(gè)任務(wù)下來,技術(shù)總監(jiān)(PhoneWindow)把任務(wù)整理一下又給了技術(shù)經(jīng)理(DecorView)宇整,技術(shù)經(jīng)理很忙瓶佳,又把任務(wù)派給了各個(gè)技術(shù)組長(zhǎng)(ContentView),然后組長(zhǎng)派任務(wù)給底下的組員來做鳞青,組員能完成最好霸饲,如果不能,又會(huì)逐級(jí)向上傳遞臂拓,直到事件結(jié)束厚脉,當(dāng)然具體做事情決不會(huì)這么簡(jiǎn)單,中間的細(xì)節(jié)就不說了胶惰,可以去找一些專門的技術(shù)文章去了解傻工,我們重點(diǎn)講講flutter是怎么設(shè)計(jì)的

Flutter 事件傳遞源碼流程

先從GestureBinding的initInstances方法開始吧

@override
void initInstances() {
  super.initInstances();
  _instance = this;
  window.onPointerDataPacket = _handlePointerDataPacket;
}

ui.Window get window => ui.window;

ui.window.onPointerDataPacket相當(dāng)于系統(tǒng)的一個(gè)接口,專門去接收硬件傳遞過來的事件方法孵滞,具體事件處理交給GestureBinding類的_handlePointerDataPacket方法中捆,接下來看一下方法

final Queue<PointerEvent> _pendingPointerEvents = Queue<PointerEvent>();
...
void _handlePointerDataPacket(ui.PointerDataPacket packet) {
    _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
    if (!locked)
      _flushPointerEventQueue();
 }

void _flushPointerEventQueue() {
    assert(!locked);
    while (_pendingPointerEvents.isNotEmpty)
      _handlePointerEvent(_pendingPointerEvents.removeFirst());
 }

從上面代碼可以看到,_pendingPointerEvents是個(gè)隊(duì)列坊饶,隊(duì)列里裝著一系列的指針事件轨香,通過指針事件轉(zhuǎn)換器把從底層傳出的物理坐標(biāo)邏輯像素根據(jù)設(shè)備像素比例參數(shù)轉(zhuǎn)換成指針事件屏幕坐標(biāo),隔離設(shè)備相關(guān)性幼东,然后通過while循環(huán)取出隊(duì)列的第一個(gè)指針事件元素進(jìn)行處理臂容,繼續(xù)看_handlePointerEvent這個(gè)方法

  void _handlePointerEvent(PointerEvent event) {
    assert(!locked);
    HitTestResult hitTestResult;
    if (event is PointerDownEvent || event is PointerSignalEvent) {
      assert(!_hitTests.containsKey(event.pointer));
      hitTestResult = HitTestResult();
      //確定命中測(cè)試的結(jié)果,通過hitTest方法來計(jì)算出HitTestResult
      hitTest(hitTestResult, event.position);
      if (event is PointerDownEvent) {
        //event.pointer是事件的唯一標(biāo)識(shí)符根蟹,相當(dāng)于事件的id,以id為key將hitTestResult存到_hitTests中
        _hitTests[event.pointer] = hitTestResult;
      }
      assert(() {
        if (debugPrintHitTestResults)
          debugPrint('$event: $hitTestResult');
        return true;
      }());
    } else if (event is PointerUpEvent || event is PointerCancelEvent) {
      //事件結(jié)束標(biāo)記脓杉,將hitTestResult從_hitTests取出并移除
      hitTestResult = _hitTests.remove(event.pointer);
    } else if (event.down) {
      //復(fù)用PointerDownEvent事件中的hitTestResult
      hitTestResult = _hitTests[event.pointer];
    }
    assert(() {
      if (debugPrintMouseHoverEvents && event is PointerHoverEvent)
        debugPrint('$event');
      return true;
    }());
    if (hitTestResult != null ||
        event is PointerHoverEvent ||
        event is PointerAddedEvent ||
        event is PointerRemovedEvent) {
      dispatchEvent(event, hitTestResult);
    }
  }

_handlePointerEvent方法會(huì)對(duì)每個(gè)取出的PointerEvent進(jìn)行處理,然后交給dispatchEvent進(jìn)行分發(fā)

  @override // from HitTestDispatcher
  void dispatchEvent(PointerEvent event, HitTestResult hitTestResult) {
    assert(!locked);
    // No hit test information implies that this is a hover or pointer
    // add/remove event.
    if (hitTestResult == null) {
      assert(event is PointerHoverEvent || event is PointerAddedEvent || event is PointerRemovedEvent);
      try {
        pointerRouter.route(event);
      } catch (exception, stack) {
        ...
      }
      return;
    }
    for (final HitTestEntry entry in hitTestResult.path) {
      try {
        entry.target.handleEvent(event.transformed(entry.transform), entry);
      } catch (exception, stack) {
       ...
      }
    }
  }

  @override // from HitTestTarget
  void handleEvent(PointerEvent event, HitTestEntry entry) {
    pointerRouter.route(event);
    if (event is PointerDownEvent) {
      gestureArena.close(event.pointer);
    } else if (event is PointerUpEvent) {
      gestureArena.sweep(event.pointer);
    } else if (event is PointerSignalEvent) {
      pointerSignalResolver.resolve(event);
    }
  }

在事件分發(fā)時(shí)简逮,如果hitTestResult為空球散,就從路由表里遍歷尋找相應(yīng)的事件,如果包含這個(gè)事件就會(huì)繼續(xù)進(jìn)行分發(fā)散庶,如果hitTestResult不為空蕉堰,就遍歷hitTestResult的path上的所有HitTestEntry,取出事件交給事件路由表繼續(xù)遍歷尋找悲龟,找到之后繼續(xù)分發(fā)事件屋讶。

RenderBinding

以上的部分都基于GestureBinding進(jìn)行分析的,其實(shí)還有個(gè)RenderBinding類

class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
  static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance;
  }
}

這里進(jìn)行了一系列的綁定操作须教,有熟悉的gestureBinding皿渗,還有RenderBinding(渲染綁定類)

//渲染樹與flutter引擎之間的膠水類
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable{
...
}

這里mixin與on關(guān)鍵字結(jié)合使用斩芭,使用on關(guān)鍵字,則表示該mixin只能在那個(gè)類的子類使用了乐疆,mixin中可以調(diào)用那個(gè)類定義的方法划乖、屬性,在on后面發(fā)現(xiàn)了GestureBinding這個(gè)熟悉的類挤土,那么RenderBinding也會(huì)重寫GestureBinding類的一些方法和屬性琴庵,比如hitTest

  @override
  void hitTest(HitTestResult result, Offset position) {
    assert(renderView != null);
    renderView.hitTest(result, position: position);
    super.hitTest(result, position);
  }

繼續(xù)追蹤renderView中的hitTest方法

 bool hitTest(HitTestResult result, { Offset position }) {
    if (child != null)
      child.hitTest(BoxHitTestResult.wrap(result), position: position);
    result.add(HitTestEntry(this));
    return true;
  }

child是RenderBox對(duì)象,追蹤進(jìn)RenderBox的hitTest方法

 bool hitTest(BoxHitTestResult result, { @required Offset position }) {
    ...
    //如果區(qū)域內(nèi)包含這個(gè)坐標(biāo)仰美,同時(shí)對(duì)child或者自身命中測(cè)試通過就添加到hitTestResult中
    if (_size.contains(position)) {
      if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
        result.add(BoxHitTestEntry(this, position));
        return true;
      }
    }
    return false;
  }

現(xiàn)在回到hitTest方法中细卧,可以發(fā)現(xiàn),方法是先對(duì)child進(jìn)行命中測(cè)試筒占,把符合條件的測(cè)試結(jié)果通過遞歸添加完畢贪庙,再把自身添加到hitTestResult中,事件通過冒泡的形式由下向上傳遞翰苫,而且不能中途中斷冒泡止邮,這個(gè)時(shí)候HitTestResult的路徑順序就是:

目標(biāo)節(jié)點(diǎn)-->父節(jié)點(diǎn)-->根節(jié)點(diǎn)-->GestureBinding

接著PointerDown,PointerMove奏窑,PointerUp导披,PointerCancel等事件分發(fā),事件都是從目標(biāo)Widget往RenderView(根節(jié)點(diǎn))都根據(jù)這個(gè)順序來遍歷調(diào)用它們的handleEvent方法埃唯,就像瀏覽器事件的冒泡過程一樣撩匕。到此,框架的事件流分析完了墨叛。該回到GestureDetector的懷抱了止毕。

GestureDetector源碼分析

GestureDetector里封裝了大量指針手勢(shì)事件,比如單擊漠趁、雙擊扁凛、橫向縱向滑動(dòng)、縮放等闯传,具體屬性在文章的開頭已經(jīng)有了一個(gè)詳細(xì)的介紹谨朝,下面我們來看這個(gè)組件內(nèi)部是怎么處理這些事件的

class GestureDetector extends StatelessWidget {
...
}

一開頭,都一個(gè)套路甥绿,沒什么看頭字币,我們直接看build方法

  @override
  Widget build(BuildContext context) {
    final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
    ...
    return RawGestureDetector(
      gestures: gestures,
      behavior: behavior,
      excludeFromSemantics: excludeFromSemantics,
      child: child,
    );
  }

中間我省去了很大一部分代碼,篇幅有點(diǎn)長(zhǎng)共缕,其實(shí)就是根據(jù)注冊(cè)回調(diào)洗出,添加對(duì)應(yīng)的GestureRecognizer,并傳遞到RawGestureDetector的gesture屬性骄呼,可以理解為GestureDetector其實(shí)就是對(duì)RawGestureDetector的一次封裝共苛,讓我們用起來更方便更直觀,繼續(xù)看RawGestureDetector是個(gè)什么東東

class RawGestureDetector extends StatefulWidget {
 ...
  class RawGestureDetectorState extends State<RawGestureDetector> {

   ...
  //默認(rèn)的命中測(cè)試行為蜓萄,沒有子元素隅茎,則組件背后也能接收到事件,如果有嫉沽,就讓子元素先響應(yīng)事件
  HitTestBehavior get _defaultBehavior {
    return widget.child == null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild;
  }

  @override
  Widget build(BuildContext context) {
    Widget result = Listener(
      onPointerDown: _handlePointerDown,
      behavior: widget.behavior ?? _defaultBehavior,
      child: widget.child,
    );
    if (!widget.excludeFromSemantics)
      result = _GestureSemantics(
        child: result,
        assignSemantics: _updateSemanticsForRenderObject,
      );
    return result;
    }
  }
}

直接看state的build方法辟犀,發(fā)現(xiàn)了一個(gè)Listener,看看源碼里是怎么說的

///  * [Listener], a widget that reports raw pointer events.

翻譯過來就是一個(gè)響應(yīng)原始指針事件的組件绸硕,這個(gè)就不往下研究了堂竟,繼續(xù)看_handlePointerDown方法

 void _handlePointerDown(PointerDownEvent event) {
    assert(_recognizers != null);
    for (final GestureRecognizer recognizer in _recognizers.values)
      recognizer.addPointer(event);
  }

 void addPointer(PointerDownEvent event) {
    _pointerToKind[event.pointer] = event.kind;
    if (isPointerAllowed(event)) {
      addAllowedPointer(event);
    } else {
      handleNonAllowedPointer(event);
    }
  }

如果設(shè)備被認(rèn)可,那么輸入事件就會(huì)被手勢(shì)識(shí)別器接受玻佩,給每個(gè)手勢(shì)識(shí)別器都添加PointDown事件出嘹,這個(gè)addPointer方法是手勢(shì)識(shí)別器的父類里的,接下來我們?nèi)テ渲械囊粋€(gè)擴(kuò)展子類識(shí)別器看看具體怎么操作的咬崔,比如HorizontalDragGestureRecognizer

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末税稼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子垮斯,更是在濱河造成了極大的恐慌郎仆,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兜蠕,死亡現(xiàn)場(chǎng)離奇詭異扰肌,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)熊杨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門曙旭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人晶府,你說我怎么就攤上這事夷狰。” “怎么了郊霎?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵沼头,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我书劝,道長(zhǎng)进倍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任购对,我火速辦了婚禮猾昆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘骡苞。我一直安慰自己垂蜗,他們只是感情好楷扬,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著贴见,像睡著了一般烘苹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上片部,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天镣衡,我揣著相機(jī)與錄音,去河邊找鬼档悠。 笑死廊鸥,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的辖所。 我是一名探鬼主播惰说,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼缘回!你這毒婦竟也來了助被?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤切诀,失蹤者是張志新(化名)和其女友劉穎揩环,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體幅虑,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡丰滑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了倒庵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片褒墨。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖擎宝,靈堂內(nèi)的尸體忽然破棺而出郁妈,到底是詐尸還是另有隱情,我是刑警寧澤绍申,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布噩咪,位于F島的核電站,受9級(jí)特大地震影響极阅,放射性物質(zhì)發(fā)生泄漏胃碾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一筋搏、第九天 我趴在偏房一處隱蔽的房頂上張望仆百。 院中可真熱鬧,春花似錦奔脐、人聲如沸俄周。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽峦朗。三九已至建丧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間甚垦,已是汗流浹背茶鹃。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工涣雕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留艰亮,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓挣郭,卻偏偏與公主長(zhǎng)得像迄埃,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子兑障,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348