Android View的繪制流程

View的繪制和事件處理是兩個重要的主題,上一篇《圖解 Android事件分發(fā)機(jī)制》已經(jīng)把事件的分發(fā)機(jī)制講得比較詳細(xì)了饵溅,這一篇是針對View的繪制,View的繪制如果你有所了解,基本分為measure、layout滥酥、draw 過程,其中比較難理解就是measure過程畦幢,所以本篇文章大幅筆地分析measure過程坎吻,相對講得比較詳細(xì),文章也比較長宇葱,如果你對View的繪制還不是很懂瘦真,對measure過程掌握得不是很深刻刊头,那么耐心點(diǎn),看完這篇文章诸尽,相信你會有所收獲的原杂。

【轉(zhuǎn)載請明顯注明出處,尊重勞動成果】

Measure過程#


對于測量我們來說幾個知識點(diǎn),了解這幾個知識點(diǎn)您机,之后的實例分析你才看得懂穿肄。
1、MeasureSpec 的理解
對于View的測量际看,肯定會和MeasureSpec接觸咸产,MeasureSpec是兩個單詞組成,翻譯過來“測量規(guī)格”或者“測量參數(shù)”仲闽,很多博客包括官方文檔對他的說明基本都是“一個MeasureSpec封裝了從父容器傳遞給子容器的布局要求”,這個MeasureSpec 封裝的是父容器傳遞給子容器的布局要求脑溢,而不是父容器對子容器的布局要求,“傳遞” 兩個字很重要赖欣,更精確的說法應(yīng)該這個MeasureSpec是由父View的MeasureSpec和子View的LayoutParams通過簡單的計算得出一個針對子View的測量要求屑彻,這個測量要求就是MeasureSpec。

  • 大家都知道一個MeasureSpec是一個大小跟模式的組合值,MeasureSpec中的值是一個整型(32位)將size和mode打包成一個Int型顶吮,其中高兩位是mode社牲,后面30位存的是size,是為了減少對象的分配開支云矫。MeasureSpec 類似于下圖膳沽,只不過這邊用的是十進(jìn)制的數(shù),而MeasureSpec 是二進(jìn)制存儲的让禀。

    注:-1 代表的是EXACTLY挑社,-2 是AT_MOST
  • MeasureSpec一共有三種模式

UPSPECIFIED : 父容器對于子容器沒有任何限制,子容器想要多大就多大
EXACTLY: 父容器已經(jīng)為子容器設(shè)置了尺寸,子容器應(yīng)當(dāng)服從這些邊界,不論子容器想要多大的空間。
AT_MOST:子容器可以是聲明大小內(nèi)的任意大小

很多文章都會把這三個模式說成這樣巡揍,當(dāng)然其實包括官方文檔也是這樣表達(dá)的痛阻,但是這樣并不好理解。特別是如果把這三種模式又和MATCH_PARENT和WRAP_CONTENT 聯(lián)系到一起腮敌,很多人就懵逼了阱当。如果從代碼上來看view.measure(int widthMeasureSpec, int heightMeasureSpec) 的兩個MeasureSpec是父類傳遞過來的,但并不是完全是父View的要求糜工,而是父View的MeasureSpec和子View自己的LayoutParams共同決定的弊添,而子View的LayoutParams其實就是我們在xml寫的時候設(shè)置的layout_width和layout_height 轉(zhuǎn)化而來的。我們先來看代碼會清晰一些:

父View的measure的過程會先測量子View捌木,等子View測量結(jié)果出來后油坝,再來測量自己,上面的measureChildWithMargins就是用來測量某個子View的,我們來分析是怎樣測量的澈圈,具體看注釋:

protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { 

// 子View的LayoutParams彬檀,你在xml的layout_width和layout_height,
// layout_xxx的值最后都會封裝到這個個LayoutParams。
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();   

//根據(jù)父View的測量規(guī)格和父View自己的Padding瞬女,
//還有子View的Margin和已經(jīng)用掉的空間大星系邸(widthUsed),就能算出子View的MeasureSpec,具體計算過程看getChildMeasureSpec方法诽偷。
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);  

//通過父View的MeasureSpec和子View的自己LayoutParams的計算坤学,算出子View的MeasureSpec,然后父容器傳遞給子容器的
// 然后讓子View用這個MeasureSpec(一個測量要求渤刃,比如不能超過多大)去測量自己拥峦,如果子View是ViewGroup 那還會遞歸往下測量贴膘。
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

// 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洋闽、一個精確指(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;    //初始化值诫舅,最后通過這個兩個值生成子View的MeasureSpec
    int resultMode = 0;    //初始化值,最后通過這個兩個值生成子View的MeasureSpec
  
    switch (specMode) {  
    // Parent has imposed an exact size on us  
    //1宫患、父View是EXACTLY的 刊懈!  
    case MeasureSpec.EXACTLY:   
        //1.1礁竞、子View的width或height是個精確值 (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是個精確值 (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是個精確值 (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對象谷丸。  
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
}    

