View的工作原理

在Android的知識(shí)體系中,View扮演著很重要的角色,簡(jiǎn)單來(lái)理解氮墨,View是Android在視覺上的呈現(xiàn)椿猎。在界面上Android提供了一套GUI庫(kù)鹉胖,里面有很多控件,但是很多時(shí)候我們并不滿足于系統(tǒng)提供的控件,因?yàn)檫@樣就意味著這應(yīng)用界面的同類比較嚴(yán)重,如何做出與眾不同的效果呢亿汞,就是自定義View。

初始ViewRoot和DecorView

首先揪阿,要先了解下View的一些基本概念疗我,這樣才能更好理解View的measure、layout和draw過(guò)程南捂。

ViewRoot對(duì)應(yīng)于ViewRootImpl類碍粥,它是連接WindowManager和DecorView的紐帶,View的三大流程是通過(guò)VeiwRoot來(lái)完成的黑毅。在ActivityThread中,當(dāng)Activity對(duì)象被創(chuàng)建完畢后钦讳,會(huì)將DecorVeiw添加到Window中矿瘦,同時(shí)會(huì)創(chuàng)建ViewRootImpl對(duì)象枕面,并將ViewRootImpl對(duì)象和DecorView建立關(guān)聯(lián)。

View的繪制流程是從ViewRoot的performTraversals方法開始的缚去,它經(jīng)過(guò)measure潮秘、layout和draw三個(gè)過(guò)程才能最終將一個(gè)View繪制出來(lái)。如圖:

View繪制流程圖

從中易结,我們可以看到枕荞,performTraversals會(huì)依次調(diào)用performMeasure、performLayout搞动、performDraw三個(gè)方法躏精,這個(gè)三個(gè)方法分別完成頂級(jí)View的measure、layout鹦肿、draw這三大方法矗烛,在onMeasure方法中則會(huì)對(duì)所有的子元素進(jìn)行measure過(guò)程,這個(gè)時(shí)候measure流程就從父容器傳遞到子元素中了箩溃,這樣就完成一次measure過(guò)程瞭吃,接著子元素會(huì)重復(fù)父容器的過(guò)程,如此反復(fù)就完成了整個(gè)View樹的遍歷涣旨。同理歪架,其他兩個(gè)步驟也是類似的過(guò)程。

measure過(guò)程決定了View的寬和高霹陡,Measure完成以后和蚪,可以通過(guò)getMeasureWidth和getMeasureHeight方法來(lái)獲取到View的測(cè)量后的寬和高。

理解MeasureSpec

確切來(lái)說(shuō)穆律,MeasureSpec在很大程度上決定了一個(gè)View的尺寸規(guī)格惠呼,之所以說(shuō)是很大程度上是因?yàn)檫@個(gè)過(guò)程還是受父容器的影響,因?yàn)楦溉萜饔绊慥iew的MeasureSpec的創(chuàng)建過(guò)程峦耘。在測(cè)量過(guò)程中剔蹋,系統(tǒng)會(huì)將View的LayoutParams根據(jù)父容器所施加的規(guī)則轉(zhuǎn)換成對(duì)應(yīng)的MeasureSpec,然后再根據(jù)這個(gè)measureSpec來(lái)測(cè)量出View的寬和高辅髓。

MeasureSpec代表一個(gè)32位int值泣崩,高2位代表SpecMode,低30位代表SpecSize洛口,SpecMode是指測(cè)量模式矫付,而SpecSize是指在某種測(cè)量模式下的規(guī)格大小。MeasureSpec通過(guò)將SpecMode和SpecSize打包成一個(gè)int值來(lái)避免過(guò)多的對(duì)象內(nèi)存分配第焰,為了方便操作买优,其提供了打包和解包的方法。

