Android 按鍵的焦點(diǎn)分發(fā)處理機(jī)制

本文重點(diǎn)針對android TV開發(fā)的同學(xué)孽椰,分析遙控或鍵盤按鍵事件后焦點(diǎn)的分發(fā)機(jī)制难咕。尤其是剛從手機(jī)開發(fā)轉(zhuǎn)向TV開發(fā)的同學(xué)坤检,因?yàn)樵趯?shí)際開發(fā)中總會出現(xiàn)丟焦點(diǎn)或者焦點(diǎn)給到非預(yù)期的View或者ViewGroup匀奏。但此問題實(shí)際開發(fā)情況比較復(fù)雜蛔翅,本文僅限從android基本的分發(fā)機(jī)制出發(fā)瓶殃,對整體流程進(jìn)行梳理充包,并提供一些方法改變默認(rèn)行為以達(dá)到特定需求。如果有機(jī)會后續(xù)還會有更偏向?qū)崙?zhàn)的內(nèi)容更新遥椿。

一.前言

本文源碼基于android 7.0

二.核心流程

首先我們要知道按鍵事件和觸屏事件一樣都是從硬件通過系統(tǒng)驅(qū)動傳遞給android framework層的基矮,當(dāng)然這也不是我們要關(guān)注的重點(diǎn)。事件的入口就是ViewRootImpl的processKeyEvent方法冠场。

private int processKeyEvent(QueuedInputEvent q) {
    final KeyEvent event = (KeyEvent) q.mEvent;
    // ①view樹處理事件消費(fèi)邏輯
    if (mView.dispatchKeyEvent(event)) {
        return FINISH_HANDLED;
    }
    if (shouldDropInputEvent(q)) {
        return FINISH_NOT_HANDLED;
    }
    ....//處理ctrl鍵 也就是快捷按鍵相關(guān)邏輯

    // 自動尋焦邏輯
    if (event.getAction() == KeyEvent.ACTION_DOWN) {
        int direction = 0;
        ....//根據(jù)keycode賦值direction
        if (direction != 0) {
            // ②尋找當(dāng)前界面的焦點(diǎn)view/viewGroup
            View focused = mView.findFocus();
            if (focused != null) {
                // ③根據(jù)方向按鍵尋找合適的focus view
                View v = focused.focusSearch(direction);
                if (v != null && v != focused) {
                    ...
                    // ④請求焦點(diǎn)
                    if (v.requestFocus(direction, mTempRect)) {
                        playSoundEffect(SoundEffectConstants
                                .getContantForFocusDirection(direction));
                        return FINISH_HANDLED;
                    }
                }

                // 沒有找到焦點(diǎn) 給view最后一次機(jī)會處理按鍵事件
                if (mView.dispatchUnhandledMove(focused, direction)) {
                    return FINISH_HANDLED;
                }
            } else {
                // 如果當(dāng)前界面沒有焦點(diǎn)走這里
                View v = focusSearch(null, direction);
                if (v != null && v.requestFocus(direction)) {
                    return FINISH_HANDLED;
                }
            }
        }
    }
    return FORWARD;
}

如上面的標(biāo)號家浇,就是尋焦的主要流程。其他的一些判斷代碼由于篇幅限制就不貼出了碴裙。下面分別對上述四個節(jié)點(diǎn)一一分析钢悲。

如果你去看了ViewRootImpl的源碼會發(fā)現(xiàn) 其中有三個內(nèi)部類都有這個方法,分別為:ViewPreImeInputStage舔株,EarlyPostImeInputStage,ViewPostImeInputStage他們都繼承InputStage類莺琳,上面的代碼是ViewPostImeInputStage類中的,從類名可以判斷按鍵的處理跟輸入法相關(guān)载慈,如果輸入法在前臺則會將事件先分發(fā)給輸入法惭等。

2.1 dispatchKeyEvent