上面的代碼有點(diǎn)多堡掏,希望你仔細(xì)看一些注釋,代碼寫得很多刨疼,其實計算原理很簡單:
1泉唁、如果我們在xml 的layout_width或者layout_height 把值都寫死,那么上述的測量完全就不需要了揩慕,之所以要上面的這步測量亭畜,是因為 match_parent 就是充滿父容器,wrap_content 就是自己多大就多大迎卤, 我們寫代碼的時候特別爽拴鸵,我們編碼方便的時候,google就要幫我們計算你match_parent的時候是多大蜗搔,wrap_content的是多大劲藐,這個計算過程,就是計算出來的父View的MeasureSpec不斷往子View傳遞樟凄,結(jié)合子View的LayoutParams 一起再算出子View的MeasureSpec聘芜,然后繼續(xù)傳給子View,不斷計算每個View的MeasureSpec缝龄,子View有了MeasureSpec才能更測量自己和自己的子View汰现。

2、上述代碼如果這么來理解就簡單了

  • 如果父View的MeasureSpec 是EXACTLY叔壤,說明父View的大小是確切的瞎饲,(確切的意思很好理解,如果一個View的MeasureSpec 是EXACTLY百新,那么它的size 是多大企软,最后展示到屏幕就一定是那么大)。

1饭望、如果子View 的layout_xxxx是MATCH_PARENT仗哨,父View的大小是確切,子View的大小又MATCH_PARENT(充滿整個父View)铅辞,那么子View的大小肯定是確切的厌漂,而且大小值就是父View的size。所以子View的size=父View的size斟珊,mode=EXACTLY

2苇倡、如果子View 的layout_xxxx是WRAP_CONTENT,也就是子View的大小是根據(jù)自己的content 來決定的,但是子View的畢竟是子View旨椒,大小不能超過父View的大小晓褪,但是子View的是WRAP_CONTENT,我們還不知道具體子View的大小是多少综慎,要等到child.measure(childWidthMeasureSpec, childHeightMeasureSpec) 調(diào)用的時候才去真正測量子View 自己content的大谢练隆(比如TextView wrap_content 的時候你要測量TextView content 的大小,也就是字符占用的大小示惊,這個測量就是在child.measure(childWidthMeasureSpec, childHeightMeasureSpec)的時候好港,才能測出字符的大小,MeasureSpec 的意思就是假設(shè)你字符100px米罚,但是MeasureSpec 要求最大的只能50px钧汹,這時候就要截掉了)。通過上述描述录择,子View MeasureSpec mode的應(yīng)該是AT_MOST拔莱,而size 暫定父View的 size,表示的意思就是子View的大小沒有不確切的值糊肠,子View的大小最大為父View的大小辨宠,不能超過父View的大小(這就是AT_MOST 的意思)货裹,然后這個MeasureSpec 做為子View measure方法 的參數(shù),做為子View的大小的約束或者說是要求精偿,有了這個MeasureSpec子View再實現(xiàn)自己的測量弧圆。

3、如果如果子View 的layout_xxxx是確定的值(200dp)笔咽,那么就更簡單了搔预,不管你父View的mode和size是什么,我都寫死了就是200dp叶组,那么控件最后展示就是就是200dp拯田,不管我的父View有多大,也不管我自己的content 有多大甩十,反正我就是這么大船庇,所以這種情況MeasureSpec 的mode = EXACTLY 大小size=你在layout_xxxx 填的那個值。

  • 如果父View的MeasureSpec 是AT_MOST侣监,說明父View的大小是不確定鸭轮,最大的大小是MeasureSpec 的size值,不能超過這個值橄霉。

1窃爷、如果子View 的layout_xxxx是MATCH_PARENT,父View的大小是不確定(只知道最大只能多大),子View的大小MATCH_PARENT(充滿整個父View)按厘,那么子View你即使充滿父容器医吊,你的大小也是不確定的,父View自己都確定不了自己的大小逮京,你MATCH_PARENT你的大小肯定也不能確定的遮咖,所以子View的mode=AT_MOST,size=父View的size造虏,也就是你在布局雖然寫的是MATCH_PARENT御吞,但是由于你的父容器自己的大小不確定,導(dǎo)致子View的大小也不確定漓藕,只知道最大就是父View的大小陶珠。

2、如果子View 的layout_xxxx是WRAP_CONTENT享钞,父View的大小是不確定(只知道最大只能多大)揍诽,子View又是WRAP_CONTENT,那么在子View的Content沒算出大小之前栗竖,子View的大小最大就是父View的大小暑脆,所以子View MeasureSpec mode的就是AT_MOST,而size 暫定父View的 size狐肢。

3添吗、如果如果子View 的layout_xxxx是確定的值(200dp),同上份名,寫多少就是多少碟联,改變不了的。

  • 如果父View的MeasureSpec 是UNSPECIFIED(未指定),表示沒有任何束縛和約束僵腺,不像AT_MOST表示最大只能多大鲤孵,不也像EXACTLY表示父View確定的大小,子View可以得到任意想要的大小辰如,不受約束

