源碼探索系列11---關(guān)于View的繪制

我們開發(fā)過程辰企,基本需要自定義View阱表,畫一些自己的小插件出來
這需要我們掌握整個View的繪畫過程和一些別的小技巧。
這里總結(jié)下整個View的源碼中涉及到的一些繪制過程的核心部分川尖,
之后再來看下整體的內(nèi)容丐一,畢竟整個源碼有近2W1行藻糖,不是隨便一時半會能搞定的,還是得下不少功夫库车。

起航 ------ 繪制流程

API:23

一般View的“生命周期”即繪畫的流程像下面這樣巨柒。

st=>start: View的繪畫流程
 
op=>operation: measure()

op2=>operation: layout()

op3=>operation: draw()

e=>end: 結(jié)束

st->op->op2->op3->e

這個是一般的流程都這樣,

  1. 我們的measure負(fù)責(zé)去測量View的WidthHeight,
  2. 然后我們的layout負(fù)責(zé)去確定其在父容器的位置柠衍,
  3. 最后由draw來負(fù)責(zé)在屏幕上畫內(nèi)容洋满。

但實際還有一些別的步驟流程,如這些函數(shù)由上一層來調(diào)用珍坊, 就像我們的Activity的onCreate等牺勾!
不過現(xiàn)在先不提及。我們繼續(xù)看各個階段具體到底是做什么先阵漏。

measure

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    boolean optical = isLayoutModeOptical(this);
   ...
   
   onMeasure(widthMeasureSpec, heightMeasureSpec);
   
   ...
}

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

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;
}

我們的measure函數(shù)是個final類型的驻民,里面主要是調(diào)用了onMeasure函數(shù)翻具,由他做具體測量。
這里需要補(bǔ)充一部分內(nèi)容裆泳,關(guān)于MeasureSpec.EXACTLY工禾,MeasureSpec.AT_MOSTMeasureSpec.UNSPECIFIED

  • EXACTLY:
    這個詞的意思是父容器已經(jīng)檢測出View的精確大形趴(eg:width=200dp/match_parent),這時我們的View的最終大小值就是specSize的值笙隙。
  • AT_MOST:
    這個詞的意思是父容器指定了一個大芯固怠(eg:width=wrap_content),這時我們的View的大小是要小于等于specSize的值坏快,最終大小到底是多大莽鸿,要看View的具體實現(xiàn)祥得。
  • UNSPECIFIED:
    這個詞的意思是父容器不對View有任何大小的限制级及,需要多大就設(shè)置多大饮焦,但這一般是系統(tǒng)內(nèi)部用來表示一種測量的狀態(tài)县踢。當(dāng)然還有別的用處硼啤,例如我們的ScrollView丙曙,他就可以用這個來告訴子View,大小無限扯旷,任意畫钧忽。

上面的解釋看起來這個View的MeasureSpec類型由我們的LayoutParams來設(shè)置耸黑,但實際這個MeasureSpec是由View和父容器一起決定的大刊。這個好理解缺菌,例如我們的LinearLayout里面有個View伴郁,前者設(shè)置最高為200dp焊傅,后者為300dp狐胎,最終這個子View大小不由自己設(shè)置的300dp決定顽爹。具體的測量過程,下次再開貼說捏题,就不插在這里了公荧。我們繼續(xù)主線

這樣我們回看上面應(yīng)該就好理解getDefaultSize()里面的到底是什么意思了循狰。
MeasureSpec.UNSPECIFIED:的狀況下券勺,大小是result = size;关炼,由傳過來的參數(shù)覺得儒拂,我們看下具體做了什么

protected int getSuggestedMinimumHeight() {
    return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}

protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

我們拿getSuggestedMinimumHeight()來看下
里面含義就是:

  • 如果我沒背景社痛,那么就是mMinHeight大小蒜哀,這個值對應(yīng)于我們寫的android:minHeight="20dp"屬性,他的默認(rèn)值是0赊抖。

      case R.styleable.View_minWidth:
                      mMinWidth = a.getDimensionPixelSize(attr, 0);
                      break;
    
  • 如果我有背景氛雪,那就選背景的最小高度和mMinHeight中最大的报亩。
    這個背景的getMinimumHeight()內(nèi)容是

     /**
    * Returns the minimum height suggested by this Drawable. If a View uses this
    * Drawable as a background, it is suggested that the View use at least this
    * value for its height. (There will be some scenarios where this will not be
    * possible.) This value should INCLUDE any padding.
    *
    * @return The minimum height suggested by this Drawable. If this Drawable
    *         doesn't have a suggested minimum height, 0 is returned.
    */
     public int getMinimumHeight() {
       final int intrinsicHeight = getIntrinsicHeight();
       return intrinsicHeight > 0 ? intrinsicHeight : 0;
    

    }
    自帶的解釋已經(jīng)很具體了弦追,返回Drawable的最小高度掸哑,沒有的話就返回0苗分;可能有些奇怪牵辣,說得好像我們的Drawable可以沒高是的择浊。確實有些沒有琢岩,例如我們在自定義一些我們的圓角的Button在不同點擊效果時候用到的<shape>標(biāo)簽寫的背景,他就沒有薇缅。