SpecMode有三類,每一類都表示了特殊的含義杀赢。

  • UNSPECIFIED烘跺。父容器不對(duì)View有任何限制,要多大就給多大脂崔,這種情況一般用于系統(tǒng)內(nèi)部滤淳,表示一種測(cè)量的狀態(tài)。
  • EXACTLY砌左。父容器已經(jīng)檢測(cè)出View所需要的精確大小脖咐,這個(gè)時(shí)候View的最終大小就是SpecSize所指定的值。它對(duì)應(yīng)于LayoutParams中的match_parent和具體的數(shù)值這兩種模式汇歹。
  • AT_MOST屁擅。父容器指定了一個(gè)可用大小即SpecSize,View的大小不能大于這個(gè)值秤朗,具體是什么值要看不同的View的具體實(shí)現(xiàn)煤蹭。它對(duì)應(yīng)于LayoutParams中的wrap_content。

簡(jiǎn)單來(lái)說(shuō)取视,當(dāng)View采用固定寬/高的時(shí)候硝皂,不管父容器的MeasureSpec是什么,View的MeasureSpec都是EXACTLY模式并且大小遵循LayoutParams中的大小作谭,當(dāng)View的寬/高是match_parent時(shí)稽物,如果父容器的模式是EXACTLY,那么View也是精確模式并且其大小是父容器的剩余空間折欠,如果父容器是最大模式贝或,那么View也是最大模式并且其大小不會(huì)超過(guò)父容器的剩余空間,當(dāng)View的寬/高是wrap_content時(shí)锐秦,不管父容器的模式是精確還是最大化咪奖,View的模式總是最大化并且大小不能超過(guò)父容器的剩余空間。

View的工作流程

View的工作流程主要是指measure羊赵、layout、draw這三大流程扇谣,即測(cè)量昧捷、布局、繪制罐寨,其中measure確定View的測(cè)量寬/高靡挥,layout確定View的最終寬/高和四個(gè)頂點(diǎn)的位置,而draw則將View繪制到屏幕上鸯绿。

measure過(guò)程

measure過(guò)程要分情況來(lái)看跋破,如果是一個(gè)原始的View簸淀,那么通過(guò)measure方法就完成了其測(cè)量過(guò)程,如果是一個(gè)ViewGroup毒返,除了完成自己的測(cè)量過(guò)程外啃擦,還會(huì)遍歷去調(diào)用所有子元素的measure方法,各個(gè)子元素再遞歸去執(zhí)行這個(gè)流程饿悬。

1,View的measure過(guò)程

measure方法是一個(gè)final類型的方法聚霜,這意味著子類不能重寫此方法狡恬,在View的measure方法中會(huì)去調(diào)用View的onMeasure方法,所以只需看onMeasure方法即可蝎宇。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

上面的代碼很簡(jiǎn)潔弟劲,但是簡(jiǎn)潔并不代表簡(jiǎn)單,setMeasuredDimension方法會(huì)設(shè)置View的寬/高的測(cè)量值姥芥,因此我們只需要看getDefaultSize這個(gè)方法兔乞。

public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

簡(jiǎn)單理解,其實(shí)getDefaultSize方法返回的大小就是MeasureSpec中的specSize凉唐,而這個(gè)specSize就是View測(cè)量后的大小庸追,但View的最終大小是在layout階段確定的,所以這里必須要加以區(qū)分台囱,但是幾乎所有情況下的View的測(cè)量大小和最終大小是相等的淡溯。

同時(shí),直接繼承View的自定義控件需要重寫onMeasure方法并設(shè)置wrap_content時(shí)的自身大小簿训,否則在布局中使用wrap_content就相當(dāng)于使用match_parent咱娶。為什么呢,如果View在布局中使用wrap_content强品,那么它的specMode是AT_MOST模式膘侮,在這種模式下,它的寬/高等于specSize的榛,也就是說(shuō)琼了,這種情況下的View的specSize是parentSize,而parentSize是父容器中目前當(dāng)前剩余使用的大小困曙,也就是父容器當(dāng)前剩余的空間大小表伦。