1普监、如果子View 的layout_xxxx是MATCH_PARENT,因為父View的MeasureSpec是UNSPECIFIED琉兜,父View自己的大小并沒有任何約束和要求凯正,
那么對于子View來說無論是WRAP_CONTENT還是MATCH_PARENT,子View也是沒有任何束縛的呕童,想多大就多大漆际,沒有不能超過多少的要求,一旦沒有任何要求和約束夺饲,size的值就沒有任何意義了奸汇,所以一般都直接設(shè)置成0

2施符、同上...

3、如果如果子View 的layout_xxxx是確定的值(200dp)擂找,同上戳吝,寫多少就是多少,改變不了的(記住贯涎,只有設(shè)置的確切的值听哭,那么無論怎么測量,大小都是不變的塘雳,都是你寫的那個值)

到此為止陆盘,你是否對MeasureSpec 和三種模式、還有WRAP_CONTENT和MATCH_PARENT有一定的了解了败明,如果還有任何問題隘马,歡迎在我簡書(用戶名:Kelin)評論里留言。

2妻顶、View的測量過程主要是在onMeasure()方法
打開View的源碼酸员,找到measure方法,這個方法代碼不少讳嘱,但是測量工作都是在onMeasure()做的幔嗦,measure方法是final的所以這個方法也不可重寫,如果想自定義View的測量沥潭,你應(yīng)該去重寫onMeasure()方法

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
  ......
  onMeasure(widthMeasureSpec,heightMeasureSpec);
  .....
}

3邀泉、View的onMeasure 的默認(rèn)實現(xiàn)
打開View.java 的源碼來看下onMeasure的實現(xiàn)

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

View的onMeasure方法默認(rèn)實現(xiàn)很簡單,就是調(diào)用setMeasuredDimension()叛氨,setMeasuredDimension()可以簡單理解就是給mMeasuredWidth和mMeasuredHeight設(shè)值呼渣,如果這兩個值一旦設(shè)置了,那么意味著對于這個View的測量結(jié)束了寞埠,這個View的寬高已經(jīng)有測量的結(jié)果出來了。如果我們想設(shè)定某個View的高寬焊夸,完全可以直接通過setMeasuredDimension(100仁连,200)來設(shè)置死它的高寬(不建議),但是setMeasuredDimension方法必須在onMeasure方法中調(diào)用阱穗,不然會拋異常饭冬。我們來看下對于View來說它的默認(rèn)高寬是怎么獲取的。

//獲取的是android:minHeight屬性的值或者View背景圖片的大小值
protected int getSuggestedMinimumWidth() { 
   return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); 
} 
//@param size參數(shù)一般表示設(shè)置了android:minHeight屬性或者該View背景圖片的大小值  
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:        //表示該View的大小父視圖未定揪阶,設(shè)置為默認(rèn)值 
     result = size;  
     break;    
   case MeasureSpec.AT_MOST:    
   case MeasureSpec.EXACTLY:        
     result = specSize;  
     break;   
 }    
return result;
}

getDefaultSize的第一個參數(shù)size等于getSuggestedMinimumXXXX返回的的值(建議的最小寬度和高度)昌抠,而建議的最小寬度和高度都是由View的Background尺寸與通過設(shè)置View的minXXX屬性共同決定的,這個size可以理解為View的默認(rèn)長度鲁僚,而第二個參數(shù)measureSpec炊苫,是父View傳給自己的MeasureSpec,這個measureSpec是通過測量計算出來的裁厅,具體的計算測量過程前面在講解MeasureSpec已經(jīng)講得比較清楚了(是有父View的MeasureSpec和子View自己的LayoutParams 共同決定的)只要這個測試的mode不是UNSPECIFIED(未確定的),那么默認(rèn)的就會用這個測量的數(shù)值當(dāng)做View的高度侨艾。

對于View默認(rèn)是測量很簡單执虹,大部分情況就是拿計算出來的MeasureSpec的size 當(dāng)做最終測量的大小。而對于其他的一些View的派生類唠梨,如TextView袋励、Button、ImageView等当叭,它們的onMeasure方法系統(tǒng)了都做了重寫茬故,不會這么簡單直接拿 MeasureSpec 的size來當(dāng)大小,而去會先去測量字符或者圖片的高度等蚁鳖,然后拿到View本身content這個高度(字符高度等)磺芭,如果MeasureSpec是AT_MOST,而且View本身content的高度不超出MeasureSpec的size才睹,那么可以直接用View本身content的高度(字符高度等)徘跪,而不是像View.java 直接用MeasureSpec的size做為View的大小。

4琅攘、ViewGroup的Measure過程
ViewGroup 類并沒有實現(xiàn)onMeasure垮庐,我們知道測量過程其實都是在onMeasure方法里面做的,我們來看下FrameLayout 的onMeasure 方法,具體分析看注釋哦坞琴。

