Flutter 之 手勢(shì)原理與手勢(shì)沖突 (五十)

手勢(shì)的識(shí)別和處理都是在事件分發(fā)階段的

1.手勢(shì)識(shí)別原理

GestureDetector 是一個(gè) StatelessWidget浊闪,返回的是一個(gè) RawGestureDetector蛾茉。

GestureDetector Build方法 源碼

@override
Widget build(BuildContext context) {
  final  gestures = <Type, GestureRecognizerFactory>{};
  // 構(gòu)建 TapGestureRecognizer 
  if (onTapDown != null ||
      onTapUp != null ||
      onTap != null ||
      ... //省略
  ) {
    gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
      () => TapGestureRecognizer(debugOwner: this),
      (TapGestureRecognizer instance) {
        instance
          ..onTapDown = onTapDown
          ..onTapUp = onTapUp
          ..onTap = onTap
          //省略
      },
    );
  }

  
  return RawGestureDetector(
    gestures: gestures, // 傳入手勢(shì)識(shí)別器
    behavior: behavior, // 同 Listener 中的 HitTestBehavior
    child: child,
  );
}

注意,上面我們刪除了很多代碼闻伶,只保留了 TapGestureRecognizer(點(diǎn)擊手勢(shì)識(shí)別器) 相關(guān)代碼,我們以點(diǎn)擊手勢(shì)識(shí)別為例講一下整個(gè)過(guò)程忠藤。RawGestureDetector 中會(huì)通過(guò) Listener 組件監(jiān)聽(tīng) PointerDownEvent 事件掉冶,相關(guān)源碼如下:

@override
Widget build(BuildContext context) {
  ... // 省略無(wú)關(guān)代碼
  Widget result = Listener(
    onPointerDown: _handlePointerDown,
    behavior: widget.behavior ?? _defaultBehavior,
    child: widget.child,
  );
}  
 
void _handlePointerDown(PointerDownEvent event) {
  for (final GestureRecognizer recognizer in _recognizers!.values)
    recognizer.addPointer(event);
}  

下面我們看一下 TapGestureRecognizer 的幾個(gè)相關(guān)方法,由于 TapGestureRecognizer 有多層繼承關(guān)系糙及,筆者合并了一個(gè)簡(jiǎn)化版:

class CustomTapGestureRecognizer1 extends TapGestureRecognizer {

  void addPointer(PointerDownEvent event) {
    //會(huì)將 handleEvent 回調(diào)添加到 pointerRouter 中
    GestureBinding.instance!.pointerRouter.addRoute(event.pointer, handleEvent);
  }
  
  @override
  void handleEvent(PointerEvent event) {
    //會(huì)進(jìn)行手勢(shì)識(shí)別详幽,并決定是是調(diào)用 acceptGesture 還是 rejectGesture,
  }
  
  @override
  void acceptGesture(int pointer) {
    // 競(jìng)爭(zhēng)勝出會(huì)調(diào)用
  }

  @override
  void rejectGesture(int pointer) {
    // 競(jìng)爭(zhēng)失敗會(huì)調(diào)用
  }
}

可以看到當(dāng) PointerDownEvent 事件觸發(fā)時(shí)浸锨,會(huì)調(diào)用 TapGestureRecognizer 的 addPointer唇聘,在 addPointer 中會(huì)將 handleEvent 方法添加到 pointerRouter 中保存起來(lái)。這樣一來(lái)當(dāng)手勢(shì)發(fā)生變化時(shí)只需要在 pointerRouter中取出 GestureRecognizer 的 handleEvent 方法進(jìn)行手勢(shì)識(shí)別即可柱搜。