那么該如何該解決這個(gè)問(wèn)題,很簡(jiǎn)單慷丽,代碼如下:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(200, 200);
        } else if (widthSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(200, heightSpecSize);
        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(widthSpecSize, 200);
        }
    }

給wrap_content設(shè)置一個(gè)默認(rèn)值蹦哼,比如都是寬/高都是200px。

2要糊,ViewGroup的measure過(guò)程

對(duì)于ViewGroup來(lái)說(shuō)纲熏,除了完成自己的measure過(guò)程以外,還會(huì)遍歷去調(diào)用所有子元素的measure方法,各個(gè)子元素再去遞歸執(zhí)行這個(gè)過(guò)程局劲。和View不同的是勺拣,ViewGroup是一個(gè)抽象類,因此它沒有重寫View的onMeasure方法鱼填,但是它提供了一個(gè)叫measureChildren的方法药有。

    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

從上述代碼來(lái)看,ViewGroup在measure時(shí)苹丸,會(huì)對(duì)每一個(gè)子元素進(jìn)行measure愤惰。具體measureChild這個(gè)方法的實(shí)現(xiàn)也很好理解,如下所示:

protected void measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

很明顯赘理,measureChild的思想就是取出子元素的LayoutParams宦言,然后再通過(guò)getChildMeasureSpec來(lái)創(chuàng)建子元素的MeasureSpec,接著將MeasureSpec直接傳遞給View的measure方法進(jìn)行測(cè)量商模。

Layout過(guò)程

Layout的作用是ViewGroup用來(lái)確定子元素的位置奠旺,當(dāng)ViewGroup的位置被確定后,它在onLayout中會(huì)遍歷所有的子元素并調(diào)用其layout方法施流,在layout方法中onLayout方法又會(huì)被調(diào)用响疚。先看下View中的layout方法

    @SuppressWarnings({"unchecked"})
    public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    }

layout方法的大致流程:首先會(huì)通過(guò)setFrame方法來(lái)設(shè)定View的四個(gè)頂點(diǎn)的位置,即初始化mLeft嫂沉、mRight稽寒、mTop和mBottom這四個(gè)值,View的四個(gè)頂點(diǎn)一旦確定趟章,那么View在父容器中的位置也就確定了杏糙,接著就會(huì)調(diào)用onLayout方法,這個(gè)方法用途是父容器確定子元素的位置蚓土,和onMeasure方法類似宏侍,onLayout的具體實(shí)現(xiàn)同樣和具體的布局有關(guān),所以View和ViewGroup均沒有真正實(shí)現(xiàn)onLayout方法蜀漆。

draw過(guò)程

draw過(guò)程就比較簡(jiǎn)單了谅河,它的作用是將View繪制到屏幕上面,View的繪制過(guò)程循序以下幾步:

  1. 繪制背景background.draw(canvas)
  2. 繪制自己(onDraw)
  3. 繪制children(dispatchDraw)
  4. 繪制裝飾(onDrawScrollBars)

這一點(diǎn)看代碼确丢,就能看出來(lái)绷耍。