//FrameLayout 的測量
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
....
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {    
   final View child = getChildAt(i);    
   if (mMeasureAllChildren || child.getVisibility() != GONE) {   
    // 遍歷自己的子View哨查,只要不是GONE的都會參與測量,measureChildWithMargins方法在最上面
    // 的源碼已經(jīng)講過了剧辐,如果忘了回頭去看看寒亥,基本思想就是父View把自己的MeasureSpec 
    // 傳給子View結(jié)合子View自己的LayoutParams 算出子View 的MeasureSpec,然后繼續(xù)往下傳荧关,
    // 傳遞葉子節(jié)點(diǎn)溉奕,葉子節(jié)點(diǎn)沒有子View,根據(jù)傳下來的這個MeasureSpec測量自己就好了忍啤。
     measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);       
     final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 
     maxWidth = Math.max(maxWidth, child.getMeasuredWidth() +  lp.leftMargin + lp.rightMargin);        
     maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);  
     ....
     ....
   }
}
.....
.....
//所有的孩子測量之后加勤,經(jīng)過一系類的計算之后通過setMeasuredDimension設(shè)置自己的寬高,
//對于FrameLayout 可能用最大的字View的大小同波,對于LinearLayout鳄梅,可能是高度的累加,
//具體測量的原理去看看源碼未檩〈魇總的來說,父View是等所有的子View測量結(jié)束之后冤狡,再來測量自己孙蒙。
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),        
resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
....
}  

到目前為止项棠,基本把Measure 主要原理都過了一遍,接下來我們會結(jié)合實例來講解整個match的過程马篮,首先看下面的代碼:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"    
   android:id="@+id/linear"
   android:layout_width="match_parent"    
   android:layout_height="wrap_content"    
   android:layout_marginTop="50dp"    
   android:background="@android:color/holo_blue_dark"    
   android:paddingBottom="70dp"    
   android:orientation="vertical">    
   <TextView        
    android:id="@+id/text"       
    android:layout_width="match_parent"     
    android:layout_height="wrap_content"  
    android:background="@color/material_blue_grey_800"       
    android:text="TextView"        
    android:textColor="@android:color/white"        
    android:textSize="20sp" />    
   <View       
      android:id="@+id/view"       
     android:layout_width="match_parent" 
     android:layout_height="150dp"    
     android:background="@android:color/holo_green_dark" />
</LinearLayout>

上面的代碼對于出來的布局是下面的一張圖

對于上面圖可能有些不懂沾乘,這邊做下說明:

整個圖是一個DecorView,DecorView可以理解成整個頁面的根View,DecorView是一個FrameLayout,包含兩個子View,一個id=statusBarBackground的View和一個是LineaLayout浑测,id=statusBarBackground的View翅阵,我們可以先不管(我也不是特別懂這個View,應(yīng)該就是statusBar的設(shè)置背景的一個控件,方便設(shè)置statusBar的背景)迁央,而這個LinearLayout比較重要掷匠,它包含一個title和一個content,title很好理解其實就是TitleBar或者ActionBar,content 就更簡單了岖圈,setContentView()方法你應(yīng)該用過吧讹语,android.R.id.content 你應(yīng)該聽過吧,沒錯就是它,content是一個FrameLayout蜂科,你寫的頁面布局通過setContentView加進(jìn)來就成了content的直接子View顽决。

整個View的布局圖如下:


這張圖在下面分析measure,會經(jīng)常用到导匣,主要用于了解遞歸的時候view 的measure順序

注:
1才菠、 header的是個ViewStub,用來惰性加載ActionBar,為了便于分析整個測量過程贡定,我把Theme設(shè)成NoActionBar赋访,避免ActionBar 相關(guān)的measure干擾整個過程,這樣可以忽略掉ActionBar 的測量缓待,在調(diào)試代碼更清晰蚓耽。
2、包含Header(ActionBar)和id/content 的那個父View旋炒,我不知道叫什么名字好步悠,我們姑且叫它ViewRoot(看上圖),它是垂直的LinearLayout,放著整個頁面除statusBar 的之外所有的東西瘫镇,叫它ViewRoot 應(yīng)該還ok贤徒,一個代號而已。

既然我們知道整個View的Root是DecorView汇四,那么View的繪制是從哪里開始的呢,我們知道每個Activity 均會創(chuàng)建一個 PhoneWindow對象踢涌,是Activity和整個View系統(tǒng)交互的接口通孽,每個Window都對應(yīng)著一個View和一個ViewRootImpl,Window和View通過ViewRootImpl來建立聯(lián)系,對于Activity來說睁壁,ViewRootImpl是連接WindowManager和DecorView的紐帶,繪制的入口是由ViewRootImpl的performTraversals方法來發(fā)起Measure背苦,Layout互捌,Draw等流程的。

我們來看下ViewRootImpl的performTraversals 方法:

private void performTraversals() { 
...... 
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); 
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); 
...... 
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); 
......
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
...... 
mView.draw(canvas); 
......
}

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; 
   ...... 
  } 
 return measureSpec; 
}

