android view事件分發(fā)機制

android 事件分發(fā)機制

1.android 點擊事件

當用戶觸摸屏幕時(View 或 ViewGroup派生的控件),將產(chǎn)生點擊事件(Touch事件)
一共包含四種事件類型
事件類型 具體動作
MotionEvent.ACTION_DOWN 按下View(所有事件的開始)

MotionEvent.ACTION_UP 抬起View(與DOWN對應)

MotionEvent.ACTION_MOVE 滑動View

MotionEvent.ACTION_CANCEL 結(jié)束事件(非人為原因)

一般情況下,事件列都是以DOWN事件開始铁追、UP事件結(jié)束,中間有無數(shù)的MOVE事件,當一個點擊事件(MotionEvent )產(chǎn)生后,
系統(tǒng)需把這個事件傳遞給一個具體的 View 去處理

2.事件的傳遞

事件傳遞順序: activity ——>ViewGroup——>View
事件分發(fā)過程由哪些方法協(xié)作完成等限?
dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()

dispatchTouchEvent():分發(fā)(傳遞)事件,當點擊事件可以傳遞時芬膝,這個方法就會被調(diào)用
onTouchEvent():處理點擊事件,在dispatchTouchEvent()內(nèi)部調(diào)用
onInterceptTouchEvent():判斷是否攔截某個事件,在ViewGroup的dispatchTouchEvent()內(nèi)部調(diào)用
下面我們逐個來分析各事件的傳遞

2.1.Activity的事件傳遞

當一個點擊事件發(fā)生時望门,事件最先傳到Activity的dispatchTouchEvent()進行事件分發(fā),源碼入下:

/**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     *
     * @param ev The touch screen event.
     *
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

分析2:getWindow().superDispatchTouchEvent(ev)
說明:
a. getWindow() = 獲取Window類的對象
b. Window類是抽象類,其唯一實現(xiàn)類 = PhoneWindow類锰霜;即此處的Window類對象 = PhoneWindow類對象
c. Window類的superDispatchTouchEvent() = 1個抽象方法筹误,由子類PhoneWindow類實現(xiàn)
DecorView類是PhoneWindow類的一個內(nèi)部類
b. DecorView繼承自FrameLayout,是所有界面的父類癣缅,F(xiàn)rameLayout是ViewGroup的子類厨剪,故DecorView的間接父類 = ViewGroup

這就實現(xiàn)了事件從activity 傳遞到viewGroup,我們看看源碼:(截取部分代碼)

2.2.ViewGroup事件分發(fā)

我們看源碼中核心分發(fā)方法dispatchTouchEvent

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        
            ...

            // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                
            // 重點分析1:ViewGroup每次事件分發(fā)時,都需調(diào)用onInterceptTouchEvent()詢問是否攔截事件
            // 判斷值1:disallowIntercept = 是否禁用事件攔截的功能(默認是false)友存,可通過調(diào)用requestDisallowInterceptTouchEvent()修改
            // 判斷值2: onInterceptTouchEvent(ev)
                        // a. 若在onInterceptTouchEvent()中返回false(即不攔截事件)祷膳,intercepted值為true,從而進入到條件判斷的內(nèi)部
                        // b. 若在onInterceptTouchEvent()中返回true(即攔截事件)屡立,intercepted值為false直晨,從而跳出了這個條件判斷
                        // c. 關(guān)于onInterceptTouchEvent() ->>分析1        
                    
                    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.
            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;
            
            //重點分析2:只有當canceled 、intercepted都為false 時,事件傳遞才能進行下去抡秆,接下來遍歷所有子view
            找到被觸摸或者點擊的子view
            if (!canceled && !intercepted) {

                ...

                    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();
                        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);
                            //重點分析3:將事件繼續(xù)傳遞到子view 中奕巍,
                            // 若該控件可點擊,那么點擊時儒士,dispatchTouchEvent的返回值必定是true,因此會導致條件判斷成立
                            alreadyDispatchedToNewTouchTarget 賦值為true,并且直接跳出遍歷
                            // 于是給ViewGroup的dispatchTouchEvent()直接返回了true檩坚,即直接跳出
                            // 即把ViewGroup的點擊事件攔截掉   
                            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.
                //沒有找到目標view,則viewGroup自己處理該事件着撩,調(diào)用onTouch——>onTouchEvent()
                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;
                }
            }

            ...
        return handled;
    }
2.3.View的事件分發(fā)過程

接下來我們看view的分發(fā)

public boolean dispatchTouchEvent(MotionEvent event) {
        ...

        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            /**重點分析1:*/
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                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;
    }

