Flutter完整開發(fā)實戰(zhàn)詳解(十三滔岳、全面深入觸摸和滑動原理)

本篇將帶你深入了解 Flutter 中的手勢事件傳遞屯换、事件分發(fā)编丘、事件沖突競爭与学,滑動流暢等等的原理,幫你構(gòu)建一個完整的 Flutter 閉環(huán)手勢知識體系嘉抓,這也許是目前最全面的手勢事件和滑動源碼的深入文章了索守。

文章匯總地址:

Flutter 完整實戰(zhàn)實戰(zhàn)系列文章專欄

Flutter 番外的世界系列文章專欄

Flutter 中默認情況下,以 Android 為例抑片,所有的事件都是起原生源于 io.flutter.view.FlutterView 這個 SurfaceView 的子類卵佛,整個觸摸手勢事件實質(zhì)上經(jīng)歷了 JAVA => C++ => Dart 的一個流程,整個流程如下圖所示敞斋,無論是 Android 還是 IOS 截汪,原生層都只是將所有事件打包下發(fā),比如在 Android 中植捎,手勢信息被打包成 ByteBuffer 進行傳遞衙解,最后在 Dart 層的 _dispatchPointerDataPacket 方法中,通過 _unpackPointerDataPacket 方法解析成可用的 PointerDataPacket 對象使用焰枢。

image

那么具體在 Flutter 中是如何分發(fā)使用手勢事件的呢蚓峦?

1、事件流程

在前面的流程圖中我們知道济锄,在 Dart 層中手勢事件都是從 _dispatchPointerDataPacket 開始的暑椰,之后會通過 Zone 判斷環(huán)境回調(diào),會執(zhí)行 GestureBinding 這個膠水類中的 _handlePointerEvent 方法荐绝。(如果對 Zone 或者 GestureBinding 有疑問可以翻閱前面的篇章)

如下代碼所示干茉, GestureBinding_handlePointerEvent 方法中主要是 hitTestdispatchEvent通過 hitTest 碰撞,得到一個包含控件的待處理成員列表 HitTestResult很泊,然后通過 dispatchEvent 分發(fā)事件并產(chǎn)生競爭角虫,得到勝利者相應(yīng)。

  void _handlePointerEvent(PointerEvent event) {
    assert(!locked);
    HitTestResult hitTestResult;
    if (event is PointerDownEvent || event is PointerSignalEvent) {
      hitTestResult = HitTestResult();
      ///開始碰撞測試了委造,會添加各個控件戳鹅,得到一個需要處理的控件成員列表
      hitTest(hitTestResult, event.position);
      if (event is PointerDownEvent) {
        _hitTests[event.pointer] = hitTestResult;
      }
    } else if (event is PointerUpEvent || event is PointerCancelEvent) {
      ///復(fù)用機制,抬起和取消昏兆,不用hitTest枫虏,移除
      hitTestResult = _hitTests.remove(event.pointer);
    } else if (event.down) {
      ///復(fù)用機制,手指處于滑動中爬虱,不用hitTest
      hitTestResult = _hitTests[event.pointer];
    }
    if (hitTestResult != null ||
        event is PointerHoverEvent ||
        event is PointerAddedEvent ||
        event is PointerRemovedEvent) {
      ///開始分發(fā)事件
      dispatchEvent(event, hitTestResult);
    }
  }

了解了結(jié)果后隶债,接下來深入分析這兩個關(guān)鍵方法:

1.1 、hitTest

hitTest 方法主要為了得到一個 HitTestResult 跑筝,這個 HitTestResult 內(nèi)有一個 List<HitTestEntry> 是用于分發(fā)和競爭事件的死讹,而每個 HitTestEntry.target 都會存儲每個控件的 RenderObject

因為 RenderObject 默認都實現(xiàn)了 HitTestTarget 接口曲梗,所以可以理解為: HitTestTarget 大部分時候都是 RenderObject 赞警,而 HitTestResult 就是一個帶著碰撞測試后的控件列表妓忍。

事實上 hitTestHitTestable 抽象類的方法,而 Flutter 中所有實現(xiàn) HitTestable 的類有 GestureBindingRendererBinding 愧旦,它們都是 mixinsWidgetsFlutterBinding 這個入口類上世剖,并且因為它們的 mixins 順序的關(guān)系,所以 RendererBindinghitTest 會先被調(diào)用笤虫,之后才調(diào)用 GestureBindinghitTest 旁瘫。