performTraversals 中我們看到的mView其實就是DecorView,View的繪制從DecorView開始行剂, 在mView.measure()的時候調(diào)用getRootMeasureSpec獲得兩個MeasureSpec做為參數(shù)秕噪,getRootMeasureSpec的兩個參數(shù)(mWidth, lp.width)mWith和mHeight 是屏幕的寬度和高度, lp是WindowManager.LayoutParams厚宰,它的lp.width和lp.height的默認(rèn)值是MATCH_PARENT,所以通過getRootMeasureSpec 生成的測量規(guī)格MeasureSpec 的mode是MATCH_PARENT 腌巾,size是屏幕的高寬。
因為DecorView 是一個FrameLayout 那么接下來會進(jìn)入FrameLayout 的measure方法铲觉,measure的兩個參數(shù)就是剛才getRootMeasureSpec的生成的兩個MeasureSpec澈蝙,DecorView的測量開始了。
首先是DecorView 的 MeasureSpec 撵幽,根據(jù)上面的分析DecorView 的 MeasureSpec是Windows傳過來的灯荧,我們畫出DecorView 的MeasureSpec 圖:


注:
1、-1 代表的是EXACTLY盐杂,-2 是AT_MOST
2逗载、由于屏幕的像素是1440x2560,所以DecorView 的MeasureSpec的size 對應(yīng)于這兩個值

那么接下來在FrameLayout 的onMeasure()方法DecorView開始for循環(huán)測量自己的子View,測量完所有的子View再來測量自己,由下圖可知链烈,接下來要測量ViewRoot的大小


//FrameLayout 的測量
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
....
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {    
   final View child = getChildAt(i);    
   if (mMeasureAllChildren || child.getVisibility() != GONE) {   
    // 遍歷自己的子View厉斟,只要不是GONE的都會參與測量,measureChildWithMargins方法在最上面
    // 的源碼已經(jīng)講過了测垛,如果忘了回頭去看看捏膨,基本思想就是父View把自己當(dāng)MeasureSpec 
    // 傳給子View結(jié)合子View自己的LayoutParams 算出子View 的MeasureSpec,然后繼續(xù)往下穿食侮,
    // 傳遞葉子節(jié)點(diǎn)号涯,葉子節(jié)點(diǎn)沒有子View,只要負(fù)責(zé)測量自己就好了锯七。
     measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);      
     ....
     ....
   }
}
....
}  

DecorView 測量ViewRoot 的時候把自己的widthMeasureSpec和heightMeasureSpec傳進(jìn)去了链快,接下來你就要去看measureChildWithMargins的源碼了

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

ViewRoot 是系統(tǒng)的View,它的LayoutParams默認(rèn)都是match_parent,根據(jù)我們文章最開始MeasureSpec 的計算規(guī)則眉尸,ViewRoot 的MeasureSpec mode應(yīng)該等于EXACTLY(DecorView MeasureSpec 的mode是EXACTLY域蜗,ViewRoot的layoutparams 是match_parent),size 也等于DecorView的size噪猾,所以ViewRoot的MeasureSpec圖如下:


算出ViewRoot的MeasureSpec 之后霉祸,開始調(diào)用ViewRoot.measure 方法去測量ViewRoot的大小,然而ViewRoot是一個LinearLayout 袱蜡,ViewRoot.measure最終會執(zhí)行的LinearLayout 的onMeasure 方法丝蹭,LinearLayout 的onMeasure 方法又開始逐個測量它的子View,上面的measureChildWithMargins方法又會被調(diào)用坪蚁,那么根據(jù)View的層級圖奔穿,接下來測量的是header(ViewStub),由于header的Gone镜沽,所以直接跳過不做測量工作,所以接下來輪到ViewRoot的第二個child content(android.R.id.content),我們要算出這個content 的MeasureSpec贱田,所以又要拿ViewRoot 的MeasureSpec 和 android.R.id.content的LayoutParams 做計算了缅茉,計算過程就是調(diào)用getChildMeasureSpec的方法,


protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { 
   .....
   final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,           
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin  + heightUsed, lp.height);  
   ....
}

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  
    int specMode = MeasureSpec.getMode(spec);  //獲得父View的mode  
    int specSize = MeasureSpec.getSize(spec);  //獲得父View的大小  
  
    int size = Math.max(0, specSize - padding); //父View的大小-自己的Padding+子View的Margin,得到值才是子View可能的最大值。  
     .....
}

