View:繪制流程

1 Android視圖層次結(jié)構(gòu)

視圖層次結(jié)構(gòu).png

上圖是針對(duì)比較老的Android系統(tǒng)版本中制作的虏等,新的版本中會(huì)略有出入,但整體上沒變拷邢。平時(shí)在Activity中setContentView(...)時(shí)挨队,添加到“+id/content”的FrameLayout上嘁字,自己的布局對(duì)應(yīng)的是上圖中ViewGrop的樹狀結(jié)構(gòu)。

  • Window概念
    Window表示的是一個(gè)窗口的概念掸掸,它是站在WindowManagerService角度上的一個(gè)抽象的概念氯庆,Android中所有的視圖都是通過Window來呈現(xiàn)的,不管是Activity扰付、Dialog還是Toast堤撵,只要有View的地方就一定有Window。
    注意:抽象的Window概念和PhoneWindow這個(gè)類并不是同一個(gè)東西羽莺,PhoneWindow表示的是手機(jī)屏幕的抽象实昨,它充當(dāng)Activity和DecorView之間的媒介,就算沒有PhoneWindow也是可以展示View的盐固。

  • DecorView概念
    DecorView是整個(gè)Window界面的最頂層View荒给,View的測(cè)量、布局刁卜、繪制志电、事件分發(fā)都是由DecorView往下遍歷這個(gè)View樹。DecorView作為頂級(jí)View蛔趴,一般情況下它內(nèi)部會(huì)包含一個(gè)豎直方向的LinearLayout挑辆,在這個(gè)LinearLayout里面有上下兩個(gè)部分(具體情況和Android的版本及主題有關(guān)),上面是【標(biāo)題欄】孝情,下面是【內(nèi)容欄】之拨。在Activity中我們通過setContentView所設(shè)置的布局文件其實(shí)就是被加載到【內(nèi)容欄】中的,而內(nèi)容欄的id是content咧叭,因此指定布局的方法叫setContent()秀又。

  • ViewRoot概念
    ViewRoot對(duì)應(yīng)于ViewRootImpl類,它是連接WindowManager和DecorView的紐帶稳吮,View的三大流程均是通過ViewRoot來完成的。在ActivityThread中派撕,當(dāng)Activity對(duì)象被創(chuàng)建完之后,會(huì)將DecorView添加到Window中睬魂,同時(shí)會(huì)創(chuàng)建對(duì)應(yīng)的ViewRootImpl终吼,并將ViewRootImpl和DecorView建立關(guān)聯(lián),并保存到WindowManagerGlobal對(duì)象中氯哮。

2 繪制的起源點(diǎn)

Activity啟動(dòng)在ActivityThread.java類中完成际跪,期間會(huì)調(diào)用到handleResumeActivity(...)方法,這個(gè)方法是View繪制的起源點(diǎn)喉钢,整個(gè)調(diào)用鏈如下圖所示:


View繪制起源時(shí)序圖.png

2.1 handleResumeActivity()

關(guān)鍵代碼如下:

//=== ActivityThread.java ===
final void handleResumeActivity(...) {
    ......
    //跟蹤代碼后發(fā)現(xiàn)其初始賦值為mWindow = new PhoneWindow(this, window, activityConfigCallback);
    r.window = r.activity.getWindow(); 
    //從PhoneWindow實(shí)例中獲取DecorView  
    View decor = r.window.getDecorView();
    ......
    //跟蹤代碼后發(fā)現(xiàn)姆打,vm值為上述PhoneWindow實(shí)例中獲取的WindowManager。
    ViewManager wm = a.getWindowManager();
    ......
    //當(dāng)前window的屬性肠虽,從代碼跟蹤來看是PhoneWindow窗口的屬性
    WindowManager.LayoutParams l = r.window.getAttributes();
    ......
    wm.addView(decor, l);
    ......
}

ViewManager是一個(gè)接口幔戏,addView是其定義的一個(gè)方法,其實(shí)現(xiàn)類為WindowManagerImpl税课。wm.addView(decor, l)中兩個(gè)參數(shù)會(huì)層層傳遞闲延,直到ViewRootImpl類中。下面分析下這個(gè)兩個(gè)參數(shù)由來韩玩。

2.1.1 參數(shù)decor

//=== PhoneWindow.java ===

// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
......

public PhoneWindow(...){
   ......
   mDecor = (DecorView) preservedWindow.getDecorView();
   ......
}

@Override
public final View getDecorView() {
   ......
   return mDecor;
}

decor是DecorView實(shí)例垒玲,它是window的頂級(jí)視圖。其類繼承關(guān)系為:DecorView -> FrameLayout -> ViewGroup -> View

2.1.2 參數(shù)l

//=== Window.java ===
private final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
......
public final WindowManager.LayoutParams getAttributes() {
    return mWindowAttributes;
}

//=== WindowManager內(nèi)部類LayoutParams ===
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
    public LayoutParams() {
        super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        ......
    }
}

//=== ViewGroup.java內(nèi)部類LayoutParams ===
public LayoutParams(int width, int height) {
    this.width = width;
    this.height = height;
}

參數(shù)l:表示PhoneWindow的LayoutParams屬性找颓,其width和height值均為L(zhǎng)ayoutParams.MATCH_PARENT侍匙。

2.2 performTraversals()

performTraversals方法有大約800多行代碼,控制著整個(gè)繪制流程叮雳,關(guān)鍵代碼如下:

