View 事件分發(fā)機(jī)制

View 事件簡介

View 事件悼潭,既 MotionEvent江解,是用戶觸摸屏幕的一系列事件贤牛。同一事件序列是從手指接觸屏幕的那一刻起构蹬,到手指離開屏幕的那一刻結(jié)束,在這個(gè)過程中所產(chǎn)生的一系列事件悔据,這個(gè)事件序列以 ACTION_DOWN 開始庄敛,中間含有一系列的 ACTION_MOVE,最終以 ACTION_UP 結(jié)束科汗。

View 事件分發(fā)簡述

當(dāng)用戶點(diǎn)擊屏幕的時(shí)候藻烤,TouchEvent 最先傳遞給Activity.dispatchTouchEvent(MotionEvent),然后再調(diào)用DecorView.superDispatchTouchEvent(MotionEvent)头滔,接著直接調(diào)用super.dispatchTouchEvent(MotionEvent)怖亭,即ViewGroup.dispatchTouchEvent(MotionEvent)

ViewGroup 和 View 分發(fā)事件拙毫,有三個(gè)很重要的方法:

  1. public boolean dispatchTouchEvent(MotionEvent ev):用來進(jìn)行事件的分發(fā)依许,如果事件能夠傳遞給當(dāng)前 View,那么該方法一定會(huì)調(diào)用缀蹄,返回值受當(dāng)前 View 的 onTouchEvent 和下級(jí) View 的 dispatchTouchEvent 的影響峭跳,表示是否消耗當(dāng)前事件;
  2. public boolean onInterceptTouchEvent(MotionEvent ev):表示在上述方法內(nèi)部調(diào)用缺前,判斷當(dāng)前 View 是否攔截某個(gè)事件蛀醉。如果當(dāng)前 View 攔截了某個(gè)事件,那么同一個(gè)事件序列當(dāng)中衅码,此方法不會(huì)被再次調(diào)用拯刁;反之,如果是下級(jí) View 攔截了事件逝段,并且下級(jí) View 沒有調(diào)用public void requestDisallowInterceptTouchEvent(boolean disallowIntercept)垛玻,那么當(dāng)前 View 的onInterceptTouchEvent(MotionEvent ev)會(huì)被繼續(xù)調(diào)用,即當(dāng)前 View 依舊擁有攔截后續(xù)事件的能力奶躯;
  3. public boolean onTouchEvent(MotionEvent event):在 dispatchTouchEvent中調(diào)用帚桩,用來處理點(diǎn)擊事件,返回結(jié)果表示是否消耗當(dāng)前事件嘹黔,如果不消耗账嚎,則在同一個(gè)事件序列中,當(dāng)前 View 無法再次接收到事件儡蔓。

上述三個(gè)方法的關(guān)系大致可以用下面的偽代碼來表示:

public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume=false;
    if(onInterceptTouchEvent(ev)){
        consume=onTouchEvent(ev);
    }else{
        cousume=child.dispatchTouchEvent(ev);
    }
    returen consume;
}

dispatchTouchEvent搭建了事件分發(fā)的框架郭蕉,一般不需要重寫,自定義 View 時(shí)通常重寫的是onInterceptTouchEventonTouchEvent喂江。事件的分發(fā)有點(diǎn)類似有序樹的查找算法召锈,ViewGroup 就是結(jié)點(diǎn),View 就是葉子开呐。遍歷到 View 的時(shí)候烟勋,就要返回了规求。

源碼分析

ViewGroup

dispatchTouchEvent

            // 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();
            }

當(dāng)接收到 ACTION_DOWN 事件的時(shí)候,會(huì)重置一下狀態(tài)(比如說重置FLAG_DISALLOW_INTERCEPT狀態(tài)位卵惦,允許攔截事件)阻肿,清除之前的 TouchTarget。

TouchTarget :Describes a touched view and the ids of the pointers that it has captured.

            // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    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;
            }

此段代碼是判斷當(dāng)前 ViewGroup 是否要攔截事件沮尿,首先介紹幾個(gè)變量的含義:

  • mFirstTouchTarget:touch target list 的第一項(xiàng)丛塌,表示上一個(gè)處理事件的 child;
  • disallowIntercept:是否允許當(dāng)前 ViewGroup 攔截事件畜疾,默認(rèn)是允許赴邻,其 child 可以設(shè)置為不允許;

由上述代碼可知啡捶,如果 MotionEvent 不是 ACTION_DOWN 且 child 沒有處理上一個(gè)事件姥敛,則 ViewGroup 會(huì)攔截下事件;否則會(huì)調(diào)用 onInterceptTouchEvent 來判斷是否需要攔截事件(除非 child 不允許 view parent 攔截事件)瞎暑。

            if (!canceled && !intercepted) {
                ......
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    .....
                    //當(dāng) ViewGroup 不攔截事件彤敛,且事件為 DOWN 類型,那么就要遍歷其 child了赌,尋找處理事件的 child墨榄。
                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        ......
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);
                            //能夠接受事件的child:事件坐標(biāo)在 View 內(nèi);View 可見或者沒有在執(zhí)行動(dòng)畫
                            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;
                            }
                            //
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                ......
                                //用 child 獲得新的 TouchTarget勿她,并將其添加到 Touch Target list 的第一個(gè)袄秩;
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                        }
                    }
                }
            }