由上面的代碼
int size = Math.max(0, specSize - padding);
padding=mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed
算出android.R.id.content 的MeasureSpec 的size
由于ViewRoot 的mPaddingBottom=100px(這個可能和狀態(tài)欄的高度有關(guān)右蒲,我們測量的最后會發(fā)現(xiàn)id/statusBarBackground的View的高度剛好等于100px恬口,ViewRoot 是系統(tǒng)的View的它的Padding 我們沒法改變,所以計算出來Content(android.R.id.content) 的MeasureSpec 的高度少了100px ,它的寬高的mode 根據(jù)算出來也是EXACTLY(ViewRoot 是EXACTLY和android.R.id.content 是match_parent)。所以Content(android.R.id.content)的MeasureSpec 如下(高度少了100px):

Paste_Image.png

Content(android.R.id.content) 是FrameLayout,遞歸調(diào)用開始準(zhǔn)備計算id/linear的MeasureSpec蔬蕊,我們先給出結(jié)果:

圖中有兩個要注意的地方:
1、id/linear的heightMeasureSpec 的mode=AT_MOST哥谷,因為id/linear 的LayoutParams 的layout_height="wrap_content"
2岸夯、id/linear的heightMeasureSpec 的size 少了200px, 由上面的代碼
padding=mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed;
int size = Math.max(0, specSize - padding);
由于id/linear 的 android:layout_marginTop="50dp" 使得lp.topMargin=200px (本設(shè)備的density=4,px=4*pd)们妥,在計算后id/linear的heightMeasureSpec 的size 少了200px猜扮。(布局代碼前面已給出,可自行查看id/linear 控件xml中設(shè)置的屬性)

linear.measure接著往下算linear的子View的的MeasureSpec监婶,看下View 層級圖旅赢,往下走應(yīng)該是id/text,接下來是計算id/text的MeasureSpec,直接看圖惑惶,mode=AT_MOST ,size 少了280煮盼,別問我為什么 ...specSize - padding.

算出id/text 的MeasureSpec 后,接下來text.measure(childWidthMeasureSpec, childHeightMeasureSpec);準(zhǔn)備測量id/text 的高寬带污,這時候已經(jīng)到底了僵控,id/text是TextView,已經(jīng)沒有子類了鱼冀,這時候跳到TextView的onMeasure方法了报破。TextView 拿著剛才計算出來的heightMeasureSpec(mode=AT_MOST,size=1980),這個就是對TextView的高度和寬度的約束,進(jìn)到TextView 的onMeasure(widthMeasureSpec,heightMeasureSpec) 方法千绪,在onMeasure 方法執(zhí)行調(diào)試過程中充易,我們發(fā)現(xiàn)下面的代碼:

Paste_Image.png

TextView字符的高度(也就是TextView的content高度[wrap_content])測出來=107px,107px 并沒有超過1980px(允許的最大高度)荸型,所以實際測量出來TextView的高度是107px蔽氨。
最終算出id/text 的mMeasureWidth=1440px,mMeasureHeight=107px。

貼一下布局代碼,免得你忘了具體布局鹉究。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"    
   android:id="@+id/linear"
   android:layout_width="match_parent"    
   android:layout_height="wrap_content"    
   android:layout_marginTop="50dp"    
   android:background="@android:color/holo_blue_dark"    
   android:paddingBottom="70dp"    
   android:orientation="vertical">    
   <TextView        
    android:id="@+id/text"       
    android:layout_width="match_parent"     
    android:layout_height="wrap_content"  
    android:background="@color/material_blue_grey_800"       
    android:text="TextView"        
    android:textColor="@android:color/white"        
    android:textSize="20sp" />    
   <View       
      android:id="@+id/view"       
     android:layout_width="match_parent" 
     android:layout_height="150dp"    
     android:background="@android:color/holo_green_dark" />
</LinearLayout>

TextView的高度已經(jīng)測量出來了,接下來測量id/linear的第二個child(id/view)踪宠,同樣的原理測出id/view的MeasureSpec.

Paste_Image.png

id/view的MeasureSpec 計算出來后自赔,調(diào)用view.measure(childWidthMeasureSpec, childHeightMeasureSpec)的測量id/view的高寬,之前已經(jīng)說過View measure的默認(rèn)實現(xiàn)是

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

最終算出id/view的mMeasureWidth=1440px,mMeasureHeight=600px柳琢。

id/linear 的子View的高度都計算完畢了绍妨,接下來id/linear就通過所有子View的測量結(jié)果計算自己的高寬,id/linear是LinearLayout柬脸,所有它的高度計算簡單理解就是子View的高度的累積+自己的Padding.

最終算出id/linear的mMeasureWidth=1440px,mMeasureHeight=987px他去。

最終算出id/linear出來后,id/content 就要根據(jù)它唯一的子View id/linear 的測量結(jié)果和自己的之前算出的MeasureSpec一起來測量自己的結(jié)果倒堕,具體計算的邏輯去看FrameLayout onMeasure 函數(shù)的計算過程灾测。以此類推,接下來測量ViewRoot,然后再測量id/statusBarBackground,雖然不知道id/statusBarBackground 是什么垦巴,但是調(diào)試的過程中媳搪,測出的它的高度=100px, 和 id/content 的paddingTop 剛好相等。在最后測量DecorView 的高寬骤宣,最終整個測量過程結(jié)束秦爆。所有的View的大小測量完畢。所有的getMeasureWidth 和 getMeasureWidth 都已經(jīng)有值了憔披。Measure 分析到此為止等限,如有不懂,評論留言(簡書:kelin)

layout過程#


mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); 
......
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());

performTraversals 方法執(zhí)行完mView.measure 計算出mMeasuredXXX后就開始執(zhí)行l(wèi)ayout 函數(shù)來確定View具體放在哪個位置芬膝,我們計算出來的View目前只知道view矩陣的大小望门,具體這個矩陣放在哪里,這就是layout 的工作了蔗候。layout的主要作用 :根據(jù)子視圖的大小以及布局參數(shù)將View樹放到合適的位置上怒允。

既然是通過mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight()); 那我們來看下layout 函數(shù)做了什么,mView肯定是個ViewGroup锈遥,不會是View,我們直接看下ViewGroup 的layout函數(shù)

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

