Android事件分發(fā)流程(API-27)(二)

Android事件分發(fā)流程(API-27)(1)中我們有分析到當(dāng)我們在屏幕的一個點擊會走到Activity.dispatchTouchEvent()啃匿,現(xiàn)在我們分析Actvitiy.dispatchTouchEvent()之后的流程

  • Activity分發(fā)流程

    • Activity.dispatchTouchEvent()

      public boolean dispatchTouchEvent(MotionEvent ev) {
          if (ev.getAction() == MotionEvent.ACTION_DOWN) {
              // onUserInteraction是Activity的一個空方法,當(dāng)有用戶和設(shè)備有交互就會觸發(fā)
              onUserInteraction();
          }
          if (getWindow().superDispatchTouchEvent(ev)) {
              // 調(diào)用window的superDispatchTouchEvent(), 這里的getWindow我們都知道是PhoneWindow
              return true;
          }
          return onTouchEvent(ev);
      }
      

      這里我們看到當(dāng)PhoneWindow返回true就會被攔截掉,dispatchTouchEvent直接返回true,而不再執(zhí)行Activity.onTouchEvent()方法了

    • PhoneWindow.superDispatchTouchEvent()

      @Override
      public boolean superDispatchTouchEvent(MotionEvent event) {
          // 這里的mDecor我們都知道是DecorView,所以PhoneWindow直接交給了DecorView處理了
          return mDecor.superDispatchTouchEvent(event);
      }
      
    • DecorView.superDispatchTouchEvent()

      public boolean superDispatchTouchEvent(MotionEvent event) {
          // DecorView繼承的FrameLayout,F(xiàn)rameLayout繼承ViewGroup,而FrameLayout并沒有重寫dispatchTouchEvent()方法绿饵,所以直接調(diào)用ViewGroup的該方法
          return super.dispatchTouchEvent(event);
      }
      

      上面的流程我們可以知道,在getWindow().superDispatchTouchEvent(ev)的流程交給了ViewGroup處理瓶颠,我們先來看看ViewGroup返回false之后的流程Activity.onTouchEvent()

    • Activity.onTouchEvent()

      public boolean onTouchEvent(MotionEvent event) {
          // mWindow是PhoneWindow拟赊,PhoneWindow繼承Window這個抽象類,PhoneWindow沒有重寫該方法粹淋,直接看Window的該方法
          if (mWindow.shouldCloseOnTouch(this, event)) {
              finish();
              return true;
          }
      
          return false;
      }
      
    • Window.shouldCloseOnTouch()

      /** @hide */
      public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
          final boolean isOutside =
              event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event)
              || event.getAction() == MotionEvent.ACTION_OUTSIDE;
          if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
              // 如果是在Window的區(qū)域之外吸祟,則返回true
              return true;
          }
          // 否則返回false
          return false;
      }
      
      // 這里主要是判斷是否在Window的邊界外
      private boolean isOutOfBounds(Context context, MotionEvent event) {
          final int x = (int) event.getX();
          final int y = (int) event.getY();
          final int slop = ViewConfiguration.get(context).getScaledWindowTouchSlop();
          final View decorView = getDecorView();
          return (x < -slop) || (y < -slop)
              || (x > (decorView.getWidth()+slop))
              || (y > (decorView.getHeight()+slop));
      }
      

      所以Activity.onTouchEvent()主要是判斷是否在Window的區(qū)域之外瑟慈,如果在區(qū)域之外則finish當(dāng)前Activity,并返回true屋匕,否則返回false葛碧,總之到這里ActivitydispatchTouchEvent的事件分發(fā)流程就結(jié)束了

    • 流程圖

      Activity事件分發(fā)流程.jpg
  • ViewGroup分發(fā)流程

    上面我們分析了Activity層的事件分發(fā)的簡單流程,現(xiàn)在來看下ViewGroup.dispatchTouchEvent之后的流程是怎樣的

    • ViewGroup.dispatchTouchEvent里的流程較多过吻,我們只看主要的代碼

      @Override
      public boolean dispatchTouchEvent(MotionEvent ev) {
          if (mInputEventConsistencyVerifier != null) {
              // 用于調(diào)試进泼,輸入事件一致性校驗,View中的InputEventConsistencyVerifier屬性
              mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
          }
      
          // If the event targets the accessibility focused view and this is it, start
          // normal event dispatch. Maybe a descendant is what will handle the click.
          // 處理輔助功能
          if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
              ev.setTargetAccessibilityFocus(false);
          }
      
          boolean handled = false; // 返回值纤虽,默認(rèn)false
          if (onFilterTouchEventForSecurity(ev)) {
              // 安全策略過濾
              final int action = ev.getAction();
              final int actionMasked = action & MotionEvent.ACTION_MASK;
      
              // Handle an initial down.
              if (actionMasked == MotionEvent.ACTION_DOWN) {
                  // Throw away all previous state when starting a new touch gesture.
                  // The framework may have dropped the up or cancel event for the previous gesture
                  // due to an app switch, ANR, or some other state change.
                  cancelAndClearTouchTargets(ev);
                  resetTouchState();
              }
      
              // Check for interception.
              // 當(dāng)前ViewGroup是否要攔截
              final boolean intercepted;
              if (actionMasked == MotionEvent.ACTION_DOWN
                  || mFirstTouchTarget != null) {
                  // FLAG_DISALLOW_INTERCEPT 禁止攔截標(biāo)識乳绕,可以調(diào)用requestDisallowInterceptTouchEvent(boolean disallowIntercept)禁止父ViewGroup攔截
                  final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                  if (!disallowIntercept) {
                      // 1. 重點來了,調(diào)用了ViewGroup.onInterceptTouchEvent
                      intercepted = onInterceptTouchEvent(ev);
                      ev.setAction(action); // restore action in case it was changed
                  } else {
                      // 不進行攔截
                      intercepted = false;
                  }
              } else {
                  // There are no touch targets and this action is not an initial down
                  // so this view group continues to intercept touches.
                  intercepted = true;
              }
      
              // If intercepted, start normal event dispatch. Also if there is already
              // a view that is handling the gesture, do normal event dispatch.
              if (intercepted || mFirstTouchTarget != null) {
                  ev.setTargetAccessibilityFocus(false);
              }
      
              // Check for cancelation.
              // 當(dāng)前事件是否被取消
              final boolean canceled = resetCancelNextUpFlag(this)
                  || actionMasked == MotionEvent.ACTION_CANCEL;
      
              // Update list of touch targets for pointer down, if needed.
              final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
              TouchTarget newTouchTarget = null;
              boolean alreadyDispatchedToNewTouchTarget = false;
              if (!canceled && !intercepted) {
                  // 如果事件沒有被取消也沒有被攔截逼纸,則分發(fā)給對應(yīng)的子View/ViewGroup
                  // If the event is targeting accessiiblity focus we give it to the
                  // view that has accessibility focus and if it does not handle it
                  // we clear the flag and dispatch the event to all children as usual.
                  // We are looking up the accessibility focused host to avoid keeping
                  // state since these events are very rare.
                  View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                      ? findChildWithAccessibilityFocus() : null;
      
                  if (actionMasked == MotionEvent.ACTION_DOWN
                      || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                      || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                      final int actionIndex = ev.getActionIndex(); // always 0 for down
                      final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                          : TouchTarget.ALL_POINTER_IDS;
      
                      // Clean up earlier touch targets for this pointer id in case they
                      // have become out of sync.
                      removePointersFromTouchTargets(idBitsToAssign);
      
                      final int childrenCount = mChildrenCount;
                      if (newTouchTarget == null && childrenCount != 0) {
                          final float x = ev.getX(actionIndex);
                          final float y = ev.getY(actionIndex);
                          // Find a child that can receive the event.
                          // Scan children from front to back.
                          final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                          final boolean customOrder = preorderedList == null
                              && isChildrenDrawingOrderEnabled();
                          // 遍歷所有的子View/ViewGroup
                          final View[] children = mChildren;
                          for (int i = childrenCount - 1; i >= 0; i--) {
                              final int childIndex = getAndVerifyPreorderedIndex(
                                  childrenCount, i, customOrder);
                              final View child = getAndVerifyPreorderedView(
                                  preorderedList, children, childIndex);
      
                              // If there is a view that has accessibility focus we want it
                              // to get the event first and if not handled we will perform a
                              // normal dispatch. We may do a double iteration but this is
                              // safer given the timeframe.
                              if (childWithAccessibilityFocus != null) {
                                  if (childWithAccessibilityFocus != child) {
                                      continue;
                                  }
                                  childWithAccessibilityFocus = null;
                                  i = childrenCount - 1;
                              }
                              
                              // 子控件是否能夠接受事件或者點擊位置是否在該子控件上
                              if (!canViewReceivePointerEvents(child)
                                  || !isTransformedTouchPointInView(x, y, child, null)) {
                                  ev.setTargetAccessibilityFocus(false);
                                  continue;
                              }
      
                              newTouchTarget = getTouchTarget(child);
                              if (newTouchTarget != null) {
                                  // Child is already receiving touch within its bounds.
                                  // Give it the new pointer in addition to the ones it is handling.
                                  newTouchTarget.pointerIdBits |= idBitsToAssign;
                                  break;
                              }
      
                              resetCancelNextUpFlag(child);
                              // 調(diào)用子控件進行分發(fā) dispatchTransformedTouchEvent
                              if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                  // Child wants to receive touch within its bounds.
                                  mLastTouchDownTime = ev.getDownTime();
                                  if (preorderedList != null) {
                                      // childIndex points into presorted list, find original index
                                      for (int j = 0; j < childrenCount; j++) {
                                          if (children[childIndex] == mChildren[j]) {
                                              mLastTouchDownIndex = j;
                                              break;
                                          }
                                      }
                                  } else {
                                      mLastTouchDownIndex = childIndex;
                                  }
                                  mLastTouchDownX = ev.getX();
                                  mLastTouchDownY = ev.getY();
                                  newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                  alreadyDispatchedToNewTouchTarget = true;
                                  break;
                              }
      
                              // The accessibility focus didn't handle the event, so clear
                              // the flag and do a normal dispatch to all children.
                              ev.setTargetAccessibilityFocus(false);
                          }
                          if (preorderedList != null) preorderedList.clear();
                      }
      
                      if (newTouchTarget == null && mFirstTouchTarget != null) {
                          // Did not find a child to receive the event.
                          // Assign the pointer to the least recently added target.
                          newTouchTarget = mFirstTouchTarget;
                          while (newTouchTarget.next != null) {
                              newTouchTarget = newTouchTarget.next;
                          }
                          newTouchTarget.pointerIdBits |= idBitsToAssign;
                      }
                  }
              }
      
              // Dispatch to touch targets.
              if (mFirstTouchTarget == null) {
                  // No touch targets so treat this as an ordinary view.
                  // 沒有子視圖接收洋措,分發(fā)給當(dāng)前視圖
                  handled = dispatchTransformedTouchEvent(ev, canceled, null,
                                                          TouchTarget.ALL_POINTER_IDS);
              } else {
                  // Dispatch to touch targets, excluding the new touch target if we already
                  // dispatched to it.  Cancel touch targets if necessary.
                  TouchTarget predecessor = null;
                  TouchTarget target = mFirstTouchTarget;
                  while (target != null) {
                      final TouchTarget next = target.next;
                      if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                          handled = true;
                      } else {
                          final boolean cancelChild = resetCancelNextUpFlag(target.child)
                              || intercepted;
                          if (dispatchTransformedTouchEvent(ev, cancelChild,
                                                            target.child, target.pointerIdBits)) {
                              handled = true;
                          }
                          if (cancelChild) {
                              if (predecessor == null) {
                                  mFirstTouchTarget = next;
                              } else {
                                  predecessor.next = next;
                              }
                              target.recycle();
                              target = next;
                              continue;
                          }
                      }
                      predecessor = target;
                      target = next;
                  }
              }
      
              // Update list of touch targets for pointer up or cancel, if needed.
              if (canceled
                  || actionMasked == MotionEvent.ACTION_UP
                  || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                  resetTouchState();
              } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
                  final int actionIndex = ev.getActionIndex();
                  final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                  removePointersFromTouchTargets(idBitsToRemove);
              }
          }
      
          if (!handled && mInputEventConsistencyVerifier != null) {
              mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
          }
          return handled;
      }
      
    • 將事件分發(fā)給指定的控件dispatchTransformedTouchEvent

      private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                                                    View child, int desiredPointerIdBits) {
          final boolean handled;
      
          // Canceling motions is a special case.  We don't need to perform any transformations
          // or filtering.  The important part is the action, not the contents.
          // 事件取消
          final int oldAction = event.getAction();
          if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
              event.setAction(MotionEvent.ACTION_CANCEL);
              if (child == null) {
                  // 調(diào)用onTouchEvent方法處理
                  handled = super.dispatchTouchEvent(event);
              } else {
                  handled = child.dispatchTouchEvent(event);
              }
              event.setAction(oldAction);
              return handled;
          }
      
          // Calculate the number of pointers to deliver.
          // 計算觸摸事件id
          final int oldPointerIdBits = event.getPointerIdBits();
          final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
      
          // If for some reason we ended up in an inconsistent state where it looks like we
          // might produce a motion event with no pointers in it, then drop the event.
          if (newPointerIdBits == 0) {
              return false;
          }
      
          // If the number of pointers is the same and we don't need to perform any fancy
          // irreversible transformations, then we can reuse the motion event for this
          // dispatch as long as we are careful to revert any changes we make.
          // Otherwise we need to make a copy.
          final MotionEvent transformedEvent;
          if (newPointerIdBits == oldPointerIdBits) {
              // 前后id相同,不需要重新計算
              if (child == null || child.hasIdentityMatrix()) {
                  if (child == null) {
                      // super這里是View樊展,child為空,把自己當(dāng)成View堆生,調(diào)用dispatchTouchEvent方法
                      // 調(diào)用onTouchEvent方法處理
                      handled = super.dispatchTouchEvent(event);
                  } else {
                      // 不為空专缠,調(diào)用相應(yīng)子View/ViewGroup的dispatchTouchEvent
                      final float offsetX = mScrollX - child.mLeft;
                      final float offsetY = mScrollY - child.mTop;
                      event.offsetLocation(offsetX, offsetY);
      
                      handled = child.dispatchTouchEvent(event);
      
                      event.offsetLocation(-offsetX, -offsetY);
                  }
                  return handled;
              }
              transformedEvent = MotionEvent.obtain(event);
          } else {
              transformedEvent = event.split(newPointerIdBits);
          }
      
          // Perform any necessary transformations and dispatch.
          if (child == null) {
              handled = super.dispatchTouchEvent(transformedEvent);
          } else {
              final float offsetX = mScrollX - child.mLeft;
              final float offsetY = mScrollY - child.mTop;
              transformedEvent.offsetLocation(offsetX, offsetY);
              if (! child.hasIdentityMatrix()) {
                  transformedEvent.transform(child.getInverseMatrix());
              }
      
              handled = child.dispatchTouchEvent(transformedEvent);
          }
      
          // Done.
          transformedEvent.recycle();
          return handled;
      }
      
    • 是否攔截方法,ViewGroup.onInterceptTouchEvent

      public boolean onInterceptTouchEvent(MotionEvent ev) {
          if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
              && ev.getAction() == MotionEvent.ACTION_DOWN
              && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
              && isOnScrollbarThumb(ev.getX(), ev.getY())) {
              // true攔截
              return true;
          }
          // false不攔截
          return false;
      }
      
    • ViewGroup的分發(fā)流程相對較為復(fù)雜淑仆,簡單流程圖如下

      ViewGroup事件分發(fā)流程.jpg
  • View分發(fā)流程

    • 接下來分析View.dispatchTouchEvent

      /**
       * Pass the touch screen motion event down to the target view, or this
       * view if it is the target.
       *
       * @param event The motion event to be dispatched.
       * @return True if the event was handled by the view, false otherwise.
       */
      public boolean dispatchTouchEvent(MotionEvent event) {
          // If the event should be handled by accessibility focus first.
          if (event.isTargetAccessibilityFocus()) {
              // We don't have focus or no virtual descendant has it, do not handle the event.
              if (!isAccessibilityFocusedViewOrHost()) {
                  return false;
              }
              // We have focus and got the event, then use normal event dispatch.
              event.setTargetAccessibilityFocus(false);
          }
      
          // 返回值涝婉,默認(rèn)false
          boolean result = false;
      
          if (mInputEventConsistencyVerifier != null) {
              mInputEventConsistencyVerifier.onTouchEvent(event, 0);
          }
      
          final int actionMasked = event.getActionMasked();
          if (actionMasked == MotionEvent.ACTION_DOWN) {
              // Defensive cleanup for new gesture
              stopNestedScroll();
          }
      
          if (onFilterTouchEventForSecurity(event)) {
              if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                  // View是Enable狀態(tài)且處理ScrollBar的drag操作返回true
                  result = true;
              }
              //noinspection SimplifiableIfStatement
              ListenerInfo li = mListenerInfo;
              if (li != null && li.mOnTouchListener != null
                  && (mViewFlags & ENABLED_MASK) == ENABLED
                  && li.mOnTouchListener.onTouch(this, event)) {
                  // mOnTouchListener不為空(設(shè)置了setOnTouchListener)
                  // 且View是Enable狀態(tài)
                  // 且OnTouchListener.onTouch返回true
                  result = true;
              }
      
              if (!result && onTouchEvent(event)) {
                  // result為false則執(zhí)行onTouchEvent方法
                  result = true;
              }
          }
      
          if (!result && mInputEventConsistencyVerifier != null) {
              mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
          }
      
          // Clean up after nested scrolls if this is the end of a gesture;
          // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
          // of the gesture.
          if (actionMasked == MotionEvent.ACTION_UP ||
              actionMasked == MotionEvent.ACTION_CANCEL ||
              (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
              stopNestedScroll();
          }
      
          return result;
      }
      

      當(dāng)resultfalse時,會調(diào)用onTouchEvent方法蔗怠,這里OnTouchListener.onTouch方法會執(zhí)行在performClick之前墩弯,即可能會攔截OnClickListener事件,使得onClick方法不會執(zhí)行寞射,所以當(dāng)我們重寫setOnTouchListener方法時AS往往會給我們一個如下提示

      onTouch should call View#performClick when a click is detected less... (?F1) 
      If a View that overrides onTouchEvent or uses an OnTouchListener does not also implement performClick and call it when clicks are detected, the View may not handle accessibility actions properly. Logic handling the click actions should ideally be placed in View#performClick as some accessibility services invoke performClick when a click action should occur.
      
    • View.onTouchEvent

      public boolean onTouchEvent(MotionEvent event) {
          ...
      
          if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
              switch (action) {
                  case MotionEvent.ACTION_UP:
                      // 抬起
                      ...
                      boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                      if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                          // take focus if we don't have it already and we should in
                          // touch mode.
                          boolean focusTaken = false;
                          if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                              focusTaken = requestFocus();
                          }
      
                          if (prepressed) {
                              // The button is being released before we actually
                              // showed it as pressed.  Make it show the pressed
                              // state now (before scheduling the click) to ensure
                              // the user sees it.
                              setPressed(true, x, y);
                          }
      
                          if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                              // This is a tap, so remove the longpress check
                              removeLongPressCallback();
      
                              // Only perform take click actions if we were in the pressed state
                              // 執(zhí)行點擊操作
                              if (!focusTaken) {
                                  // Use a Runnable and post this rather than calling
                                  // performClick directly. This lets other visual state
                                  // of the view update before click actions start.
                                  if (mPerformClick == null) {
                                      mPerformClick = new PerformClick();
                                  }
                                  // 通過Handler.post(Runnable)方式執(zhí)行performClick方法
                                  if (!post(mPerformClick)) {
                                      // post方式失敗則直接調(diào)用performClick
                                      performClick();
                                  }
                              }
                          }
                      ...
                      break;
      
                  case MotionEvent.ACTION_DOWN:
                      // 按下
                      ...
                      break;
      
                  case MotionEvent.ACTION_CANCEL:
                      // 取消
                      ...
                      break;
      
                  case MotionEvent.ACTION_MOVE:
                      // 滑動
                      ...
                      break;
              }
      
              return true;
          }
      
          return false;
      }
      
    • View.performClick()

      public boolean performClick() {
          final boolean result;
          final ListenerInfo li = mListenerInfo;
          if (li != null && li.mOnClickListener != null) {
              // mClickListener不為空渔工,即setOnClickListener()
              playSoundEffect(SoundEffectConstants.CLICK);
              // 執(zhí)行onClick方法
              li.mOnClickListener.onClick(this);
              result = true;
          } else {
              result = false;
          }
      
          sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
      
          notifyEnterOrExitForAutoFillIfNeeded(true);
          // 默認(rèn)返回false
          return result;
      }
      
      
    • 流程圖

      View事件分發(fā)流程.jpg
  • 整體流程

    Android事件分發(fā)泳道圖.jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末桥温,一起剝皮案震驚了整個濱河市引矩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌侵浸,老刑警劉巖旺韭,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異掏觉,居然都是意外死亡区端,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門澳腹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來织盼,“玉大人杨何,你說我怎么就攤上這事』谡” “怎么了晚吞?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長谋国。 經(jīng)常有香客問我槽地,道長,這世上最難降的妖魔是什么芦瘾? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任捌蚊,我火速辦了婚禮,結(jié)果婚禮上近弟,老公的妹妹穿的比我還像新娘缅糟。我一直安慰自己,他們只是感情好祷愉,可當(dāng)我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布窗宦。 她就那樣靜靜地躺著,像睡著了一般二鳄。 火紅的嫁衣襯著肌膚如雪赴涵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天订讼,我揣著相機與錄音髓窜,去河邊找鬼。 笑死欺殿,一個胖子當(dāng)著我的面吹牛寄纵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播脖苏,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼程拭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了棍潘?” 一聲冷哼從身側(cè)響起哺壶,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蜒谤,沒想到半個月后山宾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡鳍徽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年资锰,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阶祭。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡绷杜,死狀恐怖直秆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鞭盟,我是刑警寧澤圾结,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站齿诉,受9級特大地震影響筝野,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜粤剧,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一歇竟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧抵恋,春花似錦焕议、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至世囊,卻和暖如春别瞭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背茸习。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工畜隶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留壁肋,地道東北人号胚。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像浸遗,于是被迫代替她去往敵國和親猫胁。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,446評論 2 348

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