想必你已經(jīng)很熟悉android事件分發(fā)機(jī)制了(當(dāng)然這也不是重點(diǎn)),key事件和touch事件原理都是一樣娃肿」径校總得來說就是由根view,這里就是DecorView它是一個FrameLayout它先分發(fā)給activity料扰,之后順序?yàn)閍ctivity-->PhoneWindow-->DecorView-->View樹凭豪,view樹中根據(jù)focusd path分發(fā),也就是從根節(jié)點(diǎn)開始直至focused view 為止的樹晒杈,具體流程可參看Android按鍵事件處理流程 -- KeyEvent嫂伞。 遍歷過程中一旦有節(jié)點(diǎn)返回true即表示消費(fèi)此事件,否則會一直傳遞下去拯钻。之所以說明這些帖努,是想提供一種攔截焦點(diǎn)的思路,如果按鍵事件傳遞過程中被消費(fèi)便不會走尋焦邏輯粪般。具體的流程后續(xù)會分享個大家拼余。

2.2 findFocus

此方法的核心就是找到當(dāng)前持有focus的view。
調(diào)用者mView即DecorView是FrameLayout 布局亩歹,沒復(fù)寫findFocus方法匙监,所以找到ViewGroup中的findFocus方法凡橱。

    public View findFocus() {
        if (isFocused()) {
            return this;
        }
        if (mFocused != null) {
            return mFocused.findFocus();
        }
        return null;
    }

邏輯很簡單如果當(dāng)前view是focused的狀態(tài)直接返回自己,否則調(diào)用內(nèi)部間接持有focus的子view即mFocused亭姥,遍歷查找focused view稼钩。可見此番查找的路徑就是focused tree达罗。

2.3 focusSearch

根據(jù)開篇的核心流程坝撑,如果在上一步中找到了focused view,則會執(zhí)行view的focusSearch(int direction)方法粮揉,否則執(zhí)行focusSearch(View focused, int direction)巡李。這兩個方法分別來自于View和ViewGroup,但核心功能是一致的滔蝉,看代碼击儡。

   /**
     * Find the nearest view in the specified direction that can take focus.
     * This does not actually give focus to that view.
     *
     * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
     *
     * @return The nearest focusable in the specified direction, or null if none
     *         can be found.
     */
    public View focusSearch(@FocusRealDirection int direction) {
        if (mParent != null) {
            return mParent.focusSearch(this, direction);
        } else {
            return null;
        }
    }

    /**
     * Find the nearest view in the specified direction that wants to take
     * focus.
     *
     * @param focused The view that currently has focus
     * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and
     *        FOCUS_RIGHT, or 0 for not applicable.
     */
    public View focusSearch(View focused, int direction) {
        if (isRootNamespace()) {
            // root namespace means we should consider ourselves the top of the
            // tree for focus searching; otherwise we could be focus searching
            // into other tabs.  see LocalActivityManager and TabHost for more info
            return FocusFinder.getInstance().findNextFocus(this, focused, direction);
        } else if (mParent != null) {
            return mParent.focusSearch(focused, direction);
        }
        return null;
    }

連注釋都是驚人的相似有木有,大致意思是將focusSearch事件一直向父View傳遞蝠引,如果這個過程上層一直沒有干涉則會遍歷到頂層DecorView阳谍。回到上面的分水嶺螃概,如果findFocus沒有找到focused view矫夯,即把null 賦值給focused傳遞,整個流程不受影響吊洼。
重點(diǎn)方法是FocusFinder.getInstance().findNextFocus(this, focused, direction);來看源碼训貌。

    /**
     * Find the next view to take focus in root's descendants, starting from the view
     * that currently is focused.
     * @param root Contains focused. Cannot be null.
     * @param focused Has focus now.
     * @param direction Direction to look.
     * @return The next focusable view, or null if none exists.
     */
    public final View findNextFocus(ViewGroup root, View focused, int direction) {
        return findNextFocus(root, focused, null, direction);
    }

    private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
        View next = null;
        if (focused != null) {
            next = findNextUserSpecifiedFocus(root, focused, direction);
        }
        if (next != null) {
            return next;
        }
        ArrayList<View> focusables = mTempList;
        try {
            focusables.clear();
            root.addFocusables(focusables, direction);
            if (!focusables.isEmpty()) {
                next = findNextFocus(root, focused, focusedRect, direction, focusables);
            }
        } finally {
            focusables.clear();
        }
        return next;
    }