public void draw(Canvas canvas) {
        if (mClipBounds != null) {
            canvas.clipRect(mClipBounds);
        }
        final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

        // Step 1, draw the background, if needed
        int saveCount;

        if (!dirtyOpaque) {
            final Drawable background = mBackground;
            if (background != null) {
                final int scrollX = mScrollX;
                final int scrollY = mScrollY;

                if (mBackgroundSizeChanged) {
                    background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);
                    mBackgroundSizeChanged = false;
                }

                if ((scrollX | scrollY) == 0) {
                    background.draw(canvas);
                } else {
                    canvas.translate(scrollX, scrollY);
                    background.draw(canvas);
                    canvas.translate(-scrollX, -scrollY);
                }
            }
        }

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            // Step 6, draw decorations (scrollbars)
            onDrawScrollBars(canvas);

            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // we're done...
            return;
        }

        /*
         * Here we do the full fledged routine...
         * (this is an uncommon case where speed matters less,
         * this is why we repeat some of the tests that have been
         * done above)
         */

        boolean drawTop = false;
        boolean drawBottom = false;
        boolean drawLeft = false;
        boolean drawRight = false;

        float topFadeStrength = 0.0f;
        float bottomFadeStrength = 0.0f;
        float leftFadeStrength = 0.0f;
        float rightFadeStrength = 0.0f;

        // Step 2, save the canvas' layers
        int paddingLeft = mPaddingLeft;

        final boolean offsetRequired = isPaddingOffsetRequired();
        if (offsetRequired) {
            paddingLeft += getLeftPaddingOffset();
        }

        int left = mScrollX + paddingLeft;
        int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
        int top = mScrollY + getFadeTop(offsetRequired);
        int bottom = top + getFadeHeight(offsetRequired);

        if (offsetRequired) {
            right += getRightPaddingOffset();
            bottom += getBottomPaddingOffset();
        }

        final ScrollabilityCache scrollabilityCache = mScrollCache;
        final float fadeHeight = scrollabilityCache.fadingEdgeLength;
        int length = (int) fadeHeight;

        // clip the fade length if top and bottom fades overlap
        // overlapping fades produce odd-looking artifacts
        if (verticalEdges && (top + length > bottom - length)) {
            length = (bottom - top) / 2;
        }

        // also clip horizontal fades if necessary
        if (horizontalEdges && (left + length > right - length)) {
            length = (right - left) / 2;
        }

        if (verticalEdges) {
            topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
            drawTop = topFadeStrength * fadeHeight > 1.0f;
            bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
            drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
        }

        if (horizontalEdges) {
            leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
            drawLeft = leftFadeStrength * fadeHeight > 1.0f;
            rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
            drawRight = rightFadeStrength * fadeHeight > 1.0f;
        }

        saveCount = canvas.getSaveCount();

        int solidColor = getSolidColor();
        if (solidColor == 0) {
            final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;

            if (drawTop) {
                canvas.saveLayer(left, top, right, top + length, null, flags);
            }

            if (drawBottom) {
                canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
            }

            if (drawLeft) {
                canvas.saveLayer(left, top, left + length, bottom, null, flags);
            }

            if (drawRight) {
                canvas.saveLayer(right - length, top, right, bottom, null, flags);
            }
        } else {
            scrollabilityCache.setFadeColor(solidColor);
        }

        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);

        // Step 4, draw the children
        dispatchDraw(canvas);

        // Step 5, draw the fade effect and restore layers
        final Paint p = scrollabilityCache.paint;
        final Matrix matrix = scrollabilityCache.matrix;
        final Shader fade = scrollabilityCache.shader;

        if (drawTop) {
            matrix.setScale(1, fadeHeight * topFadeStrength);
            matrix.postTranslate(left, top);
            fade.setLocalMatrix(matrix);
            canvas.drawRect(left, top, right, top + length, p);
        }

        if (drawBottom) {
            matrix.setScale(1, fadeHeight * bottomFadeStrength);
            matrix.postRotate(180);
            matrix.postTranslate(left, bottom);
            fade.setLocalMatrix(matrix);
            canvas.drawRect(left, bottom - length, right, bottom, p);
        }

        if (drawLeft) {
            matrix.setScale(1, fadeHeight * leftFadeStrength);
            matrix.postRotate(-90);
            matrix.postTranslate(left, top);
            fade.setLocalMatrix(matrix);
            canvas.drawRect(left, top, left + length, bottom, p);
        }

        if (drawRight) {
            matrix.setScale(1, fadeHeight * rightFadeStrength);
            matrix.postRotate(90);
            matrix.postTranslate(right, top);
            fade.setLocalMatrix(matrix);
            canvas.drawRect(right - length, top, right, bottom, p);
        }

        canvas.restoreToCount(saveCount);

        // Step 6, draw decorations (scrollbars)
        onDrawScrollBars(canvas);

        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }
    }