正常情況下應(yīng)該是手勢(shì)直接作用的對(duì)象應(yīng)該來(lái)處理手勢(shì)迟郎,所以一個(gè)簡(jiǎn)單的原則就是同一個(gè)手勢(shì)應(yīng)該只有一個(gè)手勢(shì)識(shí)別器生效,為此聪蘸,手勢(shì)識(shí)別才引入了手勢(shì)競(jìng)技場(chǎng)(Arena)的概念宪肖,簡(jiǎn)單來(lái)講:

  • 1. 每一個(gè)手勢(shì)識(shí)別器(GestureRecognizer)都是一個(gè)“競(jìng)爭(zhēng)者”(GestureArenaMember),當(dāng)發(fā)生指針事件時(shí)健爬,他們都要在“競(jìng)技場(chǎng)”去競(jìng)爭(zhēng)本次事件的處理權(quán)控乾,默認(rèn)情況最終只有一個(gè)“競(jìng)爭(zhēng)者”會(huì)勝出(win)。
  • 2. GestureRecognizer 的 handleEvent 中會(huì)識(shí)別手勢(shì)娜遵,如果手勢(shì)發(fā)生了某個(gè)手勢(shì)蜕衡,競(jìng)爭(zhēng)者可以宣布自己是否勝出,一旦有一個(gè)競(jìng)爭(zhēng)者勝出设拟,競(jìng)技場(chǎng)管理者(GestureArenaManager)就會(huì)通知其它競(jìng)爭(zhēng)者失敗衷咽。
  • 3. 勝出者的 acceptGesture 會(huì)被調(diào)用,其余的 rejectGesture 將會(huì)被調(diào)用蒜绽。

上一節(jié)我們說(shuō)過(guò)命中測(cè)試是從 RenderBinding 的 hitTest 開(kāi)始的:

@override
void hitTest(HitTestResult result, Offset position) {
  // 從根節(jié)點(diǎn)開(kāi)始進(jìn)行命中測(cè)試
  renderView.hitTest(result, position: position); 
  // 會(huì)調(diào)用 GestureBinding 中的 hitTest()方法
  super.hitTest(result, position); 
}

渲染樹(shù)命中測(cè)試完成后會(huì)調(diào)用 GestureBinding 中的 hitTest() 方法:

@override // from HitTestable
void hitTest(HitTestResult result, Offset position) {
  result.add(HitTestEntry(this));
}

很簡(jiǎn)單镶骗, GestureBinding 也通過(guò)命中測(cè)試了,這樣的話在事件分發(fā)階段躲雅,GestureBinding 的 handleEvent 也便會(huì)被調(diào)用鼎姊,由于它是最后被添加到 HitTestResult 中的,所以在事件分發(fā)階段 GestureBinding 的 handleEvent會(huì)調(diào)用:

GestureBinding 的 handleEvent 源碼

@override 
void handleEvent(PointerEvent event, HitTestEntry entry) {
  // 會(huì)調(diào)用在 pointerRouter 中添加的 GestureRecognizer 的 handleEvent
  pointerRouter.route(event);
  if (event is PointerDownEvent) {
    // 分發(fā)完畢后,關(guān)閉競(jìng)技場(chǎng)
    gestureArena.close(event.pointer);
  } else if (event is PointerUpEvent) {
    gestureArena.sweep(event.pointer);
  } else if (event is PointerSignalEvent) {
    pointerSignalResolver.resolve(event);
  }
}

gestureArena 是 GestureArenaManager 類實(shí)例相寇,負(fù)責(zé)管理競(jìng)技場(chǎng)慰于。

上面關(guān)鍵的代碼就是第一行,功能是會(huì)調(diào)用之前在 pointerRouter 中添加的 GestureRecognizer 的 handleEvent唤衫,不同 GestureRecognizer 的 handleEvent 會(huì)識(shí)別不同的手勢(shì)婆赠,然后它會(huì)和 gestureArena 交互(如果當(dāng)前的 GestureRecognizer 勝出,需要 gestureArena 去通知其它競(jìng)爭(zhēng)者它們失敗了)佳励,最終休里,如果當(dāng)前GestureRecognizer 勝出,則最終它的 acceptGesture 會(huì)被調(diào)用赃承,如果失敗則其 rejectGesture 將會(huì)被調(diào)用妙黍,因?yàn)檫@部分代碼不同的 GestureRecognizer 會(huì)不同,知道做了什么就行瞧剖,讀者有興趣可以自行查看源碼拭嫁。

2. 手勢(shì)競(jìng)爭(zhēng)

如果對(duì)一個(gè)組件同時(shí)監(jiān)聽(tīng)水平和垂直方向的拖動(dòng)手勢(shì),當(dāng)我們斜著拖動(dòng)時(shí)哪個(gè)方向的拖動(dòng)手勢(shì)回調(diào)會(huì)被觸發(fā)抓于?
實(shí)際上取決于第一次移動(dòng)時(shí)兩個(gè)軸上的位移分量做粤,哪個(gè)軸的大,哪個(gè)軸在本次滑動(dòng)事件競(jìng)爭(zhēng)中就勝出捉撮。