重點分析1:
條件1:mOnTouchListener != null,說明:mOnTouchListener變量在View.setOnTouchListener()方法里賦值匾委,也就是說
如果有設置view 的觸摸事件回調(diào)拖叙,那么,view 會優(yōu)先執(zhí)行onTouch方法
條件2:(mViewFlags & ENABLED_MASK) == ENABLED赂乐,該條件是判斷當前點擊的控件是否enable薯鳍,由于很多View默認enable,
故該條件恒定為true

條件3:mOnTouchListener.onTouch(this, event),說明:即 回調(diào)控件注冊Touch事件時的onTouch()挨措;需手動復寫設置,
以button 為例挖滤,

button.setOnTouchListener(new OnTouchListener() {  
        @Override  
        public boolean onTouch(View v, MotionEvent event) {  
     
            return false;  
        }  
    });

若在onTouch()返回true,就會讓上述三個條件全部成立浅役,從而使得View.dispatchTouchEvent()直接返回true斩松,事件分發(fā)結(jié)束
若在onTouch()返回false,就會使得上述三個條件不全部成立觉既,從而使得View.dispatchTouchEvent()中跳出If惧盹,執(zhí)行onTouchEvent(event)

繼續(xù)來看onTouchEvent()事件處理

public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();

        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return clickable;
        }
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
        //若該控件可點擊,則進入switch判斷中
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
             //若當前的事件是抬起View(主要分析)
                case MotionEvent.ACTION_UP:
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                        handleTooltipUp();
                    }
                    if (!clickable) {
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        break;
                    }
                    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
                            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();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }

                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_DOWN:
                    if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                        mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                    }
                    mHasPerformedLongPress = false;

                    if (!clickable) {
                        checkForLongClick(0, x, y);
                        break;
                    }

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0, x, y);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    if (clickable) {
                        setPressed(false);
                    }
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    break;

                case MotionEvent.ACTION_MOVE:
                    if (clickable) {
                        drawableHotspotChanged(x, y);
                    }

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        // Remove any future long press/tap checks
                        removeTapCallback();
                        removeLongPressCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            setPressed(false);
                        }
                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    }
                    break;
            }
            // 若該控件可點擊瞪讼,就一定返回true
            return true;
        }
            // 若該控件不可點擊钧椰,就一定返回false
        return false;
    }

當view 可點擊切且手勢為抬手,進過一些判斷符欠,會進入執(zhí)行performClick()方法嫡霞,這里會判斷是否有設置點擊事件setOnclickListener(),
如果有背亥,執(zhí)行onClick(),并返回true

分析到這里秒际,我們可以發(fā)現(xiàn),view的 onTouch()的執(zhí)行 先于 onClick()

總結(jié):
1.dispatchTouchEvent() 逐上至下調(diào)用 Activity——>viewGroup——>View
2.onTouchEvent() 逐下至上級返回 view——>ViewGroup——>Activity

3.View的滑動沖突

滑動沖突有三種解決方式

  • 外部攔截法
  • 內(nèi)部攔截法
3.1 外部攔截法