// === ViewRootImpl.java ===
private void performTraversals() {
   ......
   int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
   int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);      
   ......
   // Ask host how big it wants to be
   performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
   ......
   performLayout(lp, mWidth, mHeight);
   ......
   performDraw();
}

上述代碼是繪制流程的完成過程想暗,涉及三個(gè)步驟:
1)performMeasure():從根節(jié)點(diǎn)向下遍歷View樹,完成所有ViewGroup和View的測(cè)量工作帘不,計(jì)算出所有ViewGroup和View顯示出來需要的高度和寬度说莫;

2)performLayout():從根節(jié)點(diǎn)向下遍歷View樹,完成所有ViewGroup和View的布局計(jì)算工作寞焙,根據(jù)測(cè)量出來的寬高及自身屬性储狭,計(jì)算出所有ViewGroup和View顯示在屏幕上的區(qū)域;

3)performDraw():從根節(jié)點(diǎn)向下遍歷View樹捣郊,完成所有ViewGroup和View的繪制工作辽狈,根據(jù)布局過程計(jì)算出的顯示區(qū)域,將所有View的當(dāng)前需顯示的內(nèi)容畫到屏幕上呛牲。

performTraversals整體過程如下圖所示:


performTraversals整體繪制示意圖.png

3 View繪制的三個(gè)流程

一個(gè)完整的繪制流程包括measure刮萌、layout、draw三個(gè)步驟娘扩,其中:

  • measure(測(cè)量)
    系統(tǒng)會(huì)先根據(jù)xml布局文件和代碼中對(duì)控件屬性的設(shè)置着茸,來計(jì)算出每個(gè)View和ViewGrop的尺寸壮锻,并將這些尺寸保存下來。
  • layout(布局)
    根據(jù)測(cè)量出的結(jié)果以及對(duì)應(yīng)的參數(shù)涮阔,來確定每一個(gè)控件應(yīng)該顯示的位置猜绣。
  • draw(繪制)
    確定好位置后,就將這些控件繪制到屏幕上敬特。

3.1 measure過程分析

3.1.1 MeasureSpec介紹

public static class MeasureSpec {
    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
    ......
    /**
     * Measure specification mode: The parent has not imposed any constraint
     * on the child. It can be whatever size it wants.
     */
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;

    /**
     * Measure specification mode: The parent has determined an exact size
     * for the child. The child is going to be given those bounds regardless
     * of how big it wants to be.
     */
    public static final int EXACTLY     = 1 << MODE_SHIFT;

    /**
     * Measure specification mode: The child can be as large as it wants up
     * to the specified size.
     */
    public static final int AT_MOST     = 2 << MODE_SHIFT;
    ......
   
    /**
     * Creates a measure specification based on the supplied size and mode.
     *...... 
     *@return the measure specification based on size and mode        
     */
    public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                      @MeasureSpecMode int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
        ...... 
    }
    
    ......
    /**
     * Extracts the mode from the supplied measure specification.
     *......
     */
    @MeasureSpecMode
    public static int getMode(int measureSpec) {
        //noinspection ResourceType
        return (measureSpec & MODE_MASK);
    }

    /**
     * Extracts the size from the supplied measure specification.
     *......
     * @return the size in pixels defined in the supplied measure specification
     */
    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }
    ......
}
  1. MeasureSpec含義
    MeasureSpec概括了從父布局傳遞給子view布局要求掰邢。MeasureSpec是32位的int值,高2位代表SpecMode(模式)伟阔,低30位代表SepcSize(尺寸)辣之,這樣的打包方式好處是避免過多的對(duì)象內(nèi)存分配。其結(jié)構(gòu)示意圖如下:


    MeasureSpec結(jié)構(gòu)示意圖.png
  2. 三種模式

  • UNSPECIFIED:未指定尺寸模式减俏。父容器不對(duì)View有任何限制召烂,要多大就給多大碱工。(筆者注:這個(gè)在工作中極少碰到娃承,據(jù)說一般在系統(tǒng)中才會(huì)用到,后續(xù)會(huì)講得很少)
  • EXACTLY:精確值模式怕篷。父布局決定了子view的準(zhǔn)確尺寸历筝,子view無論想設(shè)置多大的值,都將限定在那個(gè)邊界內(nèi)廊谓。
  • AT_MOST:最大值模式梳猪。子view可以一直大到指定的值。(筆者注:寬高屬性設(shè)置為wrap_content蒸痹,那么它的最大值不會(huì)超過父布局給定的值春弥,所以稱為最大值模式)


    三種mode示意圖.png
  1. 主要方法
方法 含義
makeMeasureSpec 用于將mode和size打包成一個(gè)int型的MeasureSpec
getMode 從指定的measureSpec值中獲取其mode
getSize 從指定的measureSpec值中獲取其size

3.1.2 ViewGroup.LayoutParams介紹

//=== ViewGroup.java ===
public static class LayoutParams {
    ......

    /**
     * Special value for the height or width requested by a View.
     * MATCH_PARENT means that the view wants to be as big as its parent,
     * minus the parent's padding, if any. Introduced in API Level 8.
     */
    public static final int MATCH_PARENT = -1;

    /**
     * Special value for the height or width requested by a View.
     * WRAP_CONTENT means that the view wants to be just large enough to fit
     * its own internal content, taking its own padding into account.
     */
    public static final int WRAP_CONTENT = -2;