上面已經(jīng)說(shuō)過(guò)驮宴,每一個(gè)手勢(shì)識(shí)別器(GestureRecognizer)都是一個(gè)“競(jìng)爭(zhēng)者”(GestureArenaMember),當(dāng)發(fā)生指針事件時(shí)呕缭,他們都要在“競(jìng)技場(chǎng)”去競(jìng)爭(zhēng)本次事件的處理權(quán)堵泽,默認(rèn)情況最終只有一個(gè)“競(jìng)爭(zhēng)者”會(huì)勝出(win)。

例如恢总,假設(shè)有一個(gè)ListView迎罗,它的第一個(gè)子組件也是ListView,如果現(xiàn)在滑動(dòng)這個(gè)子ListView片仿,父ListView會(huì)動(dòng)嗎纹安?答案是否定的,這時(shí)只有子ListView會(huì)動(dòng)砂豌,因?yàn)檫@時(shí)子ListView會(huì)勝出而獲得滑動(dòng)事件的處理權(quán)厢岂。

示例1


class MSGestureDetailDemo extends StatelessWidget {
  const MSGestureDetailDemo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("GestureDemo")),
      body: Center(
        child: GestureDetector(
          onTapUp: (details) => print("2"), // 監(jiān)聽(tīng)父組件 tapUp 手勢(shì)
          child: Container(
            width: 200,
            height: 200,
            color: Colors.red,
            alignment: Alignment.center,
            child: GestureDetector(
              onTapUp: (details) => print("1"), // 監(jiān)聽(tīng)子組件 tapUp 手勢(shì)
              child: Container(
                width: 100,
                height: 100,
                color: Colors.grey,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

image.png

當(dāng)我們點(diǎn)擊子組件(灰色區(qū)域)時(shí),控制臺(tái)只會(huì)打印 “1”, 并不會(huì)打印 “2”阳距,這是因?yàn)槭种柑鸷笏#珿estureDetector1 和 GestureDetector 2 會(huì)發(fā)生競(jìng)爭(zhēng),判定獲勝的規(guī)則是“子組件優(yōu)先”筐摘,所以 GestureDetector1 獲勝卒茬,因?yàn)橹荒苡幸粋€(gè)“競(jìng)爭(zhēng)者”勝出船老,所以 GestureDetector 2 將被忽略。

這個(gè)例子中想要解決沖突的方法很簡(jiǎn)單圃酵,將 GestureDetector 換為 Listener 即可柳畔,簡(jiǎn)單的說(shuō),手勢(shì)的識(shí)別和處理是在事件的分發(fā)階段郭赐,而Listener是在監(jiān)聽(tīng)原始指針事件薪韩。具體原因我們?cè)诤竺娼忉尅?/p>

示例2
我們以拖動(dòng)手勢(shì)為例,同時(shí)識(shí)別水平和垂直方向的拖動(dòng)手勢(shì)捌锭,當(dāng)用戶按下手指時(shí)就會(huì)觸發(fā)競(jìng)爭(zhēng)(水平方向和垂直方向)俘陷,一旦某個(gè)方向“獲勝”,則直到當(dāng)次拖動(dòng)手勢(shì)結(jié)束都會(huì)沿著該方向移動(dòng)


class MSGestureDetailDemo2 extends StatefulWidget {
  const MSGestureDetailDemo2({Key? key}) : super(key: key);

  @override
  State<MSGestureDetailDemo2> createState() => _MSGestureDetailDemo2State();
}

class _MSGestureDetailDemo2State extends State<MSGestureDetailDemo2> {
  double _left = 0.0;
  double _top = 0.0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("MSGestureDetailDemo2")),
      body: Stack(
        children: [
          Positioned(
            left: _left,
            top: _top,
            child: GestureDetector(
              child: CircleAvatar(child: Text("A")),
              //垂直方向拖動(dòng)事件
              onVerticalDragUpdate: (DragUpdateDetails details) {
                _top += details.delta.dy;
                setState(() {});
              },
              // 水平方向拖動(dòng)事件
              onHorizontalDragUpdate: (DragUpdateDetails details) {
                _left += details.delta.dx;
                setState(() {});
              },
            ),
          ),
        ],
      ),
    );
  }
}

image.png

