【Android】View繪制流程

1 概述

圖1.1 Activity繪制過程

對上圖做出簡單解釋:DecorView是一個應用窗口的根容器,它本質上是一個FrameLayout。DecorView有唯一一個子View,它是一個垂直LinearLayout隅肥,包含兩個子元素,一個是TitleView(ActionBar的容器)袄简,另一個是ContentView(窗口內容的容器)腥放。關于ContentView,它是一個FrameLayout(android.R.id.content)痘番,我們平常用的setContentView就是設置它的子View捉片。上圖還表達了每個Activity都與一個Window(具體來說是PhoneWindow)相關聯(lián),用戶界面則由Window所承載汞舱。

2 view繪制流程

2.1 view繪制的三個階段

  • measure: 判斷是否需要重新計算View的大小伍纫,需要的話則計算;
  • layout: 判斷是否需要重新計算View的位置昂芜,需要的話則計算莹规;
  • draw: 判斷是否需要重新繪制View,需要的話則重繪制泌神。

這三個子階段可以用下圖來描述:


圖2.1 View的繪制流程

2.2 measure階段

此階段的目的是計算出控件樹中各個控件顯示所需要的尺寸良漱。
measure方法是為了確定View的大小舞虱,父容器會提供寬度和高度參數(shù)的約束信息。該方法是一個final類型的方法母市,意味著子類不能重寫它矾兜。真正的測量工作是在View的onMeasure方法中進行,因此關注onMeasure方法的實現(xiàn)即可患久。

2.2.1測量參數(shù)

  • MeasureSpec.UNSPECIFIED:父容器不對View有任何限制椅寺,要多大給多大。這種情況一般用于系統(tǒng)內部蒋失,表示一種測量的狀態(tài)返帕。
  • MeasureSpec.EXACTLY:父容器已經(jīng)為子容器設置了尺寸,子容器應當服從這些邊界,不論子容器想要多大的空間。它對應于LayoutParams中的match_parent和具體數(shù)值這兩種模式
  • MeasureSpec.AT_MOST:父容器指定了一個可用大小篙挽,View的大小不能超過這個值荆萤。它對應于LayoutParams中的wrap_content。

父View的measure的過程會先測量子View铣卡,等子View測量結果出來后链韭,再來測量自己,上面的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內部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構建MeasureSpec對象硼莽。 
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode); 
}

如果父View的MeasureSpec 是EXACTLY庶溶,說明父View的大小是確切的,(確切的意思很好理解懂鸵,如果一個View的MeasureSpec 是EXACTLY偏螺,那么它的size 是多大,最后展示到屏幕就一定是那么大)

  • 如果子View 的layout_xxxx是MATCH_PARENT匆光,父View的大小是確切套像,子View的大小又MATCH_PARENT(充滿整個父View),那么子View的大小肯定是確切的终息,而且大小值就是父View的size夺巩。所以子View的size=父View的size,mode=EXACTLY
  • 如果子View 的layout_xxxx是WRAP_CONTENT周崭,也就是子View的大小是根據(jù)自己的content 來決定的柳譬,但是子View的畢竟是子View,大小不能超過父View的大小休傍,但是子View的是WRAP_CONTENT征绎,我們還不知道具體子View的大小是多少,要等到child.measure(childWidthMeasureSpec, childHeightMeasureSpec) 調用的時候才去真正測量子View 自己content的大小(比如TextView wrap_content 的時候你要測量TextView content 的大小人柿,也就是字符占用的大小柴墩,這個測量就是在child.measure(childWidthMeasureSpec, childHeightMeasureSpec)的時候,才能測出字符的大小凫岖,MeasureSpec 的意思就是假設你字符100px江咳,但是MeasureSpec 要求最大的只能50px,這時候就要截掉了)哥放。通過上述描述歼指,子View MeasureSpec mode的應該是AT_MOST,而size 暫定父View的 size甥雕,表示的意思就是子View的大小沒有不確切的值踩身,子View的大小最大為父View的大小,不能超過父View的大猩缏丁(這就是AT_MOST 的意思)挟阻,然后這個MeasureSpec 做為子View measure方法 的參數(shù),做為子View的大小的約束或者說是要求峭弟,有了這個MeasureSpec子View再實現(xiàn)自己的測量附鸽。
  • 如果如果子View 的layout_xxxx是確定的值(200dp),那么就更簡單了瞒瘸,不管你父View的mode和size是什么坷备,我都寫死了就是200dp,那么控件最后展示就是就是200dp情臭,不管我的父View有多大省撑,也不管我自己的content 有多大,反正我就是這么大谎柄,所以這種情況MeasureSpec 的mode = EXACTLY 大小size=你在layout_xxxx 填的那個值

