View事件傳遞機(jī)制源碼解析

點(diǎn)擊事件的傳遞規(guī)則

點(diǎn)擊事件歧焦,說直接一點(diǎn)就是MotionEvent對(duì)象嗤军,事件的傳遞莉御,就是將MotionEvent對(duì)象分發(fā)給不同的View來處理的過程且轨,分發(fā)過程主要由三個(gè)重要方法組成:

  • public boolean dispatchTouchEvent(MotionEvent event);
  • public boolean onInterceptTouchEvent(MotionEvent ev);(只存在于ViewGroup中,默認(rèn)值為false)
  • public boolean onTouchEvent(MotionEvent event);

先簡(jiǎn)單描述一下它們的關(guān)系:
首先魄健,事件會(huì)傳遞給根ViewGroup赋铝,它的dispatch方法被調(diào)用,在dispatch中會(huì)詢問onIntercept來決定該ViewGroup是否消費(fèi)該事件沽瘦,如果返回true革骨,那么該ViewGroup就會(huì)消費(fèi)掉該事件,將event交給當(dāng)前ViewGrouponTouchEvent來處理析恋,事件結(jié)束良哲。如果onIntercept返回了false,表示當(dāng)前ViewGroup不消費(fèi)事件助隧,會(huì)將它傳遞給他的子元素筑凫。然后子元素的dispatch方法執(zhí)行,一直到事件被處理(onTouchEvent返回值為true)喇颁。單一View沒有onIntercept漏健,dispatchTouchEvent會(huì)直接接收onTouchEvent的返回值,返回true說明事件被當(dāng)前View接收并處理了橘霎。

Activity#DispatchTouchEvent源碼分析

所有的點(diǎn)擊事件都是從Activity開始傳遞的蔫浆,先看一下Activity中,dispatch方法的源碼

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

首先姐叁,事件會(huì)傳遞到getWindow().superDispatchTouchEvent(ev)瓦盛,它的返回值決定了是否會(huì)調(diào)用Activity的onTouchEvent(ev)
我們繼續(xù)看getWindow().superDispatchTouchEvent(ev)的源碼外潜。Window的唯一實(shí)現(xiàn)類就是PhoneWindow(com.android.internal.policy.impl.PhoneWindow)原环,直接查看superDispatchTouchEvent方法:

@Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

代碼很簡(jiǎn)單,直接調(diào)用了DecorViewsuperDispatchTouchEvent方法处窥。DecorViewPhoneWindow的內(nèi)部類嘱吗,看一下代碼:

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
......
        public boolean superDispatchTouchEvent(MotionEvent event) {
            return super.dispatchTouchEvent(event);
        }
.......

看到這里,事件的傳遞流程其實(shí)就已經(jīng)很清楚了:

Activity首先接收到事件,通過一系列的調(diào)用谒麦,最終傳遞到了 DecorViewsuperDispatchTouchEvent俄讹,DecorView繼承自FrameLayout又繼承自ViewGroup,最終調(diào)用的方法實(shí)際上是ViewGroupdispatchTouchEvent(ev)方法绕德。由ViewGroup來進(jìn)行事件分發(fā)患膛,如果分發(fā)成功,事件被消費(fèi)耻蛇,返回值為true踪蹬。如果分發(fā)失敗,事件仍然沒有被消耗臣咖,返回false跃捣,事件就會(huì)交給Activity#onTouchEvent處理。

我們只需要記住一點(diǎn):事件傳遞從Activity開始夺蛇,最終會(huì)傳遞到根ViewGroup的dispatchTouchEvent枝缔。如果最終返回值為false,說明事件沒有被攔截蚊惯,最終會(huì)交給Activity的onTouchEvent來處理。

ViewGroup的DispatchTouchEvent源碼分析