此示例運(yùn)行后舀锨,每次拖動(dòng)只會(huì)沿一個(gè)方向移動(dòng)(水平或垂直)岭洲,而競(jìng)爭(zhēng)發(fā)生在手指按下后首次移動(dòng)(move)時(shí)宛逗,此例中具體的“獲勝”條件是:首次移動(dòng)時(shí)的位移在水平和垂直方向上的分量大的一個(gè)獲勝坎匿。

3. 多手勢(shì)沖突

由于手勢(shì)競(jìng)爭(zhēng)最終只有一個(gè)勝出者,所以雷激,當(dāng)我們通過(guò)一個(gè) GestureDetector 監(jiān)聽(tīng)多種手勢(shì)時(shí)替蔬,也可能會(huì)產(chǎn)生沖突。

假設(shè)有一個(gè)widget屎暇,它可以左右拖動(dòng)承桥,現(xiàn)在我們也想檢測(cè)在它上面手指按下和抬起的事件。

示例


class MSGestureDetailDemo3 extends StatefulWidget {
  const MSGestureDetailDemo3({Key? key}) : super(key: key);

  @override
  State<MSGestureDetailDemo3> createState() => _MSGestureDetailDemo3State();
}

class _MSGestureDetailDemo3State extends State<MSGestureDetailDemo3> {
  double _left = 0.0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("MSGestureDetailDemo3")),
      body: Stack(
        children: [
          Positioned(
            left: _left,
            child: GestureDetector(
              child: CircleAvatar(child: Text("A")),
              onHorizontalDragUpdate: (DragUpdateDetails details) {
                _left += details.delta.dx;
                setState(() {});
              },
              onHorizontalDragEnd: (details) {
                print("onHorizontalDragEnd");
              },
              onTapDown: (TapDownDetails details) {
                print("down");
              },
              onTapUp: (TapUpDetails details) {
                print("up");
              },
            ),
          ),
        ],
      ),
    );
  }
}

image.png

現(xiàn)在我們按住圓形“A”拖動(dòng)然后抬起手指根悼,控制臺(tái)日志如下:

flutter: down
flutter: onHorizontalDragEnd

我們發(fā)現(xiàn)沒(méi)有打印"up"凶异,這是因?yàn)樵谕蟿?dòng)時(shí),剛開(kāi)始按下手指且沒(méi)有移動(dòng)時(shí)挤巡,拖動(dòng)手勢(shì)還沒(méi)有完整的語(yǔ)義剩彬,此時(shí)TapDown手勢(shì)勝出(win),此時(shí)打印"down"矿卑,而拖動(dòng)時(shí)喉恋,拖動(dòng)手勢(shì)會(huì)勝出,當(dāng)手指抬起時(shí)母廷,onHorizontalDragEnd 和 onTapUp發(fā)生了沖突轻黑,但是因?yàn)槭窃谕蟿?dòng)的語(yǔ)義中,所以onHorizontalDragEnd勝出琴昆,所以就會(huì)打印 “onHorizontalDragEnd”氓鄙。

如果我們的代碼邏輯中,對(duì)于手指按下和抬起是強(qiáng)依賴的业舍,比如在一個(gè)輪播圖組件中玖详,我們希望手指按下時(shí)把介,暫停輪播,而抬起時(shí)恢復(fù)輪播蟋座,但是由于輪播圖組件中本身可能已經(jīng)處理了拖動(dòng)手勢(shì)(支持手動(dòng)滑動(dòng)切換)拗踢,甚至可能也支持了縮放手勢(shì),這時(shí)我們?nèi)绻谕獠吭儆胦nTapDown向臀、onTapUp來(lái)監(jiān)聽(tīng)的話是不行的巢墅。

這時(shí)我們應(yīng)該怎么做?其實(shí)很簡(jiǎn)單券膀,通過(guò)Listener監(jiān)聽(tīng)原始指針事件就行:


class MSGestureDetailDemo4 extends StatefulWidget {
  const MSGestureDetailDemo4({Key? key}) : super(key: key);

  @override
  State<MSGestureDetailDemo4> createState() => _MSGestureDetailDemo4State();
}