如果當(dāng)前焦點(diǎn)不為空,則先去讀取上層設(shè)置的specifiedFocusId冒窍。

    private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) {
        // check for user specified next focus
        View userSetNextFocus = focused.findUserSetNextFocus(root, direction);
        if (userSetNextFocus != null && userSetNextFocus.isFocusable()
                && (!userSetNextFocus.isInTouchMode()
                        || userSetNextFocus.isFocusableInTouchMode())) {
            return userSetNextFocus;
        }
        return null;
    }

    /**
     * If a user manually specified the next view id for a particular direction,
     * use the root to look up the view.
     * @param root The root view of the hierarchy containing this view.
     * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, FOCUS_FORWARD,
     * or FOCUS_BACKWARD.
     * @return The user specified next view, or null if there is none.
     */
    View findUserSetNextFocus(View root, @FocusDirection int direction) {
        switch (direction) {
            case FOCUS_LEFT:
                if (mNextFocusLeftId == View.NO_ID) return null;
                return findViewInsideOutShouldExist(root, mNextFocusLeftId);
            case FOCUS_RIGHT:
                if (mNextFocusRightId == View.NO_ID) return null;
                return findViewInsideOutShouldExist(root, mNextFocusRightId);
            case FOCUS_UP:
                if (mNextFocusUpId == View.NO_ID) return null;
                return findViewInsideOutShouldExist(root, mNextFocusUpId);
            case FOCUS_DOWN:
                if (mNextFocusDownId == View.NO_ID) return null;
                return findViewInsideOutShouldExist(root, mNextFocusDownId);
            case FOCUS_FORWARD:
                if (mNextFocusForwardId == View.NO_ID) return null;
                return findViewInsideOutShouldExist(root, mNextFocusForwardId);
            case FOCUS_BACKWARD: {
                if (mID == View.NO_ID) return null;
                final int id = mID;
                return root.findViewByPredicateInsideOut(this, new Predicate<View>() {
                    @Override
                    public boolean apply(View t) {
                        return t.mNextFocusForwardId == id;
                    }
                });
            }
        }
        return null;
    }

有木有很熟悉findUserSetNextFocus的實(shí)現(xiàn)递沪,如果上層給View/ViewGroup設(shè)置了setNextDownId/setNextLeftId/...,則android系統(tǒng)會從root view樹中查找此id對應(yīng)的view并返回综液,此分支尋焦邏輯結(jié)束款慨。可見為View/ViewGroup設(shè)置了nextDownId,nextLeftId等屬性可定向分配焦點(diǎn)。
若沒有設(shè)置上面的屬性谬莹,走下面的流程

        ArrayList<View> focusables = mTempList;
        try {
            focusables.clear();
            root.addFocusables(focusables, direction);
            if (!focusables.isEmpty()) {
                next = findNextFocus(root, focused, focusedRect, direction, focusables);
            }
        } finally {
            focusables.clear();
        }
        return next;
    @Override
    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
        final int focusableCount = views.size();

        final int descendantFocusability = getDescendantFocusability();

        if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
            if (shouldBlockFocusForTouchscreen()) {
                focusableMode |= FOCUSABLES_TOUCH_MODE;
            }

            final int count = mChildrenCount;
            final View[] children = mChildren;

            for (int i = 0; i < count; i++) {
                final View child = children[i];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                    child.addFocusables(views, direction, focusableMode);
                }
            }
        }
        ...
    }

邏輯也出來了檩奠,用一個集合存儲那些focusable并且可見的view,注意到addFocusables方法調(diào)用者是root附帽,也就是整個view樹都會進(jìn)行遍歷埠戳。FOCUS_BLOCK_DESCENDANTS這個屬性也很熟悉,如果為ViewGroup設(shè)置該屬性則其子view都不會統(tǒng)計到focusable范圍中蕉扮。
最終findNextFocus方法:

private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,
        int direction, ArrayList<View> focusables) {
    if (focused != null) {
        if (focusedRect == null) {
            focusedRect = mFocusedRect;
        }
        // fill in interesting rect from focused
        focused.getFocusedRect(focusedRect);
        root.offsetDescendantRectToMyCoords(focused, focusedRect);
    } else {
      ...
    }

    switch (direction) {
        case View.FOCUS_FORWARD:
        case View.FOCUS_BACKWARD:
            return findNextFocusInRelativeDirection(focusables, root, focused, focusedRect,
                    direction);
        case View.FOCUS_UP:
        case View.FOCUS_DOWN:
        case View.FOCUS_LEFT:
        case View.FOCUS_RIGHT:
            return findNextFocusInAbsoluteDirection(focusables, root, focused,
                    focusedRect, direction);
        default:
            throw new IllegalArgumentException("Unknown direction: " + direction);
    }
}

正常流程到這里focused不為空整胃,focusedRect為空,鍵值一般為上下左右方向按鍵喳钟,因此走絕對方向爪模。

View findNextFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused,
        Rect focusedRect, int direction) {
    // initialize the best candidate to something impossible
    // (so the first plausible view will become the best choice)
    mBestCandidateRect.set(focusedRect);
    switch(direction) {
        case View.FOCUS_LEFT:
            mBestCandidateRect.offset(focusedRect.width() + 1, 0);
            break;
        case View.FOCUS_RIGHT:
            mBestCandidateRect.offset(-(focusedRect.width() + 1), 0);
            break;
        case View.FOCUS_UP:
            mBestCandidateRect.offset(0, focusedRect.height() + 1);
            break;
        case View.FOCUS_DOWN:
            mBestCandidateRect.offset(0, -(focusedRect.height() + 1));
    }

    View closest = null;

    int numFocusables = focusables.size();
    for (int i = 0; i < numFocusables; i++) {
        View focusable = focusables.get(i);

        // only interested in other non-root views
        if (focusable == focused || focusable == root) continue;

        // get focus bounds of other view in same coordinate system
        focusable.getFocusedRect(mOtherRect);
        root.offsetDescendantRectToMyCoords(focusable, mOtherRect);

        if (isBetterCandidate(direction, focusedRect, mOtherRect, mBestCandidateRect)) {
            mBestCandidateRect.set(mOtherRect);
            closest = focusable;
        }
    }
    return closest;
}

核心算法就在這里了欠啤,遍歷focusables集合,拿出每個view的rect屬性和當(dāng)前focused view的rect進(jìn)行“距離”的比較屋灌,最終得到“距離”最近的候選者并返回。至此应狱,整個尋焦邏輯結(jié)束共郭。感興趣的同學(xué)可研究內(nèi)部比較的算法。

在整個尋焦過程中疾呻,我們發(fā)現(xiàn)focusSearch方法是public的除嘹,因此可在view樹的某個節(jié)點(diǎn)復(fù)寫此方法并返回期望view從而達(dá)到“攔截”默認(rèn)尋焦的流程。同理岸蜗,addFocusables方法也是public的尉咕,復(fù)寫此方法可縮小比較view的范圍,提高效率璃岳。

2.4 requestFocus

最后一步是請求焦點(diǎn)年缎,根據(jù)代碼條件會出現(xiàn)兩個分支,一個是調(diào)用兩個參數(shù)的requestFocus(int direction, Rect previouslyFocusedRect)铃慷,此方法來自View但是ViewGroup有override单芜;另一個是一個參數(shù)的requestFocus(int direction),來自View且聲明為final犁柜。所以就要分上一步尋找到的focus目標(biāo)是View還是ViewGroup兩種情況進(jìn)行分析洲鸠。

如果是View,來看View的requestFocus源碼

public final boolean requestFocus(int direction) {
    return requestFocus(direction, null);
}

public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
    return requestFocusNoSearch(direction, previouslyFocusedRect);
}

private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
    // need to be focusable
    if ((mViewFlags & FOCUSABLE_MASK) != FOCUSABLE ||
            (mViewFlags & VISIBILITY_MASK) != VISIBLE) {
        return false;
    }

    // need to be focusable in touch mode if in touch mode
    if (isInTouchMode() &&
        (FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
           return false;
    }

    // need to not have any parents blocking us
    if (hasAncestorThatBlocksDescendantFocus()) {
        return false;
    }

    handleFocusGainInternal(direction, previouslyFocusedRect);
    return true;
}