源碼很長(zhǎng)灵临,只截取了部分關(guān)鍵代碼如下:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        //標(biāo)記事件是否已經(jīng)被攔截
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            //@ 獲取Action截型,Masked表示不考慮多點(diǎn)觸控
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            //@ 如果是ACTION_DOWN,清除所有初始狀態(tài)儒溉,主要包括以下動(dòng)作:
            //  設(shè)置mFirstTouchTarget值為null
            //  清除FLAG_DISALLOW_INTERCEPT標(biāo)志宦焦,如果設(shè)置該標(biāo)志為true,ViewGroup不攔截事件
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // 用于標(biāo)記事件是否被當(dāng)前ViewGroup消費(fèi)
            final boolean intercepted;
            //以下代碼用來判斷事件是否被當(dāng)前ViewGroup消費(fèi)顿涣,主要流程如下:
            //-----------------------------------------------------------
            //1.Down事件到來波闹,必定會(huì)進(jìn)入intercepted = onInterceptTouchEvent(ev);
            //2.如果ViewGroup決定消費(fèi)事件,那么intercepted被賦值為true
            //3.intercepted被賦值為true之后涛碑,下面標(biāo)記###--->1的if代碼塊不再執(zhí)行精堕,直接跳到 ###--->2
            //4.mFirstTouchTarget賦值的語句在###--->3標(biāo)記,包含在###--->1的if代碼塊內(nèi)蒲障,所以歹篓,如果當(dāng)前ViewGroup消費(fèi)了事件,mFirstTouchTarget就一定是null
            //5.繼續(xù)看###--->2的代碼揉阎,mFirstTouchTarget等于null庄撮,滿足條件,調(diào)用dispatchTransformedTouchEvent方法毙籽,注意第三個(gè)參數(shù)為null
            //6.查看dispatchTransformedTouchEvent洞斯,發(fā)現(xiàn)它調(diào)用了super.dispatchTouchEvent,也就是View的dispatchTouchEvent
            //7.View的dispatchTouchEvent下文還會(huì)仔細(xì)講解坑赡,它最終將事件交給了onTouch烙如,onTouchEvent么抗,onClick方法
            //8.所以我們得出以下結(jié)論:如果ViewGroup的onInterceptTouchEvent,事件不會(huì)傳遞給子View厅翔,而是交給了自己的onTouch乖坠,onTouchEvent,onClick方法處理
            //9.Down事件結(jié)束之后刀闷,MOve和Up到來時(shí),actionMasked == MotionEvent.ACTION_DOWN為false熊泵,mFirstTouchTarget != null也是false,事件不會(huì)再進(jìn)入onInterceptTouchEvent甸昏,而是直接交給當(dāng)前ViewGroup處理了
            //-----------------------------------------------------------
            //如果onInterceptTouchEvent返回false顽分,流程如下:
            //1.Down事件到來,必定會(huì)進(jìn)入intercepted = onInterceptTouchEvent(ev);
            //2.ViewGroup不消費(fèi)事件施蜜,intercepted被賦值為false卒蘸,必定會(huì)進(jìn)入###--->1出的if代碼塊
            //3.運(yùn)行到###--->4處,會(huì)對(duì)遍歷子View翻默,如果找到了攔截該事件的對(duì)象缸沃,在###--->3處的addTouchTarget方法內(nèi)對(duì)mFirstTouchTarget進(jìn)行賦值,并指向了當(dāng)前ViewGroup的子元素修械。
            //4.找到事件的攔截者并賦值給mFirstTouchTarget之后趾牧,繼續(xù)向下運(yùn)行到 ###--->5,此時(shí)mFirstTouchTarge肯污!=null翘单,執(zhí)行else分支,在###--->6 處將事件交給了當(dāng)前View的child來處理
            //5.Down事件結(jié)束之后蹦渣,MOve和Up到來時(shí)哄芜,mFirstTouchTarget仍然持有Target,所以下面的if滿足mFirstTouchTarget != null柬唯,繼續(xù)詢問ViewGroup是否攔截事件
            //6.MOve和Up的傳遞流程繼續(xù)重復(fù)了Down事件的流程
/******************************************************************************/
/*/            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;
/*/            }
/******************************************************************************/
            ......

            // 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;
###--->1      if (!canceled && !intercepted) {
                    

                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    ......

                    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;
 ###--->4                       for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                           ......

                            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);
                            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();
###--->3                 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();
                    }
                       ......
                }
            }
            //以下部分用于傳遞事件认臊,到這里時(shí)已經(jīng)找到了處理事件的對(duì)象,只是將事件交付給相應(yīng)的對(duì)象來處理
            // Dispatch to touch targets.
 ###--->2           if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
###--->5          } 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;
 ###--->6                  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;
}

通過對(duì)上邊代碼的分析锄奢,我們可以得到以下結(jié)論:

1.所有的ACTION_DOWN都會(huì)進(jìn)入onInterceptTouchEvent方法美尸,由ViewGroup判斷是否攔截,如果ViewGroup攔截了ACTION_DOWN斟薇,后續(xù)動(dòng)作(ACTION_MOVE,UP)都會(huì)交給ViewGroup處理师坎,不會(huì)再進(jìn)入onInterceptTouchEvent,這時(shí)候ViewGrouponTouchEvent方法就會(huì)被調(diào)用堪滨,