class _MSGestureDetailDemo4State extends State<MSGestureDetailDemo4> {
  double _left = 0.0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("MSGestureDetailDemo4")),
      body: Stack(
        children: [
          Positioned(
            left: _left,
            child: Listener(
              onPointerDown: (PointerDownEvent event) {
                print("down");
              },
              onPointerUp: (PointerUpEvent event) {
                print("up");
              },
              child: GestureDetector(
                child: CircleAvatar(
                  child: Text("A"),
                ),
                onHorizontalDragUpdate: (DragUpdateDetails details) {
                  _left += details.delta.dx;
                  setState(() {});
                },
                onHorizontalDragEnd: (DragEndDetails details) {
                  print("onHorizontalDragEnd");
                },
              ),
            ),
          ),
        ],
      ),
    );
  }
}

image.png

現(xiàn)在我們按住圓形“A”拖動(dòng)然后抬起手指君纫,控制臺(tái)日志如下:

flutter: down
flutter: up
flutter: onHorizontalDragEnd

是有“up”打印的,說(shuō)明我們監(jiān)聽(tīng)到指針抬起事件了芹彬。

4. 解決手勢(shì)沖突

手勢(shì)是對(duì)原始指針的語(yǔ)義化的識(shí)別蓄髓,手勢(shì)沖突只是手勢(shì)級(jí)別的,也就是說(shuō)只會(huì)在組件樹(shù)中的多個(gè) GestureDetector 之間才有沖突的場(chǎng)景舒帮,如果壓根就沒(méi)有使用 GestureDetector 則不存在所謂的沖突会喝,因?yàn)槊恳粋€(gè)節(jié)點(diǎn)都能收到事件,只是在 GestureDetector 中為了識(shí)別語(yǔ)義玩郊,它會(huì)去決定哪些子節(jié)點(diǎn)應(yīng)該忽略事件肢执,哪些節(jié)點(diǎn)應(yīng)該生效。

解決手勢(shì)沖突的方法有兩種:

  • 1. 使用 Listener译红。這相當(dāng)于跳出了手勢(shì)識(shí)別那套規(guī)則预茄。
  • 2. 自定義手勢(shì)手勢(shì)識(shí)別器( Recognizer)。

4.1 通過(guò) Listener 解決手勢(shì)沖突

通過(guò) Listener 解決手勢(shì)沖突的原因是競(jìng)爭(zhēng)只是針對(duì)手勢(shì)的侦厚,而 Listener 是監(jiān)聽(tīng)原始指針事件耻陕,原始指針事件并非語(yǔ)義話的手勢(shì),所以根本不會(huì)走手勢(shì)競(jìng)爭(zhēng)的邏輯刨沦,所以也就不會(huì)相互影響诗宣。

拿上面兩個(gè) Container 嵌套的例子來(lái)說(shuō),通過(guò)Listener的解決方式為:


class MSGestureDetailDemo5 extends StatelessWidget {
  const MSGestureDetailDemo5({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("MSGestureDetailDemo5")),
      body: Center(
        child: Listener(
          onPointerUp: (details) => print("2"), // 監(jiān)聽(tīng)父組件 tapUp 手勢(shì)
          child: Container(
            width: 200,
            height: 200,
            color: Colors.red,
            alignment: Alignment.center,
            child: GestureDetector(
              onTapUp: (details) => print("1"), // 監(jiān)聽(tīng)子組件 tapUp 手勢(shì)
              child: Container(
                width: 100,
                height: 100,
                color: Colors.grey,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

image.png

點(diǎn)擊灰色區(qū)域已卷,會(huì)同時(shí)打印“2”梧田、“1”

代碼很簡(jiǎn)單,只需將 GestureDetector 換位 Listener 即可侧蘸,可以兩個(gè)都換裁眯,也可以只換一個(gè)』浒可以看見(jiàn)穿稳,通過(guò)Listener直接識(shí)別原始指針事件來(lái)解決沖突的方法很簡(jiǎn)單,因此晌坤,當(dāng)遇到手勢(shì)沖突時(shí)逢艘,我們應(yīng)該優(yōu)先考慮 Listener 旦袋。

4.2 通過(guò)自定義 Recognizer 解決手勢(shì)沖突

自定義手勢(shì)識(shí)別器的方式比較麻煩,原理是當(dāng)確定手勢(shì)競(jìng)爭(zhēng)勝出者時(shí)它改,會(huì)調(diào)用勝出者的acceptGesture 方法疤孕,表示“宣布成功”,然后會(huì)調(diào)用其它手勢(shì)識(shí)別的rejectGesture 方法央拖,表示“宣布失敗”祭阀。既然如此,我們可以自定義手勢(shì)識(shí)別器(Recognizer)鲜戒,然后去重寫(xiě)它的rejectGesture 方法:在里面調(diào)用acceptGesture 方法专控,這就相當(dāng)于它失敗是強(qiáng)制將它也變成競(jìng)爭(zhēng)的成功者了,這樣它的回調(diào)也就會(huì)執(zhí)行遏餐。

我們先自定義tap手勢(shì)識(shí)別器(Recognizer):


class MSCustomTapGestureRecognizer extends TapGestureRecognizer {
  @override
  void rejectGesture(int pointer) {
    //強(qiáng)制宣布成功
    super.acceptGesture(pointer);
  }
}

//創(chuàng)建一個(gè)新的GestureDetector伦腐,用我們自定義的 CustomTapGestureRecognizer 替換默認(rèn)的
RawGestureDetector customGestureDetector({
  GestureTapCallback? onTap,
  GestureTapDownCallback? onTapDown,
  Widget? child,
}) {
  return RawGestureDetector(
    child: child,
    gestures: {
      MSCustomTapGestureRecognizer:
          GestureRecognizerFactoryWithHandlers<MSCustomTapGestureRecognizer>(
        () => MSCustomTapGestureRecognizer(),
        (MSCustomTapGestureRecognizer rec) {
          rec
            ..onTap = onTap
            ..onTapDown = onTapDown;
        },
      ),
    },
  );
}

我們通過(guò) RawGestureDetector 來(lái)自定義 customGestureDetector,GestureDetector 中也是通過(guò) RawGestureDetector 來(lái)包裝各種Recognizer 來(lái)實(shí)現(xiàn)的失都,我們需要自定義哪個(gè) Recognizer柏蘑,就添加哪個(gè)即可。

現(xiàn)在我們看看修改調(diào)用代碼:將GestureDetector 替換為customGestureDetector


class MSGestureDetailDemo6 extends StatelessWidget {
  const MSGestureDetailDemo6({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("MSGestureDetailDemo5")),
      body: Center(
        child: customGestureDetector(
          onTap: () => print("2"), // 監(jiān)聽(tīng)父組件 tapUp 手勢(shì)
          child: Container(
            width: 200,
            height: 200,
            color: Colors.red,
            alignment: Alignment.center,
            child: customGestureDetector(
              onTap: () => print("1"), // 監(jiān)聽(tīng)子組件 tapUp 手勢(shì)
              child: Container(
                width: 100,
                height: 100,
                color: Colors.grey,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

這樣就 OK 了嗅剖,需要注意辩越,這個(gè)例子同時(shí)說(shuō)明了一次手勢(shì)處理過(guò)程也是可以有多個(gè)勝出者的嘁扼。

https://book.flutterchina.club/chapter8/gesture_conflict.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末信粮,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子趁啸,更是在濱河造成了極大的恐慌强缘,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件不傅,死亡現(xiàn)場(chǎng)離奇詭異旅掂,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)访娶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)商虐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人崖疤,你說(shuō)我怎么就攤上這事秘车。” “怎么了劫哼?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵叮趴,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我权烧,道長(zhǎng)眯亦,這世上最難降的妖魔是什么伤溉? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮妻率,結(jié)果婚禮上乱顾,老公的妹妹穿的比我還像新娘。我一直安慰自己宫静,他們只是感情好糯耍,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著囊嘉,像睡著了一般温技。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上扭粱,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天舵鳞,我揣著相機(jī)與錄音,去河邊找鬼琢蛤。 笑死蜓堕,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的博其。 我是一名探鬼主播套才,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼慕淡!你這毒婦竟也來(lái)了背伴?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤峰髓,失蹤者是張志新(化名)和其女友劉穎傻寂,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體携兵,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡疾掰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了徐紧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片静檬。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖并级,靈堂內(nèi)的尸體忽然破棺而出拂檩,到底是詐尸還是另有隱情,我是刑警寧澤死遭,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布广恢,位于F島的核電站,受9級(jí)特大地震影響呀潭,放射性物質(zhì)發(fā)生泄漏钉迷。R本人自食惡果不足惜至非,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望糠聪。 院中可真熱鬧荒椭,春花似錦、人聲如沸舰蟆。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)身害。三九已至味悄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間塌鸯,已是汗流浹背侍瑟。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丙猬,地道東北人涨颜。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像茧球,于是被迫代替她去往敵國(guó)和親庭瑰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

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