繼續(xù)回主線

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
    ...
    setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    mMeasuredWidth = measuredWidth;
    mMeasuredHeight = measuredHeight;

    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

最后就設(shè)置了測量的大小,是的測量的大小娩缰,不是最終的大小拼坎,最終的大小還是需要根據(jù)實際做調(diào)整的泰鸡。
這樣我們的measure,測量過程就基本結(jié)束了饰迹。

一些題外話:

這里補(bǔ)充一個早年無知時候遇到的坑啊鸭,那時候項目要求弄一個像下面這樣的一個帶有氣泡框的進(jìn)度條赠制,

這里寫圖片描述
這里寫圖片描述

那時候就直接類似于下面這樣,繼承View绊谭,然后重寫onDraw函數(shù)龙誊,在里面繪制好整個樣子鹤树。

public class BubbleProgressBar extends View {

        public void onDraw(@NonNull Canvas canvas) {
              //畫進(jìn)度和泡泡框
        }
}    

但畫好后逊朽,遇到個問題追他,這個View居然自動填充滿整個界面邑狸,我設(shè)置的是wrap_content单雾,感覺應(yīng)該是系統(tǒng)幫我搞好硅堆,弄成很小的一個啊渐逃,怎么就那么大呢茄菊? 后來查了資料發(fā)現(xiàn)买羞,如果我們是直接繼承于View,那需要重寫下那個measure函數(shù)群叶,要不然他就會自動填滿,為啥呢舶衬?回看那個getDefaultSize函數(shù)

case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
       result = specSize;
     break;

我們的wrap_content就是那個AT_MOSTEXACTLY是同條路逛犹,實際就等于寫了Match_parent舞蔽。
所以我們得根據(jù)情況來做判斷渗柿,來給點指定大小

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    if(heightMode==MeasureSpec.AT_MOST widthMode == MeasureSpec.AT_MOST ){
       setMeasuredDimension(mOurDefalutHeight,mOurDefalutWidth);
    }         
     ...
}    

現(xiàn)在想想,大概當(dāng)年設(shè)計這個View類的人遇到了這個默認(rèn)初始化大小應(yīng)該是多大才合適的問題陨溅,所以干脆直接來個填充全局的方式声登。

前進(jìn) ------ Layout過程

看完了測量出界面的大小,我們需要開始下一步layout的過程了卸察。
layout主要是用來確定View的位置的合武,具體如下

public void layout(int l, int t, int r, int b) {
   
    ...

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

     ...
}

整個流程大致是先用setFrame()函數(shù)去設(shè)置我們的View的位置涡扼,然后調(diào)用onLayout來讓服從其確定之元素的位置稼跳,由于這個onLayout做的是具體的布局工作,需要具體的繼承的人去做吃沪,例如我們的LinearLayout有水平和垂直之分汤善,所以在View中里面什么也沒有。最后是調(diào)用監(jiān)聽函數(shù)票彪,通知他們onLayoutChange()了红淡。

 protected boolean setFrame(int left, int top, int right, int bottom) {
    boolean changed = false;

    if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
        changed = true;

        // Remember our drawn bit
        int drawn = mPrivateFlags & PFLAG_DRAWN;
        int oldWidth = mRight - mLeft;
        int oldHeight = mBottom - mTop;
        int newWidth = right - left;
        int newHeight = bottom - top;
        boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

        // Invalidate our old position
        invalidate(sizeChanged);

        mLeft = left;
        mTop = top;
        mRight = right;
        mBottom = bottom;
        
        mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

        if (sizeChanged) {
            sizeChange(newWidth, newHeight, oldWidth, oldHeight);
        }

         ...
    }
    return changed;
}

/**
 * Called from layout when this view should  
 * assign a size and position to each of its children.
 *
 * Derived classes with children should override this method 
 * and call layout on each of their children. 
 */
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

好了,基本的layout過程就這么結(jié)束了降铸,我們的View的布局也就確定了推掸。
接下來就看下draw過程了雾家。

前進(jìn)前進(jìn)------畫界面的Draw

這個畫的過程,主要就是把View繪制到屏幕上去,根據(jù)寫的注釋,我們看到View的繪制過程有這里六個步驟。其中兩個可以忽略的。

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

繼續(xù)的步驟如下:

public void draw(Canvas canvas) {
    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;


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

    if (!dirtyOpaque) {
        drawBackground(canvas);
    }

    // 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)
     */
     
        ... 畫特效部分
 }

我們再細(xì)看下各個步驟