    /**
     * Information about how wide the view wants to be. Can be one of the
     * constants FILL_PARENT (replaced by MATCH_PARENT
     * in API Level 8) or WRAP_CONTENT, or an exact size.
     */
    public int width;

    /**
     * Information about how tall the view wants to be. Can be one of the
     * constants FILL_PARENT (replaced by MATCH_PARENT
     * in API Level 8) or WRAP_CONTENT, or an exact size.
     */
    public int height;
    ......
}
  1. LayoutParams含義
    View用LayoutParams告訴父布局,它們想要怎樣被布局叠荠。其width和height屬性對(duì)應(yīng)著layout_width和layout_height屬性匿沛。ViewGroup不同的子類,會(huì)定義出不同LayoutParams子類榛鼎。

  2. LayoutParams取值
    LayoutParams指定三種數(shù)值:MATCH_PARENT逃呼、WRAP_CONTENT、具體數(shù)值者娱;

  • MATCH_PARENT:該view希望和父布局尺寸一樣大抡笼。
  • WRAP_CONTENT:該view希望其大小為僅僅足夠包裹住其內(nèi)容即可。

3.1.3 View測(cè)量流程

3.1.3.1 ViewRootImpl.performMeasure()
//=== ViewRootImpl.java ===
public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
          ......
          mView = view;
          ......
          mWindowAttributes.copyFrom(attrs);
          ......
    }
    
    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
           ......
           mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
           ......
    }
}

① setView的參數(shù)view和attrs是ActivityThread類中addView方法傳遞過來的黄鳍,可以確定mView指的是DecorView推姻。
② 在performMeasure()中,其實(shí)是DecorView在執(zhí)行measure()操作框沟。如果您這存在“mView不是View類型的嗎拾碌,怎么會(huì)指代DecorView作為整個(gè)View體系的根view呢”這樣的疑惑吐葱,那這里就啰嗦一下,DecorView extends FrameLayout extends ViewGroup extends View校翔,通過這個(gè)繼承鏈可以看到弟跑,DecorView是一個(gè)容器,但ViewGroup也是View的子類防症,View是所有控件的基類孟辑,所以這里View類型的mView指代DecorView是沒毛病的。

childWidthMeasureSpec和childHeightMeasureSpec由來

int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);

getRootMeasureSpec(int,int)方法的完整源碼如下所示:

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {

    case ViewGroup.LayoutParams.MATCH_PARENT:
        // Window can't resize. Force root view to be windowSize.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
    case ViewGroup.LayoutParams.WRAP_CONTENT:
        // Window can resize. Set max size for root view.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
        break;
    default:
        // Window wants to be an exact size. Force root view to be that size.
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}

① 基于window的layout params蔫敲,在window中為root view獲取measureSpec饲嗽。
② 參數(shù)windowSize:window的可用寬度和高度值;參數(shù)rootDimension:window的寬/高的layout param值奈嘿。

3.1.3.2 View.measure()

盡管mView就是DecorView貌虾,但是由于measure()方法是final型的,View子類都不能重寫該方法裙犹,所以這里追蹤measure()的時(shí)候就直接進(jìn)入到View類中了尽狠,這里貼出關(guān)鍵流程代碼:

//=== View.java ===
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
      ......
      // measure ourselves, this should set the measured dimension flag back
      onMeasure(widthMeasureSpec, heightMeasureSpec);
      ......
}

① 系統(tǒng)將measure方法定義為final,說明系統(tǒng)不希望整個(gè)測(cè)量流程框架被修改叶圃。
② view的實(shí)際測(cè)量工作放在onMeasure方法實(shí)現(xiàn)的袄膏??
③ 參數(shù)widthMeasureSpec:父布局加入的水平空間要求掺冠;參數(shù)heightMeasureSpec:父布局加入的垂直空間要求沉馆。

3.1.3.3 View.onMeasure()
//=== View.java ===
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                         getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

① 當(dāng)重寫該方法時(shí),必須調(diào)用setMeasuredDimension(int,int)來存儲(chǔ)該view測(cè)量出的寬和高德崭,否則會(huì)觸發(fā)IllegalStateException斥黑,由measure(int,int)拋出。調(diào)用基類的onMeasure(int,int)方法是一個(gè)有效的方法眉厨。
② ViewGroup的子類必須重寫該方法锌奴,才能繪制該容器內(nèi)的子view。如果是自定義一個(gè)子控件缺猛,extends View缨叫,那么并不是必須重寫該方法;
③ 容器類控件都是ViewGroup的子類荔燎,如FrameLayout耻姥、LinearLayout等,都會(huì)重寫onMeasure方法有咨,根據(jù)自己的特性來進(jìn)行測(cè)量琐簇;如果是葉子節(jié)點(diǎn)view,即最里層的控件,如TextView等婉商,也可能會(huì)重寫onMeasure方法似忧,所以當(dāng)流程走到onMeasure(...)時(shí),流程可能就會(huì)切到那些重寫的onMeasure()方法中去丈秩。
④ widthMeasureSpec:父布局加入的水平空間要求盯捌;heightMeasureSpec:父布局加入的垂直空間要求。
⑤ 如果該方法被重寫蘑秽,子類負(fù)責(zé)確保測(cè)量的高和寬至少是該view的mininum高度和mininum寬度值(鏈接getSuggestedMininumHeight()和getSuggestedMininumWidth())饺著;