看來最終都會走到requestFocusNoSearch方法馋缅,而且其中的核心方法一看就知道是handleFocusGainInternal扒腕。

void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {
    if (DBG) {
        System.out.println(this + " requestFocus()");
    }

    if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
        mPrivateFlags |= PFLAG_FOCUSED;

        View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;

        if (mParent != null) {
            mParent.requestChildFocus(this, this);
        }

        if (mAttachInfo != null) {
            mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
        }

        onFocusChanged(true, direction, previouslyFocusedRect);
        refreshDrawableState();
    }
}

大致也分為幾步:

  1. requestChildFocus 將焦點(diǎn)通過遞歸傳遞給父View,父View更新mFocused屬性萤悴,屬性值就是其中包含focused view的子View/ViewGroup瘾腰,這樣focused view tree就更新了。
  2. 各種回調(diào)focus狀態(tài)給各個監(jiān)聽器稚疹,我們常用的OnFocusChangedListener就是其中一種居灯。
  3. refreshDrawableState更新drawable狀態(tài)。

再來看如果是ViewGroup内狗,ViewGroup的requestFocus源碼如下:

/**
 * {@inheritDoc}
 *
 * Looks for a view to give focus to respecting the setting specified by
 * {@link #getDescendantFocusability()}.
 *
 * Uses {@link #onRequestFocusInDescendants(int, android.graphics.Rect)} to
 * find focus within the children of this group when appropriate.
 *
 * @see #FOCUS_BEFORE_DESCENDANTS
 * @see #FOCUS_AFTER_DESCENDANTS
 * @see #FOCUS_BLOCK_DESCENDANTS
 * @see #onRequestFocusInDescendants(int, android.graphics.Rect) 
 */
@Override
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
    if (DBG) {
        System.out.println(this + " ViewGroup.requestFocus direction="
                + direction);
    }
    int descendantFocusability = getDescendantFocusability();

    switch (descendantFocusability) {
        case FOCUS_BLOCK_DESCENDANTS:
            return super.requestFocus(direction, previouslyFocusedRect);
        case FOCUS_BEFORE_DESCENDANTS: {
            final boolean took = super.requestFocus(direction, previouslyFocusedRect);
            return took ? took : onRequestFocusInDescendants(direction, previouslyFocusedRect);
        }
        case FOCUS_AFTER_DESCENDANTS: {
            final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect);
            return took ? took : super.requestFocus(direction, previouslyFocusedRect);
        }
        default:
            throw new IllegalStateException("descendant focusability must be "
                    + "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS "
                    + "but is " + descendantFocusability);
    }
}

這里必須要清楚descendantFocusability屬性值
看注釋結(jié)合代碼邏輯可知怪嫌,此屬性決定requestFocus事件的傳遞順序。

  • FOCUS_BLOCK_DESCENDANTS:子view不處理柳沙,本view直接處理岩灭,這樣就走到了上述View的requestFocus邏輯。
  • FOCUS_BEFORE_DESCENDANTS:本view先處理赂鲤,如果消費(fèi)了事件噪径,子View不再處理柱恤,反之再交給子View處理。
  • FOCUS_AFTER_DESCENDANTS:同上找爱,只不過順序變?yōu)樽覸iew先處理梗顺。

那這個值的默認(rèn)值是什么呢?其實(shí)在ViewGroup的構(gòu)造方法中調(diào)用了initViewGroup方法车摄,在這個方法中默認(rèn)設(shè)置了descendantFocusability的屬性為FOCUS_BEFORE_DESCENDANTS寺谤,也就是本View先處理。
最后看下onRequestFocusInDescendants的源碼:

/**
 * Look for a descendant to call {@link View#requestFocus} on.
 * Called by {@link ViewGroup#requestFocus(int, android.graphics.Rect)}
 * when it wants to request focus within its children.  Override this to
 * customize how your {@link ViewGroup} requests focus within its children.
 * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
 * @param previouslyFocusedRect The rectangle (in this View's coordinate system)
 *        to give a finer grained hint about where focus is coming from.  May be null
 *        if there is no hint.
 * @return Whether focus was taken.
 */