當(dāng) ViewGroup 不攔截事件時(shí),且事件類型為 DOWN 時(shí)逢并,ViewGroup 會(huì)遍歷其 child之剧,尋找能接收事件的 child,然后調(diào)用dispatchTransformedTouchEvent將事件傳遞給 child 進(jìn)行處理砍聊。具體邏輯處理可以看代碼中的注釋猪狈。

            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                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 (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        ......                    
                        }
                }
            }

如果沒有 child 要處理事件,那么就ViewGroup 自身嘗試處理事件辩恼;如果有,那么遍歷 Touch Target list 谓形,每一個(gè)child 都嘗試處理事件灶伊。

下面讓我們看一下dispatchTransformedTouchEvent的代碼:

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        ......
        // 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.
        //根據(jù) pointerId、child 的偏移量對(duì) MotionEvent 進(jìn)行轉(zhuǎn)換
        final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    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);
        }
          //如果 child 為null寒跳,那么調(diào)用 super.dispatchTouchEvent聘萨,即 View 的 dispatchTouchEvent 方法,
          //看能否消耗該事件童太,否則米辐,事件傳遞給 child 進(jìn)行處理
        // Perform any necessary transformations and dispatch.
        if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            ......
            handled = child.dispatchTouchEvent(transformedEvent);
        }

        // Done.
        transformedEvent.recycle();
        return handled;
    }

邏輯不復(fù)雜胸完,主要就是對(duì) MotionEvent 進(jìn)行根據(jù) pointerId 、child 的偏移量進(jìn)行轉(zhuǎn)換翘贮,然后如果 child 不為null赊窥,那么調(diào)用 child 的 dispatchTouchEvent,將事件傳遞給 child 進(jìn)行處理狸页,如果 child 為null锨能,那么調(diào)用 super.dispatchTouchEvent,即viewgroup嘗試處理該事件芍耘。

上面就是 ViewGroup 分發(fā)事件基本框架代碼址遇,大致可以總結(jié)如下:

  1. 如果事件是 DOWN 類型,先判斷當(dāng)前 ViewGroup 是否攔截該事件(通常 ViewGroup 是不會(huì)攔截 DOWN 事件斋竞,否則child 完全接受不到事件了)倔约;如果不攔截事件,那么遍歷其 child坝初,尋找處理該事件的 child浸剩。如果沒有 child 處理事件,那么 ViewGroup 自行嘗試處理該事件脖卖,否則 child 處理該事件乒省,并將 mFirstTouchTarget 設(shè)置為該 child;
  2. 事件不是 DOWN畦木,且之前的事件沒有 child 進(jìn)行處理(mFirstTouchTarget 為 null)袖扛,那么ViewGroup 嘗試處理該事件;如果之前的事件有 child 進(jìn)行處理了十籍,那么先判斷 ViewGroup是否需要攔截該事件蛆封,不需要,則直接交由之前處理事件的 child 直接處理勾栗。

View

dispatchTouchEvent

不同于 ViewGroup惨篱,View 不能再向下分發(fā)事件烧董,要么自身處理事件缓溅,要么不處理返回給 parent處理,相當(dāng)于樹中的葉子诵叁,所以 View 沒有 onInterceptTouchEvent 方法界牡,它的 dispatchTouchEvent 也是用來判斷并處理事件的簿寂。下面看看其源碼:

public boolean dispatchTouchEvent(MotionEvent event) {
        ......
        boolean result = false;

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

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
        ......
        return result;
    }

由上可知,首先會(huì)判斷 View 是否設(shè)置了 OnTouchListener宿亡,如果是常遂,則將事件交給它處理,否則才會(huì)調(diào)用 VIew 的 onTouchEvent 方法挽荠】烁欤可見 OnTouchListener 的優(yōu)先級(jí)高于 View 的 onTouchEvent平绩,這是方便我們?cè)谕獠吭O(shè)置處理事件的方法。
接下來看看 View 的 onTouchEvent 方法:

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
        }
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            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
                            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:
                    mHasPerformedLongPress = false;

                    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:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_MOVE:
                    drawableHotspotChanged(x, y);

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        removeTapCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            // Remove any future long press/tap checks
                            removeLongPressCallback();

                            setPressed(false);
                        }
                    }
                    break;
            }

            return true;
        }

        return false;