getSuggestedMinimumWidth()方法

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

"mininum width“指的是在xml布局文件中該view的“android:minWidth"屬性值,“background's minimum width”值是指“android:background”的寬度肠牲。該方法的返回值就是兩者之間較大的那一個(gè)值幼衰,用來作為該view的最小寬度值。現(xiàn)在應(yīng)該很容易理解了吧缀雳,當(dāng)一個(gè)view在layout文件中同時(shí)設(shè)置了這兩個(gè)屬性時(shí)渡嚣,為了兩個(gè)條件都滿足,自然要選擇值大一點(diǎn)的那個(gè)了肥印。

getDefaultSize()方法

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

① 參數(shù)由widthMeasureSpec變成了measuredWidth识椰,即由“父布局加入的水平空間要求”轉(zhuǎn)變?yōu)榱藇iew的寬度,measuredHeigh也是一樣竖独。
② 如果父布局沒有施加任何限制裤唠,即MeasureSpec的mode為UNSPECIFIED挤牛,那么返回值為參數(shù)中提供的size值莹痢。如果父布局施加了限制,則返回的默認(rèn)尺寸為保存在參數(shù)measureSpec中的specSize值墓赴。

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

① measuredWidth:該view被測(cè)量出寬度值竞膳;measuredHeight:該view被測(cè)量出的高度值。

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

View中的成員變量mMeasureWidth和mMeasureHeight就被賦值了诫硕,這也就意味著坦辟,View的測(cè)量就結(jié)束了。

//=== View.java ===
public static final int MEASURED_SIZE_MASK = 0x00ffffff;

public final int getMeasuredWidth() {
   return mMeasuredWidth & MEASURED_SIZE_MASK;
}

public final int getMeasuredHeight() {
   return mMeasuredHeight & MEASURED_SIZE_MASK;
}

① 獲取原始的測(cè)量寬度和高度章办,這兩個(gè)方法需在setMeasuredDimension()方法執(zhí)行后才有效锉走,否則返回值為0。

3.1.4 DecorView測(cè)量過程

3.1.4.1 DecorView.onMeasure()
//=== DecorView.java ===
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    ......
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    ......
}

//=== FrameLayout.java ===
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        ......
        measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
        ......             
    }
    ......
    setMeasuredDimension(......)
}

① DecorView的繼承鏈:DecorView extends FrameLayout extends ViewGroup extends View藕届。當(dāng)DecorView第一次調(diào)用到measure()方法后挪蹭,流程就開始切換到重寫的onMeasure()中。DecorView在onMeasure()方法做一些事項(xiàng)后休偶,調(diào)用父類的onMeasure方法梁厉。
② FrameLayout對(duì)OnMeasure()方法進(jìn)行重寫,當(dāng)所有子view測(cè)量完成后踏兜,最后調(diào)用setMeasuredDimension(...)來測(cè)量自己的词顾。

3.1.4.2 ViewGroup.measureChildWithMargins()

measureChild()方法和measureChildWithMargins()

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

protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                    + widthUsed, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                    + heightUsed, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

① measureChildWithMargins在measureChild的基礎(chǔ)上增加:已使用的寬高八秃、margin值。其實(shí)它們的功能都是一樣的肉盹,最后都是生成子View的MeasureSpec昔驱,并傳遞給子View繼續(xù)測(cè)量,即最后一句代碼child.measure(childWidthMeasureSpec, childHeightMeasureSpec)上忍。
② 在FrameLayout和LinearLayout中重寫的onMeasure方法中調(diào)用的就是后者舍悯,而AbsoluteLayout中就是間接地調(diào)用的前者。而RelativeLayout中睡雇,兩者都沒有調(diào)用萌衬,而是自己寫了一套方法,不過該方法和后者方法僅略有差別它抱,但基本功能還是一樣秕豫。

getChildMeasureSpec()方法
目的:將父布局傳遞來的MeasureSpec和其子view的LayoutParams,整合成子View的MeasureSpec观蓄。