那么這兩個 hitTest 又分別干了什么事呢?

1.2琼蚯、RendererBinding.hitTest

RendererBinding.hitTest 中會執(zhí)行 renderView.hitTest(result, position: position); 酬凳,如下代碼所示,renderView.hitTest 方法內(nèi)會執(zhí)行 child.hitTest 凌停,它將嘗試將符合條件的 child 控件添加到 HitTestResult 里粱年,最后把自己添加進去。

///RendererBinding

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

而查看 child.hitTest 方法源碼罚拟,如下所示台诗,RenderObjcet 中的hitTest ,會通過 _size.contains 判斷自己是否屬于響應(yīng)區(qū)域赐俗,確認響應(yīng)后執(zhí)行 hitTestChildrenhitTestSelf 拉队,嘗試添加下級的 child 和自己添加進去,這樣的遞歸就讓我們自下而上的得到了一個 HitTestResult 的相應(yīng)控件列表了阻逮,最底下的 Child 在最上面粱快。

  ///RenderObjcet
  
  bool hitTest(HitTestResult result, { @required Offset position }) {
    if (_size.contains(position)) {
      if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
        result.add(BoxHitTestEntry(this, position));
        return true;
      }
    }
    return false;
  }

1.3、GestureBinding.hitTest

最后 GestureBinding.hitTest 方法不過最后把 GestureBinding 自己也添加到 HitTestResult 里叔扼,最后因為后面我們的流程還會需要回到 GestureBinding 中去處理事哭。

1.4、dispatchEvent