@SuppressWarnings({"ConstantConditions"})
protected boolean onRequestFocusInDescendants(int direction,
        Rect previouslyFocusedRect) {
    int index;
    int increment;
    int end;
    int count = mChildrenCount;
    if ((direction & FOCUS_FORWARD) != 0) {
        index = 0;
        increment = 1;
        end = count;
    } else {
        index = count - 1;
        increment = -1;
        end = -1;
    }
    final View[] children = mChildren;
    for (int i = index; i != end; i += increment) {
        View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
            if (child.requestFocus(direction, previouslyFocusedRect)) {
                return true;
            }
        }
    }
    return false;
}

由此可知吮播,根據(jù)方向鍵決定遍歷順序变屁,遍歷過程只要有一個子View處理了焦點(diǎn)事件便立即返回,整個流程結(jié)束意狠。很多常用的類都復(fù)寫過此方法粟关,比如 RecyclerView,ViewPager等等环戈。

三.總結(jié)

整篇文章源碼分析挺多的闷板,主要是為了找到可對尋焦邏輯有影響的關(guān)鍵節(jié)點(diǎn),實(shí)際上也是Android系統(tǒng)為上層開的"口子"谷市,方便根據(jù)實(shí)際需求改變默認(rèn)行為蛔垢。

  • 消費(fèi)按鍵事件,事件在傳遞過程中如果被消費(fèi)便不會走尋焦邏輯迫悠,這是一種攔截焦點(diǎn)的思路鹏漆。
  • focusSearch,上層可復(fù)寫此方法返回特定view创泄,來直接中斷尋焦流程艺玲。RecyclerView就復(fù)寫了這個方法,并且為LayoutManager留了一個onInterceptFocusSearch回調(diào)鞠抑,將攔截事件轉(zhuǎn)發(fā)給LayoutManager來實(shí)現(xiàn)特定的攔截焦點(diǎn)邏輯饭聚,比如常用的列表邊界攔截。
  • 為View/ViewGroup設(shè)置了nextDownId,nextLeftId等屬性可定向分配焦點(diǎn)搁拙。
  • addFocusables秒梳,復(fù)寫此方法可縮小/擴(kuò)大比較view的候選者,間接影響焦點(diǎn)的分配箕速。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末酪碘,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子盐茎,更是在濱河造成了極大的恐慌兴垦,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異探越,居然都是意外死亡狡赐,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門钦幔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來枕屉,“玉大人,你說我怎么就攤上這事鲤氢〔笫” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵铜异,是天一觀的道長。 經(jīng)常有香客問我秸架,道長揍庄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任东抹,我火速辦了婚禮蚂子,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缭黔。我一直安慰自己食茎,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布馏谨。 她就那樣靜靜地躺著别渔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪惧互。 梳的紋絲不亂的頭發(fā)上哎媚,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機(jī)與錄音喊儡,去河邊找鬼拨与。 笑死,一個胖子當(dāng)著我的面吹牛艾猜,可吹牛的內(nèi)容都是我干的买喧。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼匆赃,長吁一口氣:“原來是場噩夢啊……” “哼淤毛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起炸庞,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤钱床,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后埠居,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體查牌,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡事期,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了纸颜。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兽泣。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖胁孙,靈堂內(nèi)的尸體忽然破棺而出唠倦,到底是詐尸還是另有隱情,我是刑警寧澤涮较,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布稠鼻,位于F島的核電站,受9級特大地震影響狂票,放射性物質(zhì)發(fā)生泄漏候齿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一闺属、第九天 我趴在偏房一處隱蔽的房頂上張望慌盯。 院中可真熱鬧,春花似錦掂器、人聲如沸亚皂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽灭必。三九已至,卻和暖如春巍膘,著一層夾襖步出監(jiān)牢的瞬間厂财,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工峡懈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留璃饱,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓肪康,卻偏偏與公主長得像荚恶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子磷支,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353