private void drawBackground(Canvas canvas) {
    final Drawable background = mBackground;
    ...

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

然后這個onDraw和我們onLayout一樣今妄,需要自己寫杆兵,里面空空如也

protected void onDraw(Canvas canvas) {
    }

然后那個dispatchDraw()也是,這個需要我們自己做,但這個更多的是針對于ViewGroup類的包含子View的。這樣Draw事件就傳遞給下面,遍歷所有的子View元素的Draw方法,繪制完所有。

/**
 * Called by draw to draw the child views. This may be overridden
 * by derived classes to gain control just before its children are drawn
 * (but after its own view has been drawn).
 * @param canvas the canvas on which to draw the view
 */
protected void dispatchDraw(Canvas canvas) {

}

這樣我們的Draw過程也就介紹了。


一些補(bǔ)充

看完一個完整的View的繪制過程,這里補(bǔ)充一些關(guān)于ViewGroup的內(nèi)容
ViewGroup繪制過程中還需要讓他的各個子View去繪制。

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

這里看到忧陪,他對于那些除了設(shè)置為Gone不可見的叶堆,都進(jìn)行了繪制。
不過有一個點引起我的興趣,這個size的大小不是取數(shù)組children的大小,而是mChildrenCount這個值。難道這背后有一個什么故事椅棺?查了下沒什么結(jié)果诱渤。赡茸。华蜒。

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

繪制的過程也是直接調(diào)用他們的measure函數(shù)去執(zhí)行。在獲取到子View的MeasureSpec時,具體是:

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);

    int size = Math.max(0, specSize - padding);

    int resultSize = 0;
    int resultMode = 0;

    switch (specMode) {
    // Parent has imposed an exact size on us
    case MeasureSpec.EXACTLY:
        if (childDimension >= 0) {
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size. So be it.
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size. It can't be
            // bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

    // Parent has imposed a maximum size on us
    case MeasureSpec.AT_MOST:
        if (childDimension >= 0) {
            // Child wants a specific size... so be it
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size, but our size is not fixed.
            // Constrain child to not be bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size. It can't be
            // bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

    // Parent asked to see how big we want to be
    case MeasureSpec.UNSPECIFIED:
        if (childDimension >= 0) {
            // Child wants a specific size... let him have it
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size... find out how big it should
            // be
            resultSize = 0;
            resultMode = MeasureSpec.UNSPECIFIED;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size.... find out how
            // big it should be
            resultSize = 0;
            resultMode = MeasureSpec.UNSPECIFIED;
        }
        break;
    }
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

這里面做的事情,主要的就是根據(jù)父容器的MeasureSpec同時結(jié)合View本身的LayoutParams來共同決定子View的MeasureSpec睁枕,所以子元素能用的大小就是父容器的尺寸減去padding

    int specSize = MeasureSpec.getSize(spec);
    int size = Math.max(0, specSize - padding);

前面在說View的時候也有提到過這個,具體的View的大小是需要和父容器協(xié)商的租悄。

根據(jù)上面的內(nèi)容的決定子View的大小的過程,我們可以總結(jié)出一個規(guī)律谅辣,就是如果我們設(shè)置了具體的大形印(dp/px)那就是ChildSize公壤,要不然是ParentSize除了UNPSECIFIED,

childParams \ parentParams EXACTLY AT_MOST UNSPECIFIED
dp/px EXACTLY - childSize EXACTLY - childSize EXACTLY - childSize
match_parent EXACTLY - parentSize EXACTLY - parentSize UNSPECIFIED - 0
wrap_content EXACTLY - parentSize EXACTLY - parentSize UNSPECIFIED - 0

后記

一個View的繪制過程就這樣結(jié)束了,也沒太大負(fù)責(zé)的內(nèi)容,但一個View里面的內(nèi)容還是很多可以說的得湘,
例如:

  • 他內(nèi)部的post機(jī)制,他可以讓我們減少對Handler的使用。
  • Touch事件的傳遞
  • View的滑動

這些內(nèi)容我們后面繼續(xù)慢慢的補(bǔ)充吧含蓉。

另外這個View的調(diào)用者是ViewRoot,他的具體實現(xiàn)是ViewRootImpl蓄喇,在他的performTraversal函數(shù)里面发侵,執(zhí)行了我們的view的整個繪制周期的調(diào)用

st=>start: performTraversals()
e=>end: 結(jié)束

op1=>operation: View.measure
op2=>operation: View.layout
op3=>operation: View.draw

cond1=>condition: 不用重新Measure?
cond2=>condition: 不用重新Layout? 
cond3=>condition: 不用重新Draw?

st->cond1->cond2 
cond1(yes)->cond2
cond1(no)->op1
cond1->cond2->cond3
cond2(yes)->cond3
cond2(no)->op2
cond3(yes)->e
cond3(no)->op3

更具體的調(diào)用流程如下:


performMeasure->measure: 

measure->onMeasure: 
onMeasure--> View.measure:

另外我們的layout和draw的套路類似,就不細(xì)寫.

最后編輯于
?著作權(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)我...
    茶點故事閱讀 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日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了许布。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兴革。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出杂曲,到底是詐尸還是另有隱情庶艾,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布擎勘,位于F島的核電站咱揍,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏棚饵。R本人自食惡果不足惜煤裙,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望噪漾。 院中可真熱鬧硼砰,春花似錦、人聲如沸怪与。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽分别。三九已至遍愿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間耘斩,已是汗流浹背沼填。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留括授,地道東北人坞笙。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像荚虚,于是被迫代替她去往敵國和親薛夜。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,611評論 2 353

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