dispatchEvent 中主要是對事件進行分發(fā)瓜富,并且通過上述添加進去的 target.handleEvent 處理事件鳍咱,如下代碼所示,在存在碰撞結(jié)果的時候与柑,是會通過循環(huán)對每個控件內(nèi)部的handleEvent 進行執(zhí)行谤辜。

  @override // from HitTestDispatcher
  void dispatchEvent(PointerEvent event, HitTestResult hitTestResult) {
     ///如果沒有碰撞結(jié)果,那么通過 `pointerRouter.route` 將事件分發(fā)到全局處理价捧。
    if (hitTestResult == null) {
      try {
        pointerRouter.route(event);
      } catch (exception, stack) {
      return;
    }
    ///上面我們知道 HitTestEntry 中的 target 是一系自下而上的控件
    ///還有 renderView 和 GestureBinding
    ///循環(huán)執(zhí)行每一個的 handleEvent 方法
    for (HitTestEntry entry in hitTestResult.path) {
      try {
        entry.target.handleEvent(event, entry);
      } catch (exception, stack) {
      }
    }
  }

事實上并不是所有的控件的 RenderObject 子類都會處理 handleEvent 丑念,大部分時候,只有帶有 RenderPointerListener (RenderObject) / Listener (Widget) 的才會處理 handleEvent 事件结蟋,并且從上述源碼可以看出脯倚,handleEvent 的執(zhí)行是不會被攔截打斷的。

那么問題來了椎眯,如果同一個區(qū)域內(nèi)有多個控件都實現(xiàn)了 handleEvent 時挠将,那最后事件應(yīng)該交給誰消耗呢胳岂?

更具體為一個場景問題就是:比如一個列表頁面內(nèi)编整,存在上下滑動和 Item 點擊時舔稀,F(xiàn)lutter 要怎么分配手勢事件? 這就涉及到事件的競爭了掌测。

核心要來了内贮,高能預(yù)警!9夜郁!

2、事件競爭

Flutter 在設(shè)計事件競爭的時候粘勒,定義了一個很有趣的概念:通過一個競技場竞端,各個控件參與競爭,直接勝利的或者活到最后的第一位庙睡,你就獲勝得到了勝利事富。 那么為了分析接下來的“戰(zhàn)爭”,我們需要先看幾個概念:

  • GestureRecognizer :手勢識別器基類乘陪,基本上 RenderPointerListener 中需要處理的手勢事件统台,都會分發(fā)到它對應(yīng)的 GestureRecognizer,并經(jīng)過它處理和競技后再分發(fā)出去啡邑,常見有 :OneSequenceGestureRecognizer 贱勃、 MultiTapGestureRecognizerVerticalDragGestureRecognizer 谤逼、TapGestureRecognizer 等等贵扰。

  • GestureArenaManagerr :手勢競技管理,它管理了整個“戰(zhàn)爭”的過程流部,原則上競技勝出的條件是 :第一個競技獲勝的成員或最后一個不被拒絕的成員戚绕。

  • GestureArenaEntry :提供手勢事件競技信息的實體,內(nèi)封裝參與事件競技的成員贵涵。

  • GestureArenaMember:參與競技的成員抽象對象列肢,內(nèi)部有 acceptGesturerejectGesture 方法,它代表手勢競技的成員宾茂,默認 GestureRecognizer 都實現(xiàn)了它瓷马,所有競技的成員可以理解為就是 GestureRecognizer 之間的競爭。

  • _GestureArenaGestureArenaManager 內(nèi)的競技場跨晴,內(nèi)部持參與競技的 members 列表欧聘,官方對這個競技場的解釋是: 如果一個手勢試圖在競技場開放時(isOpen=true)獲勝,它將成為一個帶有“渴望獲勝”的屬性的對象端盆。當(dāng)競技場關(guān)閉(isOpen=false)時怀骤,競技場將尋找一個“渴望獲勝”的對象成為新的參與者费封,如果這時候剛好只有一個,那這一個參與者將成為這次競技場勝利的青睞存在蒋伦。

好了弓摘,知道這些概念之后我們開始分析流程,我們知道 GestureBindingdispatchEvent 時會先判斷是否有 HitTestResult 是否有結(jié)果痕届,一般情況下是存在的韧献,所以直接執(zhí)行循環(huán) entry.target.handleEvent

2.1研叫、PointerDownEvent

循環(huán)執(zhí)行過程中锤窑,我們知道 entry.target.handleEvent 會觸發(fā)RenderPointerListenerhandleEvent ,而事件流程中第一個事件一般都會是 PointerDownEvent嚷炉。

PointerDownEvent 的流程在事件競技流程中相當(dāng)關(guān)鍵渊啰,因為它會觸發(fā) GestureRecognizer.addPointer

GestureRecognizer 只有通過 addPointer 方法將 PointerDownEvent 事件和自己綁定申屹,并添加到 GestureBindingPointerRouter 事件路由和 GestureArenaManager 事件競技中绘证,后續(xù)的事件這個控件的 GestureRecognizer 才能響應(yīng)和參與競爭。

image

事實上 Down 事件在 Flutter 中一般都是用來做添加判斷的独柑,如果存在競爭時迈窟,大部分時候是不會直接出結(jié)果的,而 Move 事件在不同 GestureRecognizer 中會表現(xiàn)不同忌栅,而 UP 事件之后车酣,一般會強制得到一個結(jié)果。

所以我們知道了事件在 GestureBinding 開始分發(fā)的時候索绪,在 PointerDownEvent 時需要響應(yīng)事件的 GestureRecognizer 們湖员,會調(diào)用 addPointer 將自己添加到競爭中。之后流程中如果沒有特殊情況瑞驱,一般會執(zhí)行到參與競爭成員列表的 last娘摔,也就是 GestureBinding 自己這個 handleEvent 。

如下代碼所示唤反,走到 GestureBindinghandleEvent 凳寺,在 Down 事件的流程中,一般 pointerRouter.route 不會怎么處理邏輯彤侍,然后就是 gestureArena.close 關(guān)閉競技場了肠缨,嘗試得到勝利者。

  @override // from HitTestTarget
  void handleEvent(PointerEvent event, HitTestEntry entry) {
     /// 導(dǎo)航事件去觸發(fā)  `GestureRecognizer` 的 handleEvent
     /// 一般 PointerDownEvent 在 route 執(zhí)行中不怎么處理盏阶。
    pointerRouter.route(event);
    
    ///gestureArena 就是 GestureArenaManager
    if (event is PointerDownEvent) {
    
        ///關(guān)閉這個 Down 事件的競技晒奕,嘗試得到勝利
      /// 如果沒有的話就留到 MOVE 或者 UP。
      gestureArena.close(event.pointer);
      
    } else if (event is PointerUpEvent) {
        ///已經(jīng)到 UP 了,強行得到結(jié)果脑慧。
      gestureArena.sweep(event.pointer);
      
    } else if (event is PointerSignalEvent) {
      pointerSignalResolver.resolve(event);
    }
  }

讓我們看 GestureArenaManagerclose 方法魄眉,下面代碼我們可以看到,如果前面 Down 事件中沒有通過 addPointer 添加成員到 _arenas 中闷袒,那會連參加的機會都沒有坑律,而進入 _tryToResolveArena 之后,如果 state.members.length == 1 霜运,說明只有一個成員了脾歇,那就不競爭了蒋腮,直接它就是勝利者淘捡,直接響應(yīng)后續(xù)所有事件。 那么如果是多個的話池摧,就需要后續(xù)的競爭了焦除。

  void close(int pointer) {
    /// 拿到我們上面 addPointer 時添加的成員封裝
    final _GestureArena state = _arenas[pointer];
    if (state == null)
      return; // This arena either never existed or has been resolved.
    state.isOpen = false;
    ///開始打起來吧
    _tryToResolveArena(pointer, state);
  }
  
  void _tryToResolveArena(int pointer, _GestureArena state) {
    if (state.members.length == 1) {
      scheduleMicrotask(() => _resolveByDefault(pointer, state));
    } else if (state.members.isEmpty) {
      _arenas.remove(pointer);
    } else if (state.eagerWinner != null) {
      _resolveInFavorOf(pointer, state, state.eagerWinner);
    }
  }

2.2 開始競爭

那競爭呢?接下來我們以 TapGestureRecognizer 為例子作彤,如果控件區(qū)域內(nèi)存在兩個 TapGestureRecognizer 膘魄,那么在 PointerDownEvent 流程是不會產(chǎn)生勝利者的,這時候如果沒有 MOVE 打斷的話竭讳,到了 UP 事件時创葡,就會執(zhí)行 gestureArena.sweep(event.pointer); 強行選取一個。

而選擇的方式也是很簡單绢慢,就是 state.members.first 灿渴,從我們之前 hitTest 的結(jié)果上理解的話,就是控件樹的最里面 Child 了胰舆。 這樣勝利的 member 會通過 members.first.acceptGesture(pointer) 回調(diào)到 TapGestureRecognizer.acceptGesture 中骚露,設(shè)置 _wonArenaForPrimaryPointer 為 ture 標(biāo)志為勝利區(qū)域,然后執(zhí)行
_checkDown_checkUp 發(fā)出事件響應(yīng)觸發(fā)給這個控件缚窿。

而這里有個有意思的就是 棘幸,Down 流程的 acceptGesture 中的 _checkUp 因為沒有 _finalPosition 此時是不會被執(zhí)行的,_finalPosition 會在 handlePrimaryPointer 方法中倦零,獲得_finalPosition 并判斷 _wonArenaForPrimaryPointer 標(biāo)志為误续,再次執(zhí)行 _checkUp 才會成功。

handlePrimaryPointer 是在 UP 流程中 pointerRouter.route 觸發(fā) TapGestureRecognizerhandleEvent 觸發(fā)的扫茅。

那么問題來了蹋嵌,_checkDown_checkUp 時在 UP 事件一次性被執(zhí)行,那么如果我長按住的話诞帐,_checkDown 不是沒辦法正確回調(diào)了欣尼?

當(dāng)然不會,在 TapGestureRecognizer 中有一個 didExceedDeadline 的機制,在前面 Down 流程中愕鼓,addPointerTapGestureRecognizer 會創(chuàng)建一個定時器钙态,這個定時器的時間時 kPressTimeout = 100毫秒如果我們長按住的話菇晃,就會等待到觸發(fā) didExceedDeadline 去執(zhí)行 _checkDown 發(fā)出 onTabDown 事件了册倒。

_checkDown 執(zhí)行發(fā)送過程中,會有一個標(biāo)志為 _sentTapDown 判斷是否已經(jīng)發(fā)送過磺送,如果發(fā)送過了也不會在重發(fā)驻子,之后回到原本流程去競爭,手指抬起后得到勝利者相應(yīng)估灿,同時在 _checkUp 之后 _sentTapDown 標(biāo)識為會被重置崇呵。

這也可以分析點擊下的幾種場景:

普通按下:
  • 1、區(qū)域內(nèi)只有一個 TapGestureRecognizer :Down 事件時直接在競技場 close 時就得到競出勝利者馅袁,調(diào)用 acceptGesture 執(zhí)行 _checkUp域慷,到 Up 事件的時候通過 handlePrimaryPointer 執(zhí)行 _checkUp,結(jié)束汗销。

  • 2犹褒、區(qū)域內(nèi)有多個 TapGestureRecognizer :Down 事件時在競技場 close 不會競出勝利者,在 Up 事件的時候弛针,會在 route 過程通過handlePrimaryPointer 設(shè)置好 _finalPosition叠骑,之后經(jīng)過競技場 sweep 選取排在第一個位置的為勝利者,調(diào)用 acceptGesture削茁,執(zhí)行 _checkDown_checkUp 宙枷。

長按之后抬起:

1、區(qū)域內(nèi)只有一個 TapGestureRecognizer :除了 Down 事件是在 didExceedDeadline 時發(fā)出 _checkDown 外其他和上面基本沒區(qū)別付材。

  • 2朦拖、區(qū)域內(nèi)有多個 TapGestureRecognizer :Down 事件時在競技場 close 時不會競出勝利者,但是會觸發(fā)定時器 didExceedDeadline厌衔,先發(fā)出 _checkDown璧帝,之后再經(jīng)過 sweep 選取第一個座位勝利者,調(diào)用 acceptGesture富寿,觸發(fā) _checkUp

那么問題又來了睬隶,你有沒有疑問,如果有區(qū)域兩個 TapGestureRecognizer 页徐,長按的時候因為都觸發(fā)了 didExceedDeadline 執(zhí)行 _checkDown 嗎苏潜?

答案是:會的!因為定時器都觸發(fā)了 didExceedDeadline变勇,所以 _checkDown 都會被執(zhí)行恤左,從而都發(fā)出了 onTapDown 事件贴唇。但是后續(xù)競爭后,只會執(zhí)行一個 _checkUp 飞袋,所有只會有一個控件響應(yīng) onTap 戳气。

競技失敗:

在競技場競爭失敗的成員會被移出競技場巧鸭,移除后就沒辦法參加后面事件的競技了 瓶您,比如 TapGestureRecognizer 在接受到 PointerMoveEvent 事件時就會直接 rejected , 并觸發(fā) rejectGesture ,之后定時器會被關(guān)閉纲仍,并且觸發(fā) onTapCancel 呀袱,然后重置標(biāo)志位.

總結(jié)下:

Down 事件時通過 addPointer 加入了 GestureRecognizer 競技場的區(qū)域,在沒移除的情況下郑叠,事件可以參加后續(xù)事件的競技夜赵,在某個事件階段移除的話,之后的事件序列也會無法接受锻拘。事件的競爭如果沒有勝利者油吭,在 UP 流程中會強制指定第一個為勝利者。

2.3 滑動事件

滑動事件也是需要在 Down 流程中 addPointer 署拟,然后 MOVE 流程中,通過在 PointerRouter.route 之后執(zhí)行 DragGestureRecognizer.handleEvent 歌豺。

image.png

PointerMoveEvent 事件的 DragGestureRecognizer.handleEvent 里推穷,會通過在 _hasSufficientPendingDragDeltaToAccept判斷是否符合條件,如:

bool get _hasSufficientPendingDragDeltaToAccept => _pendingDragOffset.dy.abs() > kTouchSlop;

如果符合條件就直接執(zhí)行 resolve(GestureDisposition.accepted); 类咧,將流程回到競技場里馒铃,然后執(zhí)行 acceptGesture ,然后觸發(fā)onStartonUpdate 痕惋。

回到我們前面的上下滑動可點擊列表区宇,是不是很明確了:如果是點擊的話,沒有產(chǎn)生 MOVE 事件值戳,所以 DragGestureRecognizer 沒有被接受议谷,而Item 作為 Child 第一位,所以響應(yīng)點擊堕虹。如果有 MOVE 事件卧晓, DragGestureRecognizer 會被 acceptGesture,而點擊 GestureRecognizer 會被移除事件競爭赴捞,也就沒有后續(xù) UP 事件了逼裆。

那這個 onUpdate 是怎么讓節(jié)目動起來的?

我們以 ListView 為例子赦政,通過源碼可以知道胜宇, onUpdate 最后會調(diào)用到 Scrollable_handleDragUpdate ,這時候會執(zhí)行 Drag.update

image.png

通過源碼我們知道 ListViewDrag 實現(xiàn)其實是 ScrollDragController, 它在 Scrollable 中是和 ScrollPositionWithSingleContext 關(guān)聯(lián)的在一起的桐愉。那么 ScrollPositionWithSingleContext 又是什么封寞?

ScrollPositionWithSingleContext 其實就是這個滑動的關(guān)鍵,它其實就是 ScrollPosition 的子類仅财,而 ScrollPosition 又是 ViewportOffset 的子類狈究,而 ViewportOffset 又是一個 ChangeNotifier,出現(xiàn)如下關(guān)系:

繼承關(guān)系:ScrollPositionWithSingleContext : ScrollPosition : ViewportOffset : ChangeNotifier

所以 ViewportOffset 就是滑動的關(guān)鍵點盏求。上面我們知道響應(yīng)區(qū)域 DragGestureRecognizer 勝利之后執(zhí)行 Drag.update 抖锥,最終會調(diào)用到 ScrollPositionWithSingleContextapplyUserOffset,導(dǎo)致內(nèi)部確定位置的 pixels 發(fā)生改變碎罚,并執(zhí)行父類 ChangeNotifier 的方法notifyListeners 通知更新磅废。

而在 ListView 內(nèi)部 RenderViewportBase 中,這個 ViewportOffset 是通過 _offset.addListener(markNeedsLayout); 綁定的荆烈,so 拯勉,觸摸滑動導(dǎo)致 Drag.update ,最終會執(zhí)行到 RenderViewportBase 中的 markNeedsLayout 觸發(fā)頁面更新憔购。

至于 markNeedsLayout 如何更新界面和滾動列表宫峦,這里暫不詳細描述了,給個圖感受下:

image.png

自此玫鸟,第十三篇終于結(jié)束了导绷!(///▽///)

資源推薦

完整開源項目推薦:
我們還會再見嗎?
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末屎飘,一起剝皮案震驚了整個濱河市妥曲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钦购,老刑警劉巖檐盟,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異押桃,居然都是意外死亡葵萎,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門怨规,熙熙樓的掌柜王于貴愁眉苦臉地迎上來陌宿,“玉大人,你說我怎么就攤上這事波丰】瞧海” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵掰烟,是天一觀的道長爽蝴。 經(jīng)常有香客問我沐批,道長,這世上最難降的妖魔是什么蝎亚? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任九孩,我火速辦了婚禮,結(jié)果婚禮上发框,老公的妹妹穿的比我還像新娘躺彬。我一直安慰自己,他們只是感情好梅惯,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布宪拥。 她就那樣靜靜地躺著,像睡著了一般铣减。 火紅的嫁衣襯著肌膚如雪她君。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天葫哗,我揣著相機與錄音缔刹,去河邊找鬼。 笑死劣针,一個胖子當(dāng)著我的面吹牛校镐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播酿秸,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼灭翔,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了辣苏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤哄褒,失蹤者是張志新(化名)和其女友劉穎稀蟋,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體呐赡,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡退客,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了链嘀。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片萌狂。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖怀泊,靈堂內(nèi)的尸體忽然破棺而出茫藏,到底是詐尸還是另有隱情,我是刑警寧澤霹琼,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布务傲,位于F島的核電站凉当,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏售葡。R本人自食惡果不足惜看杭,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挟伙。 院中可真熱鬧楼雹,春花似錦、人聲如沸尖阔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诺祸。三九已至携悯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間筷笨,已是汗流浹背憔鬼。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留胃夏,地道東北人轴或。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像仰禀,于是被迫代替她去往敵國和親照雁。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

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

  • Flutter的事件源 Flutter的原始事件是由window中 PointerDataPacketCallba...
    kevin丶xie閱讀 6,128評論 2 6
  • 簡介 iOS 事件分為三大類 觸摸事件 加速器事件 遠程控制事件 以下我們講解觸摸事件觸摸事件是我們平時遇到最多的...
    AKsoftware閱讀 22,479評論 23 72
  • android中存在事件沖突答恶,flutter其實也存在饺蚊,但是官方友好的出了一個控件(GestureDetector...
    葉落清秋閱讀 17,896評論 0 7
  • 工作不順心,辭掉了悬嗓! 租了個房子污呼,單間,不大不小包竹,價格不高不低燕酷,我一個人住,足夠了周瞎。沒有工作的這幾天苗缩,全部沉浸在睡...
    孤獨者_靜夜思閱讀 326評論 3 1
  • 走著走著双絮,就走進了秋的韻致里浴麻。 碧云天得问,黃葉地,秋色連波软免,波上寒煙翠宫纬。天高云淡,秋光瀲滟膏萧。 秋風(fēng)吹落一地的蒼涼漓骚,秋...
    茶詩花閱讀 1,333評論 8 28