由上可知漠另,View 的 onTouchEvent 的處理邏輯大致如下:

  1. 只要 View 時(shí) clickable 的捏雌,總是返回 true,即總是消耗事件酗钞;
  2. 如果 View 時(shí) disabled 的腹忽,那么直接返回 TRUE,照樣消耗事件砚作,但是不執(zhí)行相應(yīng)的動(dòng)作窘奏;
  3. 如果 View 設(shè)置的 Delegate,那么直接把事件交給 Delegate處理(利用這點(diǎn)可以將事件交給其他 View 來處理)葫录;
  4. 如果 View 自己處理事件着裹,那么根據(jù)事件的類型處理方法如下:
  • DOWN:設(shè)置狀態(tài)為 pressed,同時(shí)設(shè)置一個(gè)延時(shí)任務(wù)米同,用以判斷 longClick骇扇;
  • MOVE:判斷事件是否超過了 View 的邊界,如果是面粮,重置狀態(tài)少孝,取消 longClick 的延時(shí)任務(wù)等;
  • UP:如果狀態(tài)為 pressed熬苍,那么執(zhí)行 performClick稍走,并且重置狀態(tài);
  • CANCEL:重置狀態(tài)

實(shí)戰(zhàn)分析

對(duì)于自定義 View柴底,如何正確地分發(fā)婿脸、處理事件非常重要,下面就大致說說 ViewGroup 和 View 分別是如何重寫相關(guān)方法柄驻,以實(shí)現(xiàn)需求狐树。

View

對(duì)于 View,因?yàn)椴恍枰职l(fā)事件鸿脓,所以 View 一般只需要重寫 onTouchEvent抑钟,然后根據(jù)事件類型分情況處理:

  • Donw:一定要返回 TRUE,否則同一序列的后續(xù)事件都不會(huì)交給這個(gè) View 處理了野哭;
  • Move:通常是我們處理的關(guān)鍵味赃,根據(jù)它來進(jìn)行相應(yīng)的邏輯處理,比如說移動(dòng) View虐拓,繪畫之類的;
  • Up:重置狀態(tài)傲武,資源回收等處理蓉驹;

ViewGroup

對(duì)于 ViewGroup城榛,首先是要重寫 onInterceptTouchEvent:

  • Down:返回 FALSE,這樣 child 才有可能接收到事件并進(jìn)行處理态兴;
  • Move:根據(jù)需要進(jìn)行判斷返回 TRUE or FALSE狠持,返回 TRUE,說明 ViewGroup 要攔截事件瞻润,交由其 onTouchEvent 進(jìn)行處理喘垂,F(xiàn)ALSE 則繼續(xù)給 child 進(jìn)行處理;
  • Up:返回 FALSE绍撞,這樣child 才有可能接收到 Up 事件正勒,進(jìn)行相應(yīng)的處理;

onTouchEvent:

  • 返回TRUE傻铣,表示 ViewGroup 消耗了事件章贞,后續(xù)事件都會(huì)交由它處理;
  • 返回 FALSE非洲,表示 ViewGroup 不再消耗事件鸭限,但是,如果ViewGroup dispatchTouchEvent(DownEvent)的時(shí)候返回了 TRUE两踏,即消耗了 DOWN 事件败京,那么就算他不消耗后續(xù)事件,后續(xù)事件依然會(huì)傳遞給它梦染,而不會(huì)交給 parent 來處理赡麦,最終這些未處理的事件會(huì)在 Activity 中處理;如果沒有消耗過 DOWN 事件弓坞,那么事件會(huì)直接交給 parent 來處理(因?yàn)?mFirstTouchTarget 為 null)隧甚;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市渡冻,隨后出現(xiàn)的幾起案子戚扳,更是在濱河造成了極大的恐慌,老刑警劉巖族吻,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帽借,死亡現(xiàn)場離奇詭異,居然都是意外死亡超歌,警方通過查閱死者的電腦和手機(jī)砍艾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來巍举,“玉大人脆荷,你說我怎么就攤上這事。” “怎么了蜓谋?”我有些...
    開封第一講書人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵梦皮,是天一觀的道長。 經(jīng)常有香客問我桃焕,道長剑肯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任观堂,我火速辦了婚禮让网,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘师痕。我一直安慰自己溃睹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開白布七兜。 她就那樣靜靜地躺著丸凭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪腕铸。 梳的紋絲不亂的頭發(fā)上惜犀,一...
    開封第一講書人閱讀 51,488評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音狠裹,去河邊找鬼虽界。 笑死,一個(gè)胖子當(dāng)著我的面吹牛涛菠,可吹牛的內(nèi)容都是我干的莉御。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼俗冻,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼礁叔!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起迄薄,我...
    開封第一講書人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤琅关,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后讥蔽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涣易,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年冶伞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了新症。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡响禽,死狀恐怖徒爹,靈堂內(nèi)的尸體忽然破棺而出荚醒,到底是詐尸還是另有隱情,我是刑警寧澤瀑焦,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布腌且,位于F島的核電站,受9級(jí)特大地震影響榛瓮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜巫击,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一禀晓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧坝锰,春花似錦粹懒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至弓颈,卻和暖如春帽芽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背翔冀。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來泰國打工导街, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人纤子。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓搬瑰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親控硼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子泽论,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

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