// spec參數(shù)   表示父View的MeasureSpec 
// padding參數(shù)    父View的Padding+子View的Margin混移,父View的大小減去這些邊距,才能精確算出
//               子View的MeasureSpec的size
// childDimension參數(shù)  表示該子View內(nèi)部LayoutParams屬性的值(lp.width或者lp.height)
//                    可以是wrap_content侮穿、match_parent歌径、一個(gè)精確指(an exactly size),  
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  
    int specMode = MeasureSpec.getMode(spec);  //獲得父View的mode  
    int specSize = MeasureSpec.getSize(spec);  //獲得父View的大小  

   //父View的大小-自己的Padding+子View的Margin,得到值才是子View的大小亲茅。
    int size = Math.max(0, specSize - padding);   
  
    int resultSize = 0;    //初始化值回铛,最后通過這個(gè)兩個(gè)值生成子View的MeasureSpec
    int resultMode = 0;    //初始化值,最后通過這個(gè)兩個(gè)值生成子View的MeasureSpec
  
    switch (specMode) {  
    // Parent has imposed an exact size on us  
    //1克锣、父View是EXACTLY的 茵肃!  
    case MeasureSpec.EXACTLY:   
        //1.1、子View的width或height是個(gè)精確值 (an exactly size)  
        if (childDimension >= 0) {            
            resultSize = childDimension;         //size為精確值  
            resultMode = MeasureSpec.EXACTLY;    //mode為 EXACTLY 袭祟。  
        }   
        //1.2验残、子View的width或height為 MATCH_PARENT/FILL_PARENT   
        else if (childDimension == LayoutParams.MATCH_PARENT) {  
            // Child wants to be our size. So be it.  
            resultSize = size;                   //size為父視圖大小  
            resultMode = MeasureSpec.EXACTLY;    //mode為 EXACTLY 。  
        }   
        //1.3巾乳、子View的width或height為 WRAP_CONTENT  
        else if (childDimension == LayoutParams.WRAP_CONTENT) {  
            // Child wants to determine its own size. It can't be  
            // bigger than us.  
            resultSize = size;                   //size為父視圖大小  
            resultMode = MeasureSpec.AT_MOST;    //mode為AT_MOST 您没。  
        }  
        break;  
  
    // Parent has imposed a maximum size on us  
    //2、父View是AT_MOST的 胆绊!      
    case MeasureSpec.AT_MOST:  
        //2.1氨鹏、子View的width或height是個(gè)精確值 (an exactly size)  
        if (childDimension >= 0) {  
            // Child wants a specific size... so be it  
            resultSize = childDimension;        //size為精確值  
            resultMode = MeasureSpec.EXACTLY;   //mode為 EXACTLY 。  
        }  
        //2.2辑舷、子View的width或height為 MATCH_PARENT/FILL_PARENT  
        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;                  //size為父視圖大小  
            resultMode = MeasureSpec.AT_MOST;   //mode為AT_MOST  
        }  
        //2.3喻犁、子View的width或height為 WRAP_CONTENT  
        else if (childDimension == LayoutParams.WRAP_CONTENT) {  
            // Child wants to determine its own size. It can't be  
            // bigger than us.  
            resultSize = size;                  //size為父視圖大小  
            resultMode = MeasureSpec.AT_MOST;   //mode為AT_MOST  
        }  
        break;  
  
    // Parent asked to see how big we want to be  
    //3、父View是UNSPECIFIED的 !  
    case MeasureSpec.UNSPECIFIED:  
        //3.1肢础、子View的width或height是個(gè)精確值 (an exactly size)  
        if (childDimension >= 0) {  
            // Child wants a specific size... let him have it  
            resultSize = childDimension;        //size為精確值  
            resultMode = MeasureSpec.EXACTLY;   //mode為 EXACTLY  
        }  
        //3.2还栓、子View的width或height為 MATCH_PARENT/FILL_PARENT  
        else if (childDimension == LayoutParams.MATCH_PARENT) {  
            // Child wants to be our size... find out how big it should  
            // be  
            resultSize = 0;                        //size為0! ,其值未定  
            resultMode = MeasureSpec.UNSPECIFIED;  //mode為 UNSPECIFIED  
        }   
        //3.3传轰、子View的width或height為 WRAP_CONTENT  
        else if (childDimension == LayoutParams.WRAP_CONTENT) {  
            // Child wants to determine its own size.... find out how  
            // big it should be  
            resultSize = 0;                        //size為0! 剩盒,其值未定  
            resultMode = MeasureSpec.UNSPECIFIED;  //mode為 UNSPECIFIED  
        }  
        break;  
    }  
    //根據(jù)上面邏輯條件獲取的mode和size構(gòu)建MeasureSpec對(duì)象。  
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
}    

pecMode和specSize分別是父布局傳下來的要求慨蛙,size的值是父布局尺寸要求減去其padding值辽聊,最小不會(huì)小于0。代碼最后就是將重新得到的mode和size組合生成一個(gè)新的MeasureSpec期贫,傳遞給子View跟匆,一直遞歸下去。本段代碼重難點(diǎn)就是這里新mode和新size值的確定通砍,specMode和childDimension各有3種值玛臂,所以最后會(huì)有9種組合》馑铮總結(jié)圖形如下:


父規(guī)格與子布局的組合形式.png
  • 如果specMode的值為MeasureSpec.EXACTLY迹冤,即父布局對(duì)子view的尺寸要求是一個(gè)精確值,這有兩種情況虎忌,父布局中l(wèi)ayout_width屬性值被設(shè)置為具體值泡徙,或者match_parent,它們都被定義為精確值膜蠢。子view的childDimension討論:
    ① childDimension值為具體數(shù)值時(shí)堪藐,此時(shí)resultSize為childDimension的精確值,resultMode理所當(dāng)然為MeasureSpec.EXACTLY狡蝶。這里不知道讀者會(huì)不會(huì)又疑問庶橱,如果子View的layout_width值比父布局的大贮勃,那這個(gè)結(jié)論還成立嗎贪惹?按照我們的經(jīng)驗(yàn),似乎不太能理解寂嘉,因?yàn)樽觱iew的寬度再怎么樣也不會(huì)比父布局大奏瞬。事實(shí)上,我們平時(shí)經(jīng)驗(yàn)看到的泉孩,是最后布局后繪制出來的結(jié)果硼端,而當(dāng)前步驟為測(cè)量值,是有差別的寓搬。讀者可以自定義一個(gè)View珍昨,將父布局layout_width設(shè)置為100px,該自定義的子view則設(shè)置為200px,然后在子view中重寫的onMeasure方法中打印出getMeasuredWidth()值看看镣典,其值一定是200兔毙。甚至如果子view設(shè)置的值超過屏幕尺寸,其打印值也是設(shè)置的值兄春。
    ② childDimension值為L(zhǎng)ayoutParams.MATCH_PARENT時(shí)澎剥。這個(gè)容易理解,它的尺寸和父布局一樣赶舆,也是個(gè)精確值哑姚,所以resultSize為前面求出的size值,由父布局決定芜茵,resultMode為MeasureSpec.EXACTLY叙量。
    ③ childDimension值為L(zhǎng)ayoutParams.WRAP_CONTENT時(shí)。當(dāng)子view的layout_width被設(shè)置為wrap_content時(shí)九串,子view最多能夠達(dá)到父視圖的大小宛乃,所以resultSize值為size大小,resultMode為MeasureSpec.AT_MOST蒸辆。
  • 如果specMode值為MeasureSpec.AT_MOST征炼,父視圖對(duì)應(yīng)于layout_width為wrap_content。子view的childDimension討論:
    ① childDimension為精確值時(shí)躬贡。很容易明確specSize為自身的精確值谆奥,specMode為MeasureSpec.EXACTLY。
    ② childDimension為L(zhǎng)ayoutParams.MATCH_PARENT時(shí)拂玻。specSize由父布局決定酸些,specSize為size,specMode為MeasureSpec.AT_MOST檐蚜。
    ③ childDimension為L(zhǎng)ayoutParams.WRAP_CONTENT時(shí)魄懂。specSize由父布局決定,specSize為size闯第,specMode為MeasureSpec.AT_MOST市栗。
  • 如果specMode值為MeasureSpec.UNSPECIFIED。前面說過咳短,平時(shí)很少用填帽,一般用在系統(tǒng)中,不過這里還是簡(jiǎn)單說明一下咙好。這一段有個(gè)變量View.sUseZeroUnspecifiedMeasureSpec篡腌,它是用于表示當(dāng)前的目標(biāo)api是否低于23(對(duì)應(yīng)系統(tǒng)版本為Android M)的,低于23則為true勾效,否則為false∴诘浚現(xiàn)在系統(tǒng)版本基本上都是Android M及以上的叛甫,所以這里該值我們當(dāng)成false來處理。
    ① childDimension為精確值時(shí)杨伙。很容易明確specSize為自身的精確值合溺,specMode為MeasureSpec.EXACTLY。
    ② childDimension為L(zhǎng)ayoutParams.MATCH_PARENT時(shí)缀台。specSize由父布局決定為size棠赛,specMode和父布局一樣,為MeasureSpec.UNSPECIFIED膛腐。
    ③ childDimension為L(zhǎng)ayoutParams.WRAP_CONTENT時(shí)睛约。specSize由父布局決定為size,specMode和父布局一樣哲身,為MeasureSpec.UNSPECIFIED辩涝。
3.1.4.3 DecorView視圖樹measure流程圖
--.png

3.2 layout過程分析

3.2.1 View布局流程

3.2.1.1 ViewRootImpl.performLayout()
//=== ViewRootImpl.java ===
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
   ......
   final View host = mView;
   ......
   host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
   ......
}

mView就是DecorView,lp中width和height均為L(zhǎng)ayoutParams.MATCH_PARENT勘天。

3.2.1.2 ViewGroup.layout()
//=== ViewGroup.java ===
@Override
public final void layout(int l, int t, int r, int b) {
    if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
        if (mTransition != null) {
            mTransition.layoutChange(this);
        }
        super.layout(l, t, r, b);
    } else {
        // record the fact that we noop'd it; request layout when transition finishes
        mLayoutCalledWhileSuppressed = true;
    }
}

① 由于DecorView是一個(gè)容器怔揩,是ViewGroup子類,所以跟蹤代碼的時(shí)候脯丝,實(shí)際上是先進(jìn)入到ViewGroup類中的layout方法中商膊。在layout方法中調(diào)用View.layout()方法。
② layout方法是final的宠进,說明系統(tǒng)不希望自定的ViewGroup子類破壞layout流程晕拆。

3.2.1.3 View.layout()
/**
 * Assign a size and position to a view and all of its
 * descendants
 *
 * <p>This is the second phase of the layout mechanism.
 * (The first is measuring). In this phase, each parent calls
 * layout on all of its children to position them.
 * This is typically done using the child measurements
 * that were stored in the measure pass().</p>
 *
 * <p>Derived classes should not override this method.
 * Derived classes with children should override
 * onLayout. In that method, they should
 * call layout on each of their children.</p>
 *
 * @param l Left position, relative to parent
 * @param t Top position, relative to parent
 * @param r Right position, relative to parent
 * @param b Bottom position, relative to parent
 */
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
    ......
    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);
        ......
    }
    ......
}

目的 :根據(jù)子視圖的大小以及布局參數(shù)將View樹放到合適的位置上。
① 給view和它的所有后代分配尺寸和位置材蹬。
② 派生類不應(yīng)該重寫該方法实幕,容器類應(yīng)該重寫onLayout方法。在重寫的onLayout方法中堤器,它們應(yīng)該為每一子view調(diào)用layout方法進(jìn)行布局昆庇。
③ 參數(shù)依次為:Left、Top闸溃、Right整吆、Bottom四個(gè)點(diǎn)相對(duì)父布局的位置。
④ setOpticalFrame方法最后會(huì)調(diào)用setFrame方法圈暗,將布局信息進(jìn)行保持掂为。

setFrame方法