點擊事件都先進過父容器的攔截處理狡汉,如果父容器需要此事件娄徊,就攔截,不需要則不攔截盾戴,外部攔截方法需要重寫父容器的onInterceptTouchEvent方法寄锐,在內(nèi)部做相應的攔截即可,類似這樣的偽代碼:

    public boolean onInterceptTouchEvent(MotionEvent event){
    
    boolean intercepted = false;
    int x = (int)event.getX(); 
    int y = (int)event.getY();
    switch(event.getAction()){
        case MotionEvent.ACTION_DOWN:{
            intercepted = false;
            break;
          }
        case MotionEvent.ACTION_MOVE:{
            if(父容器需要當前點擊事件)
            {
            intercepted = true;
              }else {
                intercepted = false;
            }
            break;
          }
        case MotionEvent.ACTION_UP:{
              intercepted = false;
              break;
          }
    
        default:
        break;  
      }
    mLastXIntercept = x;
    mLastYIntercept = y;

    return intercepted;
}
3.2 內(nèi)部攔截法

內(nèi)部攔截法指父容器不攔截任何事件,所有事件都傳給子元素橄仆,如果子元素需要則消耗掉剩膘,不需要則交還給父容器處理,盆顾,需配合requestDisallowInterceptTouchEvent()方法才能正常工作怠褐,我們需要重寫子元素的
dispatchTouchEvent()方法

public boolean dispatchTouchEvent(MotionEvent event){
    

    int x = (int)event.getX(); 
    int y = (int)event.getY();
    switch(event.getAction()){
        case MotionEvent.ACTION_DOWN:{
              parent.requestDisallowInterceptTouchEvent(true);
              break;
          }
        case MotionEvent.ACTION_MOVE:{
              int deltaX = x-mLastX;
              int deltaY = y-mLastY;
              if(父容器需要當前點擊事件)
              {
                parent.requestDisallowInterceptTouchEvent(false);
              }
              break;
          }
        case MotionEvent.ACTION_UP:{

              break;
          }

        default:
        break;  
      }
      mLastX = x;
      mLastY = y;

    return super.dispatchTouchEvent(event);
}

除了子元素需要處理外,父元素也需要默認攔截除了ACTION_DOWN以外的其他事件您宪,這樣當子元素調(diào)用
parent.requestDisallowInterceptTouchEvent(false)方法時奈懒,父元素才能繼續(xù)攔截所需的事件。
父元素修改如下:

    public Boolean onInterceptTouchEvent(MotionEvent event){
    
        int action =  event.getAction();
        if(action == MotionEvent.ACTION_DOWN){
                renturn false;
        }else {
                return true;
        }
    
    }

文章有部分參考自:http://www.reibang.com/p/38015afcdb58

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末宪巨,一起剝皮案震驚了整個濱河市磷杏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌捏卓,老刑警劉巖极祸,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異怠晴,居然都是意外死亡遥金,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門龄寞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來汰规,“玉大人,你說我怎么就攤上這事物邑×锵” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵色解,是天一觀的道長茂嗓。 經(jīng)常有香客問我,道長科阎,這世上最難降的妖魔是什么述吸? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮锣笨,結(jié)果婚禮上蝌矛,老公的妹妹穿的比我還像新娘。我一直安慰自己错英,他們只是感情好入撒,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著椭岩,像睡著了一般茅逮。 火紅的嫁衣襯著肌膚如雪璃赡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天献雅,我揣著相機與錄音碉考,去河邊找鬼。 笑死挺身,一個胖子當著我的面吹牛侯谁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播章钾,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼良蒸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了伍玖?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤剿吻,失蹤者是張志新(化名)和其女友劉穎窍箍,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體丽旅,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡椰棘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了榄笙。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片邪狞。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖茅撞,靈堂內(nèi)的尸體忽然破棺而出帆卓,到底是詐尸還是另有隱情,我是刑警寧澤米丘,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布剑令,位于F島的核電站,受9級特大地震影響拄查,放射性物質(zhì)發(fā)生泄漏吁津。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一堕扶、第九天 我趴在偏房一處隱蔽的房頂上張望碍脏。 院中可真熱鬧,春花似錦稍算、人聲如沸典尾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽急黎。三九已至扎狱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間勃教,已是汗流浹背淤击。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留故源,地道東北人污抬。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像绳军,于是被迫代替她去往敵國和親印机。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345