如果父View的MeasureSpec 是AT_MOST丁侄,說明父View的大小是不確定,最大的大小是MeasureSpec 的size值朝巫,不能超過這個值鸿摇。

  • 如果子View 的layout_xxxx是MATCH_PARENT,父View的大小是不確定(只知道最大只能多大)劈猿,子View的大小MATCH_PARENT(充滿整個父View)拙吉,那么子View你即使充滿父容器,你的大小也是不確定的揪荣,父View自己都確定不了自己的大小筷黔,你MATCH_PARENT你的大小肯定也不能確定的,所以子View的mode=AT_MOST仗颈,size=父View的size佛舱,也就是你在布局雖然寫的是MATCH_PARENT椎例,但是由于你的父容器自己的大小不確定,導致子View的大小也不確定请祖,只知道最大就是父View的大小订歪。
  • 如果子View 的layout_xxxx是WRAP_CONTENT,父View的大小是不確定(只知道最大只能多大)肆捕,子View又是WRAP_CONTENT刷晋,那么在子View的Content沒算出大小之前,子View的大小最大就是父View的大小慎陵,所以子View MeasureSpec mode的就是AT_MOST眼虱,而size 暫定父View的 size。
  • 如果如果子View 的layout_xxxx是確定的值(200dp)席纽,同上捏悬,寫多少就是多少,改變不了的润梯。

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

  • 如果子View 的layout_xxxx是MATCH_PARENT矫渔,因為父View的MeasureSpec是UNSPECIFIED彤蔽,父View自己的大小并沒有任何約束和要求,那么對于子View來說無論是WRAP_CONTENT還是MATCH_PARENT庙洼,子View也是沒有任何束縛的顿痪,想多大就多大,沒有不能超過多少的要求油够,一旦沒有任何要求和約束蚁袭,size的值就沒有任何意義了,所以一般都直接設置成0
  • 同上
  • 如果如果子View 的layout_xxxx是確定的值(200dp)石咬,同上揩悄,寫多少就是多少,改變不了的(記住鬼悠,只有設置的確切的值删性,那么無論怎么測量,大小都是不變的焕窝,都是你寫的那個值)

2.2.2 測量過程

View的測量過程主要是在onMeasure()方法中蹬挺,measure方法是final,所以這個方法不可重寫它掂,如果想自定義View的測量巴帮,應該去重寫onMeasure()方法。在onMeasure方法的最后需要調用setMeasuredDimension方法,不然會拋異常

基本思想:父View把自己的MeasureSpec榕茧,傳給子View結合子View自己的LayoutParams 算出子View 的MeasureSpec垃沦,然后繼續(xù)往下傳,傳遞葉子節(jié)點雪猪,葉子節(jié)點沒有子View栏尚,根據(jù)傳下來的這個MeasureSpec測量自己就好了。所有的孩子測量之后只恨,經(jīng)過一系列的計算之后通過setMeasuredDimension設置自己的寬高译仗,對于FrameLayout 可能用最大的子View的大小,對于LinearLayout官觅,可能是高度的累加纵菌,具體測量的原理去看看源碼⌒莸樱總的來說咱圆,父View是等所有的子View測量結束之后,再來測量自己功氨。通過setMeasuredDimension進行測量序苏。


圖2.2 DecorView結構圖

3 layout過程

Layout的作用是ViewGroup用來確定子元素的位置,當ViewGroup的位置被確定后捷凄,它在onLayout中會遍歷所有子元素并調用其layout方法忱详,在layout方法中又會調用onLayout方法。
layout方法的大致流程:首先會通過setFrame方法來設定View的四個頂點的位置跺涤,即初始化mLeft匈睁、mRight、mTop和mBottom桶错,View的四個頂點一旦確定航唆,那么View在父容器中的位置也就確定了;接著會調用onLayout方法院刁,這個方法的用途是父容器確定子元素的位置糯钙,因為它的具體實現(xiàn)和具體的布局有關,所以View和ViewGroup都沒有真正實現(xiàn)它黎比,而是由子類重寫超营。
注意:ViewGroup的layout方法是final類型的,它會調用onLayout方法(ViewGroup中是抽象方法阅虫,子類必須重寫)演闭,所以子類根據(jù)布局需要重寫onLayout方法。View的layout方法是public類型的颓帝,但是最終的位置確定是在onLayout方法(View中是public的空方法)中進行的米碰,所以重寫onLayout方法即可窝革。

4 draw過程

draw的繪制過程主要分為6步

  • 繪制背景
  • 如有必要,保存畫布的圖層以準備淡化
  • 對view內容進行繪制
  • 對當前view的所有子view進行繪制
  • 如有必要吕座,繪制淡化邊緣并恢復圖層
  • 對view的滾動條進行繪制

接下來看View的一個方法:

/**
* If this view doesn't do any drawing on its own, set this flag to
* allow further optimizations. By default, this flag is not set on
* View, but could be set on some View subclasses such as ViewGroup.
*
* Typically, if you override {@link #onDraw(android.graphics.Canvas)}
* you should clear this flag.
*
* @param willNotDraw whether or not this View draw on its own
*/
public void setWillNotDraw(boolean willNotDraw) {
    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}

從setWillNotDraw這個方法的注釋可看出虐译,如果一個View不需要繪制任何內容,那么設置這個標記為為true以后吴趴,系統(tǒng)會進行相應的優(yōu)化漆诽。默認情況下,View沒有起用這個優(yōu)化標記位锣枝,但是ViewGroup會默認啟用厢拭。這個標記位對實際開發(fā)的意義是:當我們的自定義控件繼承于ViewGroup并且本身不具備繪制功能時,就可以開始這個標記位便于系統(tǒng)進行優(yōu)化撇叁。如果明確知道一個ViewGroup需要通過onDraw來繪制內容供鸠,就需要顯式的關閉WILL_NOT_DRAW 這個標記位。

相關問答

  1. 在Activity啟動的時候獲取某個View的寬高
    答:在onCreate陨闹、onStart楞捂、onResume中均無法獲取到某個View的正確寬高,因為View的measure過程和Activity的生命周期方法并不是同步執(zhí)行的趋厉,無法保證Activity執(zhí)行了onCreate寨闹、onStart、onResume時某個View已經(jīng)測量完畢君账,獲取到的寬高可能為0鼻忠。可以通過下列四種方法來解決這個問題:
  • Activity重寫onWindowFocusChanged
    onWindowFocusChanged這個方法的含義是:View已經(jīng)初始化完畢杈绸,寬高已經(jīng)準備好了,所以這時候獲取的寬高是沒問題的矮瘟。這個方法會被調用多次收苏,當Activity窗口得到焦點和失去焦點的時候都會被調用吓著。
  • view.post(runnable)
    通過post可以講一個runnable投遞到消息隊列的尾部,然后等待Looper調用此runnable的時候,View已經(jīng)初始化好了园细。
  • ViewTreeObserver
    使用ViewTreeObserver的眾多回調可以完成這個功能,比如OnGlobalLayoutListener接口:當View樹的狀態(tài)發(fā)生改變或者View樹內部的View的可見性發(fā)生改變時号阿,onGlobalLayout方法將會被回調斟珊,這是獲取View寬高的一個好時機。注意該方法會被調用多次拳球。
  • view.measure(int widthMeasureSpec, int heightMeasureSpec)
    通過手動對View進行measure來得到View的寬高审姓,個人不推薦該方法,有局限性祝峻,而且容易出錯魔吐,就不介紹了扎筒。
public class TestActivity extends Activity {

    @BindView(R.id.btn)
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        ButterKnife.bind(this);

        textView.post(new Runnable() {
            @Override
            public void run() {
                int width = textView.getMeasuredWidth();
                int height = textView.getMeasuredHeight();
                Log.d("==========", "onCreate view.post  width = " + width + " ; height = " + height);
            }
        });