//=================View.java================
/**
 * Assign a size and position to this view.
 *
 * This is called from layout.
 *
 * @param left Left position, relative to parent
 * @param top Top position, relative to parent
 * @param right Right position, relative to parent
 * @param bottom Bottom position, relative to parent
 * @return true if the new size and position are different than the
 *         previous ones
 * {@hide}
 */
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;
        ......
        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;
        ......
    }
    return changed;
}

① setFrame(l, t, r, b) 可以理解為給mLeft 、mTop员串、mRight、mBottom賦值昼扛,然后基本就能確定View自己在父視圖的位置了寸齐,這幾個(gè)值構(gòu)成的矩形區(qū)域就是該View顯示的位置欲诺,這里的具體位置都是相對(duì)與父視圖的位置。
② 返回值:如果新的尺寸和位置和之前的不同渺鹦,返回true扰法。

3.2.1.4 View.onLayout()
//============View.java============
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}

//=============ViewGroup.java===========
@Override
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);

由于layout時(shí)已經(jīng)將布局信息通過setFrame方法進(jìn)行保存起來,在onLayout方法已經(jīng)無須做額外事項(xiàng)毅厚,因此方法對(duì)于葉子view意義不大塞颁。但是對(duì)于容器類來說,需要一種遍歷所有子view的機(jī)制吸耿,所以ViewGroup的子類需要重寫此方法祠锣。

3.2.2 DecorView布局過程

3.2.2.1 DecorView.onLayout()
//==============DecorView.java================
 @Override
 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
     super.onLayout(changed, left, top, right, bottom);
     ......
}

DecorView的onLayout方法會(huì)調(diào)用父類FrameLayout的onLayout方法。

3.2.2.2 FrameLayout.onLayout()
//================FrameLayout.java==============
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}

void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
    final int count = getChildCount();
    ......
    for (int i = 0; i < count; i++) {
         final View child = getChildAt(i);
         if (child.getVisibility() != GONE) {
             final LayoutParams lp = (LayoutParams) child.getLayoutParams();

             final int width = child.getMeasuredWidth();
             final int height = child.getMeasuredHeight();
             ......
             child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}

① 對(duì)每一個(gè)child調(diào)用layout方法的咽安,如果該child仍然是父布局伴网,會(huì)繼續(xù)遞歸下去;如果是葉子view妆棒,則會(huì)走到view的onLayout空方法澡腾,該葉子view布局流程走完。
② width和height分別來源于measure階段存儲(chǔ)的測(cè)量值糕珊,如果這里通過其它渠道賦給width和height值动分,那么measure階段就不需要了。

3.2.2.3 DecorView的布局流程圖
-.png

3.3 draw過程分析

3.3.1 View繪制流程

3.3.1.1 ViewRootImpl.performDraw()
//=== ViewRootImpl.java ===
private void performDraw() {
    ......
    boolean canUseAsync = draw(fullRedrawNeeded);
    ......
}

private boolean draw(boolean fullRedrawNeeded) {
    ......
    if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets)) {
        return false;
    }
    ......
}

private boolean drawSoftware(...){
    ......
    mView.draw(canvas);
    ......
}

mView就是DecorView红选,這樣就開始了DecorView視圖樹的draw流程了刺啦。

3.3.1.2 DecorView.draw()
//================DecorView.java==============
@Override
public void draw(Canvas canvas) {
    super.draw(canvas);

    if (mMenuBackground != null) {
        mMenuBackground.draw(canvas);
    }
}

用完super.draw后,還畫了菜單背景纠脾,由于FrameLayout和ViewGroup都沒有重寫該方法玛瘸,所以就直接進(jìn)入都了View類中的draw方法了。