2.如果ACTION_DOWN事件交給了ViewGroup的子View進(jìn)行處理胯陋,后續(xù)動(dòng)作(ACTION_MOVE,UP)仍然會(huì)進(jìn)入onInterceptTouchEvent,有可能會(huì)被ViewGroup攔截。子元素可以通過mParent.requestDisallowInterceptTouchEvent方法限制ViewGroup消耗事件(不進(jìn)入Intercept)遏乔,但是對(duì)ACTION_DOWN不起作用义矛。

3.ViewGroup攔截的事件會(huì)傳遞到View.dispatchTouchEvent,然后再由View傳遞給onTouch或onTouchEvent盟萨。ViewGroup并沒有覆寫onTouchEvent凉翻,onTouch等方法。

View#DispatchTouchEvent源碼分析

這里的View通常指單一View捻激,當(dāng)事件傳遞到這里的時(shí)候制轰,說明事件已經(jīng)通過ViewGroup找到了對(duì)應(yīng)的攔截方,View的DispatchTouchEvent只負(fù)責(zé)交付事件胞谭,所以代碼也相對(duì)簡(jiǎn)單:

    public boolean dispatchTouchEvent(MotionEvent event) {
        boolean result = false;
        ......
        if (onFilterTouchEventForSecurity(event)) {
            //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ì)對(duì)onTouchListener進(jìn)行判斷,如果onTouchListener中的onTouch方法返回了true丈屹,onTouchEvent就不會(huì)再執(zhí)行了调俘。
再來看onTouchEvent方法:

if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (event.getAction() == 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));
        }

        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

如果View是Disabled狀態(tài),它仍然會(huì)消耗事件(CLICKABLE和LONG_CLICKABLE任一個(gè)為true)旺垒。
然后是代理的判斷彩库。繼續(xù)往下看源碼:

if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
                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 (!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();
                    }
                    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);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    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;

從上邊的代碼可以發(fā)現(xiàn),只要View的LONG_CLICKABLE或者CLICKABLE任一個(gè)為true(View的LONG_CLICKABLE默認(rèn)為false先蒋,CLICKABLE針對(duì)不同view默認(rèn)值不同)侧巨,view就會(huì)消耗事件。只有在ACTION_UP的時(shí)候鞭达,才會(huì)執(zhí)行onClick。
另外皇忿,setOnClickListener和setOnLongClickListener會(huì)自動(dòng)將View的CLICKABLE畴蹭,CLICKABLE設(shè)置為true。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鳍烁,一起剝皮案震驚了整個(gè)濱河市叨襟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌幔荒,老刑警劉巖糊闽,帶你破解...
    沈念sama閱讀 221,406評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異爹梁,居然都是意外死亡右犹,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門姚垃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來念链,“玉大人,你說我怎么就攤上這事〉嗄梗” “怎么了谦纱?”我有些...
    開封第一講書人閱讀 167,815評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)君编。 經(jīng)常有香客問我跨嘉,道長(zhǎng),這世上最難降的妖魔是什么吃嘿? 我笑而不...
    開封第一講書人閱讀 59,537評(píng)論 1 296
  • 正文 為了忘掉前任祠乃,我火速辦了婚禮,結(jié)果婚禮上唠椭,老公的妹妹穿的比我還像新娘跳纳。我一直安慰自己,他們只是感情好贪嫂,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,536評(píng)論 6 397
  • 文/花漫 我一把揭開白布寺庄。 她就那樣靜靜地躺著,像睡著了一般力崇。 火紅的嫁衣襯著肌膚如雪斗塘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,184評(píng)論 1 308
  • 那天亮靴,我揣著相機(jī)與錄音馍盟,去河邊找鬼。 笑死茧吊,一個(gè)胖子當(dāng)著我的面吹牛贞岭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播搓侄,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼瞄桨,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了讶踪?” 一聲冷哼從身側(cè)響起芯侥,我...
    開封第一講書人閱讀 39,668評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎乳讥,沒想到半個(gè)月后柱查,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,212評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡云石,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,299評(píng)論 3 340
  • 正文 我和宋清朗相戀三年唉工,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片汹忠。...
    茶點(diǎn)故事閱讀 40,438評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡酵紫,死狀恐怖告嘲,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情奖地,我是刑警寧澤橄唬,帶...
    沈念sama閱讀 36,128評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站参歹,受9級(jí)特大地震影響仰楚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜犬庇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,807評(píng)論 3 333
  • 文/蒙蒙 一僧界、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧臭挽,春花似錦捂襟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至纽帖,卻和暖如春宠漩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背懊直。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評(píng)論 1 272
  • 我被黑心中介騙來泰國打工扒吁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人室囊。 一個(gè)月前我還...
    沈念sama閱讀 48,827評(píng)論 3 376
  • 正文 我出身青樓雕崩,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親融撞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子盼铁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,446評(píng)論 2 359

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