        ViewTreeObserver viewTreeObserver = textView.getViewTreeObserver();
        viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                //特別注意:此處不能直接使用viewTreeObserver.removeGlobalOnLayoutListener(this); 會有如下異常:
                //java.lang.IllegalStateException: This ViewTreeObserver is not alive, call getViewTreeObserver() again
                textView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                int width = textView.getMeasuredWidth();
                int height = textView.getMeasuredHeight();
                Log.d("==========", "onCreate viewTreeObserver.addOnGlobalLayoutListener  width = " + width + " ; height = " + height);
            }
        });
    }

    @Override
    protected void onStart() {
        super.onStart();
        textView.post(new Runnable() {
            @Override
            public void run() {
                int width = textView.getMeasuredWidth();
                int height = textView.getMeasuredHeight();
                Log.d("==========", "onStart view.post  width = " + width + " ; height = " + height);
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        int width = textView.getMeasuredWidth();
        int height = textView.getMeasuredHeight();
        Log.d("==========", "onResume  width = " + width + " ; height = " + height);
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if(hasFocus) {
            int width = textView.getMeasuredWidth();
            int height = textView.getMeasuredHeight();
            Log.d("==========", "onWindowFocusChanged  width = " + width + " ; height = " + height);
        }
    }
}

//測試結果:
//onResume  width = 0 ; height = 0
//onCreate viewTreeObserver.addOnGlobalLayoutListener  width = 391 ; height = 57
//onCreate view.post  width = 391 ; height = 57
//onStart view.post  width = 391 ; height = 57
//onWindowFocusChanged  width = 391 ; height = 57

  1. Activity顯示流程
    ViewRoot對應于ViewRootImpl類,它是連接WindowManager和DecorView的紐帶酬姆,View的三大流程均是通過viewRoot來完成的嗜桌。在ActivityThread中,當Activity對象創(chuàng)建完畢后辞色,會將decorView添加到window中骨宠,同時會創(chuàng)建ViewRootImpl對象viewRoot,并將viewRoot和decorView建立關聯(lián)相满。
    View的繪制流程從viewRoot的performTraversals方法開始的层亿。
  2. View的測量寬高和顯示的最終寬高區(qū)別
    答:測量寬高是在measure過程中確定的,為onMeasure方法中通過setMeasuredDimension設置的值雳灵;最終寬高是在layout過程中確定的棕所,寬為mRight - mLeft,高為mBottom - mTop悯辙。
    正常情況下琳省,它兩是相等的,某些特殊情況會不一致躲撰,比如:
//重寫View的layout方法
public void layout(int l, int t, int r, int b) {
    //最終寬高會比測量寬高大100px
    super.layout(l, t, r + 100, b + 100);
}
  1. MeasureSpec的賦值原理
    答:MeasureSpec代表一個32位的int值针贬,高2位代表SpecMode,低30位代表SpecSize拢蛋。
    DecorView:MeasureSpec由窗口的尺寸和自身的LayoutParams共同決定桦他;
    普通View:MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同決定
  2. 當一個TextView的實例調用setText()方法后執(zhí)行了什么
    答:setText最后會調用requestLayout()和invalidate()。requestLayout()會調用父容器的requestLayout方法谆棱,直至頂層View快压。requestLayout()會調用measure過程和layout過程,invalidate()會調用draw過程垃瞧。
  3. 幾個常用方法介紹
    requestLayout():調用measure過程和layout過程蔫劣;
    invalidate():必須要在UI線程調用;View可見的話个从,會調用onDraw方法脉幢;
    postInvalidate():可以在非UI線程調用,通過handler發(fā)送一個MSG_INVALIDATE消息嗦锐,然后在主線程處理消息嫌松,執(zhí)行invalidate()方法。

參考文章

https://www.cnblogs.com/jycboy/p/6219915.html
http://www.reibang.com/p/c4412f878508

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末奕污,一起剝皮案震驚了整個濱河市萎羔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌碳默,老刑警劉巖外驱,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件育灸,死亡現(xiàn)場離奇詭異,居然都是意外死亡昵宇,警方通過查閱死者的電腦和手機磅崭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瓦哎,“玉大人砸喻,你說我怎么就攤上這事〗” “怎么了割岛?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長犯助。 經(jīng)常有香客問我癣漆,道長,這世上最難降的妖魔是什么剂买? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任惠爽,我火速辦了婚禮,結果婚禮上瞬哼,老公的妹妹穿的比我還像新娘婚肆。我一直安慰自己,他們只是感情好坐慰,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布较性。 她就那樣靜靜地躺著,像睡著了一般结胀。 火紅的嫁衣襯著肌膚如雪赞咙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天糟港,我揣著相機與錄音人弓,去河邊找鬼。 笑死着逐,一個胖子當著我的面吹牛,可吹牛的內容都是我干的意蛀。 我是一名探鬼主播耸别,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼县钥!你這毒婦竟也來了秀姐?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤若贮,失蹤者是張志新(化名)和其女友劉穎省有,沒想到半個月后痒留,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡蠢沿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年伸头,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片舷蟀。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡恤磷,死狀恐怖,靈堂內的尸體忽然破棺而出野宜,到底是詐尸還是另有隱情扫步,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布匈子,位于F島的核電站河胎,受9級特大地震影響,放射性物質發(fā)生泄漏虎敦。R本人自食惡果不足惜游岳,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望原茅。 院中可真熱鬧吭历,春花似錦、人聲如沸擂橘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽通贞。三九已至朗若,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間昌罩,已是汗流浹背哭懈。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留茎用,地道東北人遣总。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像轨功,于是被迫代替她去往敵國和親旭斥。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內容

  • View的繪制和事件處理是兩個重要的主題古涧,上一篇《圖解 Android事件分發(fā)機制》已經(jīng)把事件的分發(fā)機制講得比較詳...
    Kelin閱讀 119,515評論 100 845
  • Android的UI管理系統(tǒng)層級關系 PhoneWindow是Adroid系統(tǒng)中最基本的窗口系統(tǒng)垂券,每個Activi...
    凱玲之戀閱讀 1,888評論 0 2
  • View的加載流程view布局一直貫穿于整個android應用中,不管是activity還是fragment都給我...
    ZEKI安卓學弟閱讀 263評論 0 0
  • 標簽: Android 源碼解析 View 關于View的繪制流程羡滑,或者說 View 的工作流程(說繪制流程容易讓...
    koguma閱讀 1,969評論 1 18
  • Android 的繪制過程可分為3個步驟即:measure(測量)菇爪、layout(布局)算芯、draw(繪制)。 一凳宙、...
    johnnycmj閱讀 463評論 0 1