代碼可以看個大概纫事,LayoutTransition是用于處理ViewGroup增加和刪除子視圖的動畫效果,也就是說如果當(dāng)前ViewGroup未添加LayoutTransition動畫所灸,或者LayoutTransition動畫此刻并未運(yùn)行丽惶,那么調(diào)用super.layout(l, t, r, b),繼而調(diào)用到ViewGroup中的onLayout爬立,否則將mLayoutSuppressed設(shè)置為true钾唬,等待動畫完成時再調(diào)用requestLayout()。
這個函數(shù)是final 不能重寫,所以ViewGroup的子類都會調(diào)用這個函數(shù)抡秆,layout 的具體實現(xiàn)是在super.layout(l, t, r, b)里面做的奕巍,那么我接下來看一下View類的layout函數(shù)

 public final void layout(int l, int t, int r, int b) {
       .....
      //設(shè)置View位于父視圖的坐標(biāo)軸
       boolean changed = setFrame(l, t, r, b); 
       //判斷View的位置是否發(fā)生過變化,看有必要進(jìn)行重新layout嗎
       if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
           if (ViewDebug.TRACE_HIERARCHY) {
               ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
           }
           //調(diào)用onLayout(changed, l, t, r, b); 函數(shù)
           onLayout(changed, l, t, r, b);
           mPrivateFlags &= ~LAYOUT_REQUIRED;
       }
       mPrivateFlags &= ~FORCE_LAYOUT;
       .....
   }

1儒士、setFrame(l, t, r, b) 可以理解為給mLeft 的止、mTop、mRight着撩、mBottom賦值诅福,然后基本就能確定View自己在父視圖的位置了,這幾個值構(gòu)成的矩形區(qū)域就是該View顯示的位置拖叙,這里的具體位置都是相對與父視圖的位置氓润。

2、回調(diào)onLayout薯鳍,對于View來說咖气,onLayout只是一個空實現(xiàn),一般情況下我們也不需要重載該函數(shù),:

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {  

    }  

對于ViewGroup 來說辐啄,唯一的差別就是ViewGroup中多了關(guān)鍵字abstract的修飾采章,要求其子類必須重載onLayout函數(shù)。

@Override  
protected abstract void onLayout(boolean changed,  
        int l, int t, int r, int b); 

而重載onLayout的目的就是安排其children在父視圖的具體位置壶辜,那么如何安排子View的具體位置呢悯舟?

 int childCount = getChildCount() ; 
  for(int i=0 ;i<childCount ;i++){
       View child = getChildAt(i) ;
       //整個layout()過程就是個遞歸過程
       child.layout(l, t, r, b) ;
    }

代碼很簡單,就是遍歷自己的孩子砸民,然后調(diào)用 child.layout(l, t, r, b) 抵怎,給子view 通過setFrame(l, t, r, b) 確定位置,而重點(diǎn)是(l, t, r, b) 怎么計算出來的呢岭参。還記得我們之前測量過程反惕,測量出來的MeasuredWidth和MeasuredHeight嗎?還記得你在xml 設(shè)置的Gravity嗎演侯?還有RelativeLayout 的其他參數(shù)嗎姿染,沒錯,就是這些參數(shù)和MeasuredHeight秒际、MeasuredWidth 一起來確定子View在父視圖的具體位置的悬赏。具體的計算過程大家可以看下最簡單FrameLayout 的onLayout 函數(shù)的源碼,每個不同的ViewGroup 的實現(xiàn)都不一樣娄徊,這邊不做具體分析了吧闽颇。

3、MeasuredWidth和MeasuredHeight這兩個參數(shù)為layout過程提供了一個很重要的依據(jù)(如果不知道View的大小寄锐,你怎么固定四個點(diǎn)的位置呢)兵多,但是這兩個參數(shù)也不是必須的尖啡,layout過程中的4個參數(shù)l, t, r, b完全可以由我們?nèi)我庵付ǎ鳹iew的最終的布局位置和大惺1臁(mRight - mLeft=實際寬或者mBottom-mTop=實際高)完全由這4個參數(shù)決定衅斩,measure過程得到的mMeasuredWidth和mMeasuredHeight提供了視圖大小測量的值,但我們完全可以不使用這兩個值援雇,所以measure過程并不是必須的矛渴。如果我們不使用這兩個值,那么getMeasuredWidth() 和getWidth() 就很有可能不是同一個值惫搏,它們的計算是不一樣的:

public final int getMeasuredWidth() {  
        return mMeasuredWidth & MEASURED_SIZE_MASK;  
    }  
public final int getWidth() {  
        return mRight - mLeft;  
    }

layout 過程相對簡單些,分析就到此為止蚕涤。

draw過程#


performTraversals 方法的下一步就是mView.draw(canvas); 因為View的draw 方法一般不去重寫筐赔,官網(wǎng)文檔也建議不要去重寫draw 方法,所以下一步執(zhí)行就是View.java的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);
    }