View的繪制過(guò)程的傳遞是通過(guò)dispatchDraw來(lái)實(shí)現(xiàn)的,dispatchDraw會(huì)遍歷調(diào)用所有子元素的draw方法鲜侥,如此draw事件就一層層地傳遞下去褂始。

自定義View

自定義View是一個(gè)綜合的技術(shù)體系,它涉及View的層次結(jié)構(gòu)描函、事件分發(fā)機(jī)制和View的工作原理等技術(shù)細(xì)節(jié)崎苗。

自定義View的分類

  1. 繼承View重寫onDraw方法狐粱。這種方法主要用于實(shí)現(xiàn)一些不規(guī)則的效果,即這種效果不方便通過(guò)布局的組合方式來(lái)達(dá)到胆数,往往需要靜態(tài)或者動(dòng)態(tài)地顯示一些不規(guī)則的圖形肌蜻。這種方式需要重寫onDraw方法,同時(shí)需要自己支持wrap_content必尼,并且padding也需要自己處理蒋搜。
  2. 繼承ViewGroup派生特殊的Layout。這種方法主要用于實(shí)現(xiàn)自定義的布局判莉,即除了LinearLayout齿诞、RelativeLayout、FrameLayout這幾種系統(tǒng)的布局之外骂租,我們需要重新定義一種新的布局。
  3. 繼承特定的View(比如TextView)斑司。這種方法比較常見渗饮,一般是用于擴(kuò)展某種已有的View的功能,比如TextView宿刮。
  4. 繼承特定的ViewGroup(比如LinearLayout)互站。這種效果看起來(lái)很像幾種View組合在一起的時(shí)候,可以采用這種方法實(shí)現(xiàn)僵缺。

自定義View須知

一些具體的注意事項(xiàng)胡桃。

  • 讓View支持wrap_content
  • 如果有必要,讓你的View支持padding
  • 盡量不要在View中使用Handler磕潮,沒必要
  • View中如果有線程或者動(dòng)畫翠胰,需要及時(shí)停止,參考View#onDetachedFromWindow
  • View帶有滑動(dòng)嵌套情形時(shí)自脯,需要處理好滑動(dòng)沖突
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末之景,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子膏潮,更是在濱河造成了極大的恐慌锻狗,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件焕参,死亡現(xiàn)場(chǎng)離奇詭異轻纪,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)叠纷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門刻帚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人讲岁,你說(shuō)我怎么就攤上這事我擂〕囊裕” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵校摩,是天一觀的道長(zhǎng)看峻。 經(jīng)常有香客問(wèn)我,道長(zhǎng)衙吩,這世上最難降的妖魔是什么互妓? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮坤塞,結(jié)果婚禮上冯勉,老公的妹妹穿的比我還像新娘。我一直安慰自己摹芙,他們只是感情好灼狰,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著浮禾,像睡著了一般交胚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上盈电,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天蝴簇,我揣著相機(jī)與錄音,去河邊找鬼匆帚。 笑死熬词,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的吸重。 我是一名探鬼主播互拾,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼嚎幸!你這毒婦竟也來(lái)了摩幔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鞭铆,失蹤者是張志新(化名)和其女友劉穎或衡,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體车遂,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡封断,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了舶担。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坡疼。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖衣陶,靈堂內(nèi)的尸體忽然破棺而出柄瑰,到底是詐尸還是另有隱情闸氮,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布教沾,位于F島的核電站蒲跨,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏授翻。R本人自食惡果不足惜或悲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望堪唐。 院中可真熱鬧巡语,春花似錦、人聲如沸淮菠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)合陵。三九已至理澎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間曙寡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工寇荧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留举庶,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓揩抡,卻偏偏與公主長(zhǎng)得像户侥,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子峦嗤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

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