3.3.1.3 View.draw()
public void draw(Canvas canvas) {
    ...
    /*
     * 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
    ...
    background.draw(canvas);
    ...
    // skip step 2 & 5 if possible (common case)
    ...
    // Step 2, save the canvas' layers
    ...
    if (solidColor == 0) {
        final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;

        if (drawTop) {
            canvas.saveLayer(left, top, right, top + length, null, flags);
        }
    ...
    // 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

    if (drawTop) {
        matrix.setScale(1, fadeHeight * topFadeStrength);
        matrix.postTranslate(left, top);
        fade.setLocalMatrix(matrix);
        canvas.drawRect(left, top, right, top + length, p);
    }
    ...
    // Step 6, draw decorations (scrollbars)
    onDrawScrollBars(canvas);
}

從代碼上看苟蹈,這里做了很多工作糊渊,咱們簡(jiǎn)單說明一下,有助于理解這個(gè)“畫”工作慧脱。

  1. 第1步:背景繪制
    對(duì)應(yīng)我我們?cè)趚ml布局文件中設(shè)置的“android:background”屬性渺绒,這是整個(gè)“畫”過程的第一步,這一步是不重點(diǎn)菱鸥,知道這里干了什么就行宗兼。

  2. 第3步,對(duì)View的內(nèi)容進(jìn)行繪制
    onDraw(canvas) 方法是view用來draw 自己的氮采,具體如何繪制殷绍,顏色線條什么樣式就需要子View自己去實(shí)現(xiàn),View.java 的onDraw(canvas) 是空實(shí)現(xiàn)鹊漠,ViewGroup 也沒有實(shí)現(xiàn)主到,每個(gè)View的內(nèi)容是各不相同的茶行,所以需要由子類去實(shí)現(xiàn)具體邏輯。

  3. 第4步:對(duì)當(dāng)前View的所有子View進(jìn)行繪制
    dispatchDraw(canvas) 方法是用來繪制子View的登钥,View.java 的dispatchDraw()方法是一個(gè)空方法,因?yàn)閂iew沒有子View,不需要實(shí)現(xiàn)dispatchDraw ()方法畔师,ViewGroup就不一樣了,它實(shí)現(xiàn)了dispatchDraw ()方法牧牢。

  4. 第6步:畫裝飾看锉。
    這里指畫滾動(dòng)條和前景,其實(shí)平時(shí)的每一個(gè)view都有滾動(dòng)條塔鳍,只是沒有顯示而已伯铣。同樣這也不是重點(diǎn),知道做了這些事就行献幔。

3.3.1.4 View.onDraw()
//=== View.java ===
/**
 * Implement this to do your drawing.
 *
 * @param canvas the canvas on which the background will be drawn
 */
protected void onDraw(Canvas canvas) {
}

實(shí)現(xiàn)該方法來做“畫”工作懂傀。也就是說,具體的view需要重寫該方法蜡感,來畫自己想展示的東西蹬蚁,如文字,線條等郑兴。

3.3.1.4 ViewGroup.dispathcDraw()

View.dispatchDraw()

//=== View.java ===
/**
 * 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) {
}

view沒有子視圖犀斋,不需要進(jìn)行繪制派發(fā)。

ViewGroup.dispatchDraw()

//=== ViewGroup.java ===
@Override
protected void dispatchDraw(Canvas canvas) {
    final int childrenCount = mChildrenCount;
    final View[] children = mChildren;
    ......
    for (int i = 0; i < childrenCount; i++) {
        more |= drawChild(canvas, child, drawingTime);
        ......
    }
    ...... 
}

平時(shí)常用的LinearLayout情连、FrameLayout叽粹、RelativeLayout等常用的布局控件,都沒有再重寫該方法却舀,DecorView中也一樣虫几,而是只在ViewGroup中實(shí)現(xiàn)了dispatchDraw方法的重寫。所以當(dāng)DecorView執(zhí)行完onDraw方法后挽拔,流程就會(huì)切到ViewGroup中的dispatchDraw方法了辆脸。

View.drawChild()

//=== ViewGroup.java ===
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    return child.draw(canvas, this, drawingTime);
}

畫當(dāng)前ViewGroup中的某一個(gè)子view,其中參數(shù)drawingTime表示“畫”動(dòng)作發(fā)生的時(shí)間點(diǎn)螃诅。

View.draw(...)

//=== View.java ===
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
  ......
  draw(canvas);
  ......
}
3.3.1.5 draw的繪制流程
draw的遞歸流程.png

3.3.2 DecorView繪制流程圖

---.png

3.4 繪制過程小結(jié)

到目前為止啡氢,View的繪制流程就介紹完了。根節(jié)點(diǎn)是DecorView术裸,整個(gè)View體系就是一棵以DecorView為根的View樹倘是,依次通過遍歷來完成measure、layout和draw過程袭艺。而如果要自定義view搀崭,一般都是通過重寫onMeasure(),onLayout()匹表,onDraw()來完成要自定義的部分门坷,整個(gè)繪制流程也基本上是圍繞著這幾個(gè)核心的地方來展開的宣鄙。整個(gè)繪制過程流程示意圖如下:


繪制過程流程示意圖.png

參考鏈接

[1] View繪制流程
[2] Android View的繪制流程
[3] Android圖形系統(tǒng)(三)-View繪制流程
[4] Android View繪制的三大流程
[5] Android-View繪制流程淺析
[6] Android View繪制流程
[7] Android View 繪制流程(Draw)全面解析
[8] View的繪制-draw流程詳解
[9] 深入理解 Android 之 View 的繪制流程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末袍镀,一起剝皮案震驚了整個(gè)濱河市默蚌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌苇羡,老刑警劉巖绸吸,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異设江,居然都是意外死亡锦茁,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門叉存,熙熙樓的掌柜王于貴愁眉苦臉地迎上來码俩,“玉大人,你說我怎么就攤上這事歼捏「宕妫” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵瞳秽,是天一觀的道長(zhǎng)瓣履。 經(jīng)常有香客問我,道長(zhǎng)练俐,這世上最難降的妖魔是什么袖迎? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮腺晾,結(jié)果婚禮上燕锥,老公的妹妹穿的比我還像新娘。我一直安慰自己悯蝉,他們只是感情好归形,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著泉粉,像睡著了一般连霉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上嗡靡,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天跺撼,我揣著相機(jī)與錄音,去河邊找鬼讨彼。 笑死歉井,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的哈误。 我是一名探鬼主播哩至,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼躏嚎,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了菩貌?” 一聲冷哼從身側(cè)響起卢佣,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎箭阶,沒想到半個(gè)月后虚茶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仇参,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年嘹叫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诈乒。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡罩扇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出怕磨,到底是詐尸還是另有隱情喂饥,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布癌压,位于F島的核電站仰泻,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏滩届。R本人自食惡果不足惜集侯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望帜消。 院中可真熱鬧棠枉,春花似錦、人聲如沸泡挺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)娄猫。三九已至贱除,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間媳溺,已是汗流浹背月幌。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留悬蔽,地道東北人扯躺。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親录语。 傳聞我的和親對(duì)象是個(gè)殘疾皇子倍啥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

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