注釋寫得比較清楚茴丰,一共分成6步,看到注釋沒有( // skip step 2 & 5 if possible (common case))除了2 和 5之外 我們一步一步來看:
1天吓、第一步:背景繪制
看注釋即可贿肩,不是重點(diǎn)

private void drawBackground(Canvas canvas) { 
     Drawable final Drawable background = mBackground; 
      ...... 
     //mRight - mLeft, mBottom - mTop layout確定的四個點(diǎn)來設(shè)置背景的繪制區(qū)域 
     if (mBackgroundSizeChanged) { 
        background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);   
        mBackgroundSizeChanged = false; rebuildOutline(); 
     } 
     ...... 
     //調(diào)用Drawable的draw() 把背景圖片畫到畫布上
     background.draw(canvas); 
     ...... 
}

2、第三步龄寞,對View的內(nèi)容進(jìn)行繪制汰规。
onDraw(canvas) 方法是view用來draw 自己的,具體如何繪制物邑,顏色線條什么樣式就需要子View自己去實現(xiàn)溜哮,View.java 的onDraw(canvas) 是空實現(xiàn),ViewGroup 也沒有實現(xiàn)色解,每個View的內(nèi)容是各不相同的茂嗓,所以需要由子類去實現(xiàn)具體邏輯。

3科阎、第4步 對當(dāng)前View的所有子View進(jìn)行繪制
dispatchDraw(canvas) 方法是用來繪制子View的述吸,View.java 的dispatchDraw()方法是一個空方法,因為View沒有子View,不需要實現(xiàn)dispatchDraw ()方法,ViewGroup就不一樣了锣笨,它實現(xiàn)了dispatchDraw ()方法:

@Override
 protected void dispatchDraw(Canvas canvas) {
       ...
        if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
            for (int i = 0; i < count; i++) {
                final View child = children[i];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
        } else {
            for (int i = 0; i < count; i++) {
                final View child = children[getChildDrawingOrder(count, i)];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
        }
      ......
    }

代碼一眼看出蝌矛,就是遍歷子View然后drawChild(),drawChild()方法實際調(diào)用的是子View.draw()方法,ViewGroup類已經(jīng)為我們實現(xiàn)繪制子View的默認(rèn)過程,這個實現(xiàn)基本能滿足大部分需求票唆,所以ViewGroup類的子類(LinearLayout,FrameLayout)也基本沒有去重寫dispatchDraw方法朴读,我們在實現(xiàn)自定義控件,除非比較特別走趋,不然一般也不需要去重寫它衅金, drawChild()的核心過程就是為子視圖分配合適的cavas剪切區(qū),剪切區(qū)的大小正是由layout過程決定的,而剪切區(qū)的位置取決于滾動值以及子視圖當(dāng)前的動畫氮唯。設(shè)置完剪切區(qū)后就會調(diào)用子視圖的draw()函數(shù)進(jìn)行具體的繪制了鉴吹。

4、第6步 對View的滾動條進(jìn)行繪制
不是重點(diǎn)惩琉,知道有這東西就行豆励,onDrawScrollBars 的一句注釋 :Request the drawing of the horizontal and the vertical scrollbar. The scrollbars are painted only if they have been awakened first.

一張圖看下整個draw的遞歸流程。


到此整個繪制過程基本講述完畢了瞒渠。

注:【轉(zhuǎn)載請注明良蒸,問題可提問,喜歡可打賞伍玖,博客持續(xù)更新嫩痰,歡迎關(guān)注 簡書:kelin】

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市窍箍,隨后出現(xiàn)的幾起案子串纺,更是在濱河造成了極大的恐慌,老刑警劉巖椰棘,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纺棺,死亡現(xiàn)場離奇詭異,居然都是意外死亡邪狞,警方通過查閱死者的電腦和手機(jī)祷蝌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來外恕,“玉大人杆逗,你說我怎么就攤上這事×燮#” “怎么了罪郊?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長尚洽。 經(jīng)常有香客問我悔橄,道長,這世上最難降的妖魔是什么腺毫? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任癣疟,我火速辦了婚禮,結(jié)果婚禮上潮酒,老公的妹妹穿的比我還像新娘睛挚。我一直安慰自己,他們只是感情好急黎,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布扎狱。 她就那樣靜靜地躺著侧到,像睡著了一般。 火紅的嫁衣襯著肌膚如雪淤击。 梳的紋絲不亂的頭發(fā)上匠抗,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機(jī)與錄音污抬,去河邊找鬼汞贸。 笑死,一個胖子當(dāng)著我的面吹牛印机,可吹牛的內(nèi)容都是我干的矢腻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼射赛,長吁一口氣:“原來是場噩夢啊……” “哼踏堡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起咒劲,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎诫隅,沒想到半個月后腐魂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡逐纬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年蛔屹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片豁生。...
    茶點(diǎn)故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡兔毒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出甸箱,到底是詐尸還是另有隱情育叁,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布芍殖,位于F島的核電站豪嗽,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏豌骏。R本人自食惡果不足惜龟梦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望窃躲。 院中可真熱鬧计贰,春花似錦、人聲如沸蒂窒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至樱溉,卻和暖如春挣输,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背福贞。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工撩嚼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人挖帘。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓完丽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親拇舀。 傳聞我的和親對象是個殘疾皇子逻族,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評論 2 355

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