死磕Android_View工做原理你須要知道的一切

平時在開發安卓的過程當中,View是咱們用的很是很是多的東西.用戶所看到的一切關於UI的,都是經過View繪製出來展現到屏幕上的.大多數狀況下咱們僅僅瞭解基本控件的使用方法,咱們是沒法作出很是複雜炫酷的自定義View的.咱們須要掌握View的工做原理:測量、佈局、繪製流程,掌握了這幾個基本的流程咱們才能作出更加完美的自定義View.作起來也更加駕輕就熟.固然,View的工做原理,也是大多數面試所必問的知識點.要想了解工做原理,就只能read the fucking code.java

/***
 *                                         ,s555SB@@&                          
 *                                      :9H####@@@@@Xi 
 *                                     1@@@@@@@@@@@@@@8                       
 *                                   ,8@@@@@@@@@B@@@@@@8                      
 *                                  :B@@@@X3hi8Bs;B@@@@@Ah,                   
 *             ,8i                  r@@@B:     1S ,M@@@@@@#8; 
 *            1AB35.i:               X@@8 .   SGhr ,A@@@@@@@@S                
 *            1@h31MX8                18Hhh3i .i3r ,A@@@@@@@@@5               
 *            ;@&i,58r5                 rGSS:     :B@@@@@@@@@@A               
 *             1#i . 9i hX. .: .5@@@@@@@@@@@1 
 *              sG1,  ,G53s.              9#Xi;hS5 3B@@@@@@@B1 
 *               .h8h.,A@@@MXSs,           #@H1: 3ssSSX@1 
 *               s ,@@@@@@@@@@@@Xhi,       r#@@X1s9M8 .GA981 
 *               ,. rS8H#@@@@@@@@@@#HG51;. .h31i;9@r .8@@@@BS;i; 
 *                .19AXXXAB@@@@@@@@@@@@@@#MHXG893hrX#XGGXM@@@@@@@@@@MS 
 *                s@@MM@@@hsX#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&, 
 *              :GB@#3G@@Brs ,1GM@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@B, 
 *            .hM@@@#@@#MX 51 r;iSGAM@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@8 
 *          :3B@@@@@@@@@@@&9@h :Gs   .;sSXH@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:    
 *      s&HA#@@@@@@@@@@@@@@M89A;.8S. ,r3@@@@@@@@@@@@@@@@@@@@@@@@@@@r 
 *   ,13B@@@@@@@@@@@@@@@@@@@5 5B3 ;.         ;@@@@@@@@@@@@@@@@@@@@@@@@@@@i    
 *  5#@@#&@@@@@@@@@@@@@@@@@@9 .39: ;@@@@@@@@@@@@@@@@@@@@@@@@@@@; 
 *  9@@@X:MM@@@@@@@@@@@@@@@#; ;31. H@@@@@@@@@@@@@@@@@@@@@@@@@@: 
 *   SH#@B9.rM@@@@@@@@@@@@@B :. 3@@@@@@@@@@@@@@@@@@@@@@@@@@5 
 *     ,:.   9@@@@@@@@@@@#HB5 .M@@@@@@@@@@@@@@@@@@@@@@@@@B 
 *           ,ssirhSM@&1;i19911i,.             s@@@@@@@@@@@@@@@@@@@@@@@@@@S   
 *              ,,,rHAri1h1rh&@#353Sh: 8@@@@@@@@@@@@@@@@@@@@@@@@@#: 
 *            .A3hH@#5S553&@@#h i:i9S #@@@@@@@@@@@@@@@@@@@@@@@@@A.
 *
 *
 *    又看源碼,看你妹呀!
 */
複製代碼

1. View是從何時開始繪製的?

1.1 先簡單來個demo

新建一個自定義View,名字叫MyView,分別在MyView的onMeasure(),onLayout(),onDraw()打上log.面試

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    Log.e(TAG, "onMeasure: ---MyView");
}

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    Log.e(TAG, "onLayout: ---MyView");
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Log.e(TAG, "onDraw: ---MyView");
}
複製代碼

而後在Activity的佈局中添加這個MyView,並在Activity的onCreate(),onStart(),onResume()打上log.canvas

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Log.e(TAG, "onCreate: ");
}

@Override
protected void onStart() {
    super.onStart();
    Log.e(TAG, "onStart: ");
}

@Override
protected void onResume() {
    super.onResume();
    Log.e(TAG, "onResume: ");
}
複製代碼

運行demo,看一下log:bash

05-13 22:35:37.130 17346-17346/com.xfhy.demo E/xfhy: onCreate: 
05-13 22:35:37.130 17346-17346/com.xfhy.demo E/xfhy: onStart: 
05-13 22:35:37.130 17346-17346/com.xfhy.demo E/xfhy: onResume: 
05-13 22:35:37.155 17346-17346/com.xfhy.demo E/xfhy: onMeasure: ---MyView
05-13 22:35:37.295 17346-17346/com.xfhy.demo E/xfhy: onLayout: ---MyView
05-13 22:35:37.315 17346-17346/com.xfhy.demo E/xfhy: onMeasure: ---MyView
05-13 22:35:37.315 17346-17346/com.xfhy.demo E/xfhy: onLayout: ---MyView
05-13 22:35:37.315 17346-17346/com.xfhy.demo E/xfhy: onDraw: ---MyView
複製代碼

好了,咱們從log中就能夠看出,View的繪製實際上是從Activity的onResume()以後纔開始的.app

1.2 從源碼層看看工做過程

在Activity的啓動過程當中,咱們知道,在最後那裏,ActivityThread中的handleLaunchActivity,performLaunchActivity,handleResumeActivity,這3個主要的方法完成了Activity的建立到啓動工做.ide

ps: 若是有不清楚的在網上查閱一下資料,這裏推薦剛哥的書籍:安卓開發藝術探索 第九章oop

1.2.1 handleLaunchActivity()

下面看一下handleLaunchActivity()方法:源碼分析

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...

    if (localLOGV) Slog.v(
        TAG, "Handling launch of " + r);
    
    //分析1 : 這裏是建立Activity,並調用了Activity的onCreate()和onStart()
    Activity a = performLaunchActivity(r, customIntent);

    if (a != null) {
        r.createdConfig = new Configuration(mConfiguration);
        Bundle oldState = r.state;
        //分析2 : 這裏調用Activity的onResume()
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed);
    }
    ....
}
複製代碼

handleLaunchActivity()主要是爲了調用performLaunchActivity()和handleResumeActivity()佈局

1.2.2 performLaunchActivity()

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ......
    //分析1 : 這裏底層是經過反射來建立的Activity實例
    java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
    activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);

    //底層也是經過反射構建Application,若是已經構建則不會重複構建,畢竟一個進程只能有一個Application
    Application app = r.packageInfo.makeApplication(false, mInstrumentation);

    if (activity != null) {
        Context appContext = createBaseContextForActivity(r, activity);
        CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
        Configuration config = new Configuration(mCompatConfiguration);
        if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                + r.activityInfo.name + " with config " + config);
        //分析2 : 在這裏實例化了PhoneWindow,並將該Activity設置爲PhoneWindow的Callback回調,還初始化了WindowManager
        activity.attach(appContext, this, getInstrumentation(), r.token,
                r.ident, app, r.intent, r.activityInfo, title, r.parent,
                r.embeddedID, r.lastNonConfigurationInstances, config);

        //分析3 : 間接調用了Activity的performCreate方法,間接調用了Activity的onCreate方法.
        mInstrumentation.callActivityOnCreate(activity, r.state);
        
        //分析4: 這裏和上面onCreate過程差很少,調用Activity的onStart方法
        if (!r.activity.mFinished) {
            activity.performStart();
            r.stopped = false;
        }
        ....
    }
}
複製代碼

主要過程:post

  1. 經過反射來建立的Activity實例
  2. 在這裏實例化了PhoneWindow,並將該Activity設置爲PhoneWindow的Callback回調.創建起Activity與PhoneWindow之間的聯繫.
  3. 調用了Activity的onCreate方法
  4. 調用Activity的onStart方法

對於分析1中的代碼:

public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
    //反射->實例化
    return (Activity)cl.loadClass(className).newInstance();
}
複製代碼

對於分析2中的代碼:

final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window, ActivityConfigCallback activityConfigCallback) {
    ......

    //實例化PhoneWindow
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    
    .....
    //還有一些其餘的配置代碼

    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

    mWindowManager = mWindow.getWindowManager();
    ....
}
複製代碼

對於分析3中的代碼:

//首先是來到Instrumentation的callActivityOnCreate方法
public void callActivityOnCreate(Activity activity, Bundle icicle) {
    prePerformCreate(activity);
    activity.performCreate(icicle);
    postPerformCreate(activity);
}

//而後就來到Activity的performCreate方法
final void performCreate(Bundle icicle) {
    performCreate(icicle, null);
}

final void performCreate(Bundle icicle, PersistableBundle persistentState) {
    ....
    if (persistentState != null) {
        onCreate(icicle, persistentState);
    } else {
        onCreate(icicle);
    }
    ......
}

複製代碼

對於分析4中的代碼:

//Activity
final void performStart() {
    ......
    mInstrumentation.callActivityOnStart(this);
    ......
}

//Instrumentation
public void callActivityOnStart(Activity activity) {
    activity.onStart();
}
複製代碼

1.2.3 handleResumeActivity()

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
    .....
    //分析1 : 在其內部調用Activity的onResume方法
    r = performResumeActivity(token, clearHide, reason);

    .....
    r.window = r.activity.getWindow();
    View decor = r.window.getDecorView();
    decor.setVisibility(View.INVISIBLE);
    //獲取WindowManager
    ViewManager wm = a.getWindowManager();
    WindowManager.LayoutParams l = r.window.getAttributes();
    a.mDecor = decor;

    if (a.mVisibleFromClient) {
        .....
        //分析2 : WindowManager添加DecorView
        wm.addView(decor, l);
        ...
    }
    .....

}
複製代碼

細看分析2中的邏輯:

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
複製代碼

咱們看到,方法裏面將邏輯交給了mGlobal,mGlobal是WindowManagerGlobal,WindowManagerGlobal是全局單例.WindowManagerImpl的方法都是由WindowManagerGlobal完成的.咱們跟着來到了WindowManagerGlobal的addView方法.

public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
    ....

    ViewRootImpl root;

    synchronized (mLock) {
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);

        .....

        root.setView(view, wparams, panelParentView);
    }
}
複製代碼

在這裏咱們看到了,實例化ViewRootImpl,而後創建ViewRootImpl與View的聯繫.跟着進入setView方法

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    .....
    requestLayout();
    .....
}

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        //檢查線程合法性
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        ......
    }
}

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();


void doTraversal() {
    if (mTraversalScheduled) {
        ....
        performTraversals();
        ....
    }
}

複製代碼

一路下來,咱們來到了熟悉的方法面前:performTraversals方法.

1.2.4 performTraversals()

performTraversals()方法相信你們都已經很是熟悉啦,它是整個View繪製的核心,從measure到layout,再從layout到draw,所有在這個方法裏面完成了,因此這個方法裏面的代碼很是長,這是確定的.因爲本人水平有限,就不每句代碼逐行分析了,咱們須要學習的是一個主要的流程.

下面的performTraversals()方法的超精簡代碼,裏面的代碼真的超級超級多,下面是主要流程,也是今天的主角

private void performTraversals() {
    //分析1 : 這裏面會調用performMeasure開始測量流程
    measureHierarchy(host, lp, res,
                    desiredWindowWidth, desiredWindowHeight);
    //分析2 : 開始佈局流程
    performLayout(lp, mWidth, mHeight);
    //分析3 : 開始繪畫流程
    performDraw();
}
複製代碼

首先,來個主要的流程圖,這個是performTraversals()的大體流程.

View繪製流程

如上面的代碼所示,performTraversals()會依次調用performMeasure、performLayout、performDraw方法,而這三個方法是View的繪製流程的核心所在.

  • performMeasure : 在performMeasure裏面會調用measure方法,而後measure會調用onMeasure方法,而在onMeasure方法中則會對全部的子元素進行measure過程.這至關於完成了一次從父元素到子元素的measure傳遞過程,若是子元素是一個ViewGroup,那麼繼續向下傳遞,直到全部的View都已測量完成.測量完成以後,咱們能夠根據getMeasureHeight和getMeasureWidth方法獲取該View的高度和寬度.
  • performLayout : performLayout的原理實際上是和performMeasure差很少,在performLayout裏面調用了layout方法,而後在layout方法會調用onLayout方法,onLayout又會對全部子元素進行layout過程.由父元素向子元素傳遞,最終完成全部View的layout過程.肯定View的4個點: left+top+right+bottom,layout完成以後能夠經過getWidth和getHeight獲取View的最終寬高.
  • performDraw : 也是和performMeasure差很少,從父元素從子元素傳遞.在performDraw裏面會調用draw方法,draw方法再調用drawSoftware方法,drawSoftware方法裏面回調用View的draw方法,而後再經過dispatchDraw方法分發,遍歷全部子元素的draw方法,draw事件就這樣一層層地傳遞下去.

2. View 測量流程

2.1 MeasureSpec

在開始進行理解View的測量流程以前,須要先理解MeasureSpec.

MeasureSpec表明的是32位的int值,它的高2位是SpecMode(也是一個int),低30位是SpecSize(也是一個int),SpecMode是測量模式,SpecSize是測量大小. MeasureSpec至關因而二者的結合. 系統封裝瞭如何從MeasureSpec中提取SpecMode和SpecSize,也封裝了用SpecMode和SpecSize組合成MeasureSpec.

public static class MeasureSpec {
    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    public static final int EXACTLY     = 1 << MODE_SHIFT;
    public static final int AT_MOST     = 2 << MODE_SHIFT;

    public static int makeMeasureSpec(int size,int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }

    public static int getMode(int measureSpec) {
        return (measureSpec & MODE_MASK);
    }

    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }
}
複製代碼

SpecMode有3種,分別是

  • UNSPECIFIED 父容器對View不會有任何限制,要多大給多大,通常是用在系統內部使用,咱們開發的APP用不到.
  • EXACTLY 這種狀況對應於match_parent和具體數值這兩種模式,父容器已經檢測出View須要的精確大小.
  • AT_MOST 這種狀況對應於wrap_content,父容器指定了一個最大值,View不能超過這個值.

通常來講,View的MeasureSpec由父容器的MeasureSpec和本身的LayoutParams共同決定.由於有了MeasureSpec才能夠在onMeasure中肯定測量寬高.

下面是從源碼中提取出來的摘要信息,後面會詳細看源碼分析,這裏先提取出來

  • 若是View的寬高是固定的值,那麼無論父容器的MeasureSpec是什麼,View的MeasureSpec都是EXACTLY
  • 若是View的寬高是wrap_content,那麼無論父容器的MeasureSpec是EXACTLY仍是AT_MOST,最終View的MeasureSpec都是AT_MOST,這裏暫時不用管UNSPECIFIED(咱們用不到).並且View最終的大小不能超過父容器的剩餘空間
  • 若是View的寬高是match_parent,那麼要分兩種狀況
    • 若是父容器是EXACTLY,那麼View就是EXACTLY
    • 若是父容器是AT_MOST,那麼View也是AT_MOST.

這裏不得不引用一張剛哥書籍裏面的經典表格來表示一下:

ps:這裏必需要強烈推薦一波剛哥的安卓開發藝術探索這本書.沒看過這本書,都不敢說本身是學安卓的.去年我做爲萌新買了這本書看了一遍,以爲受益不淺.今年打算再看一遍,從新梳理一下.好書是值得反覆推敲琢磨的.

2.2 從performMeasure()開始View測量

咱們從ViewRootImpl的performTraversals()開始着手,仔細觀察

private void performTraversals() {
    //host爲根視圖,即DecorView
    //desiredWindowWidth是Window的寬度
    measureHierarchy(host, lp, res,
                    desiredWindowWidth, desiredWindowHeight);
}

private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp, final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
    //分析1 : desiredWindowWidth就是Window的寬度,desiredWindowHeight是Window的高度
    childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
    childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
    //分析2 : 開始測量流程
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);                     
}
複製代碼

measure是從上往下執行的,widthMeasureSpec和heightMeasureSpec一般狀況下是由父容器傳遞給子視圖的.可是最外層的根視圖,怎麼拿到MeasureSpec呢? 在執行performMeasure方法以前,咱們須要拿到最外層的視圖的MeasureSpec,看代碼

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {
    //若是是MATCH_PARENT,那麼就是EXACTLY
    case ViewGroup.LayoutParams.MATCH_PARENT:
        // Window can't resize. Force root view to be windowSize.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
    //若是是WRAP_CONTENT,就是AT_MOST
    case ViewGroup.LayoutParams.WRAP_CONTENT:
        // Window can resize. Set max size for root view.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
        break;
    default:
        //若是是固定的值,也是EXACTLY
        // Window wants to be an exact size. Force root view to be that size.
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}
複製代碼

最外層的根視圖的MeasureSpec只由本身的LayoutParams決定,作本身的主人,舒服.

既然咱們根視圖拿到了MeasureSpec,接下來就要拿本身的MeasureSpec教孩子作人了.

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    //調用根視圖的measure方法,開始測量流程
    mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
複製代碼

這裏的mView就是DecorView.DecorView是一個FrameLayout,而FrameLayout是一個ViewGroup,而ViewGroup是一個View,這個measure方法就是在View裏面的. 由於measure是一個final方法,哈哈,因此子類不能覆寫它.

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ......
    //調用onMeasure方法 
    onMeasure(widthMeasureSpec, heightMeasureSpec);
    ......
}
複製代碼

measure方法裏面有一些檢測是否須要從新onMeasure的代碼,被我略去了.

onMeasure是View裏面的方法,ViewGroup是一個抽象類而且沒有重寫onMeasure.由於onMeasure方法的實現,每一個都是不同的,好比LinearLayout和FrameLayout的onMeasure方法確定是實現邏輯不同的.

由於DecorView是FrameLayout,因此咱們看看FrameLayout中的onMeasure.

FrameLayout->onMeasure()

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();

    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            //分析1 : 遍歷全部子控件,測量每一個子控件的大小
                //參數1:View控件
                //參數2:寬MeasureSpec
                //參數3:父容器在寬度上已經用了多少了,由於FrameLayout的規則是:前面已經放置的View並不會影響後面放置View的寬高,是直接覆蓋到上一個View上的.因此這裏傳0
                //參數4:高MeasureSpec
                //參數5:父容器在高度上已經用了多少了
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
        }
    }
    ......

    //分析2 : 測量完全部的子控件的大小以後,才知道本身的大小 這很符合FrameLayout的規則嘛
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            resolveSizeAndState(maxHeight, heightMeasureSpec,
                    childState << MEASURED_HEIGHT_STATE_SHIFT));
    ......
}
複製代碼

FrameLayout的onMeasure方法中會遍歷全部子控件,而後進行全部子控件的大小測量.最後纔來設置本身的大小.注意,onMeasure方法的入參MeasureSpec是從父容器傳過來的,意思就是給你個參考,你本身看着辦吧.

在測量子控件大小的時候會調用ViewGroup的measureChildWithMargins方法,下面是代碼:

protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
    //獲取子控件的LayoutParams
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    
    //分析1: 計算子控件在寬上的MeasureSpec 
        //參數1:父容器的MeasureSpec
        //參數2:這裏官方的入參名稱是padding,從下面這個傳值的形式來看,顯然是子控件在寬上不能利用的空間(ViewGroup的左右兩邊padding+子控件的左右margin+父容器在寬度上已經使用了而且不能再使用的空間)
        //參數3:子控件想要的寬度
    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);
    
    //分析2: 將measure過程傳遞給子控件 若是子控件又是一個ViewGroup,那麼繼續向下傳遞
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
複製代碼

在measureChildWithMargins方法裏咱們首先是看到根據子控件的LayoutParams和父容器的MeasureSpec計算子控件的MeasureSpec,而後將計算出的MeasureSpec經過子控件的measure方法傳遞下去.若是子控件又是一個ViewGroup,那麼它又會重複的measure流程,一直向下傳遞這個過程,直接最後的那個是View爲止.由於View沒有子控件,它就不能向下傳遞了.

因此咱們自定義View(這裏指那種直接繼承自View)的時候,在onMeasure方法裏面,須要根據自身的LayoutParams+父容器的MeasureSpec來計算SpecSize和SpecMode,最後根據業務場景來肯定本身的大小(調用setMeasuredDimension來肯定大小).

注意了,接下來的getChildMeasureSpec方法就比較重要了

//這裏來自ViewGroup的getChildMeasureSpec方法,無刪減
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    //根據父容器的MeasureSpec獲取父容器的SpecMode和SpecSize
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);
    
    //剩下的size
    int size = Math.max(0, specSize - padding);

    //最終的size和mode
    int resultSize = 0;
    int resultMode = 0;

    switch (specMode) {
    // Parent has imposed an exact size on us
    //父容器有一個肯定的大小
    case MeasureSpec.EXACTLY:
        if (childDimension >= 0) {
            //子控件也是肯定的大小,那麼最終的大小就是子控件設置的大小,SpecMode爲EXACTLY
            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.
            //子控件想要本身定義大小,可是不能超過剩餘空間 size
            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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size.... find out how
            // big it should be
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        }
        break;
    }
    //noinspection ResourceType
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
複製代碼

這段代碼對應着下面這段總結

  • 若是View的寬高是固定的值,那麼無論父容器的MeasureSpec是什麼,View的MeasureSpec都是EXACTLY
  • 若是View的寬高是wrap_content,那麼無論父容器的MeasureSpec是EXACTLY仍是AT_MOST,最終View的MeasureSpec都是AT_MOST,這裏暫時不用管UNSPECIFIED(咱們用不到).並且View最終的大小不能超過父容器的剩餘空間
  • 若是View的寬高是match_parent,那麼要分兩種狀況
    • 若是父容器是EXACTLY,那麼View就是EXACTLY
    • 若是父容器是AT_MOST,那麼View也是AT_MOST.

這段代碼對應着上面剛哥總結的那個表格.同時也是measure流程的核心內容.

image

由於在measureChildWithMargins方法裏咱們已經計算出子控件的MeasureSpec,而後經過measure傳遞給子控件了,若是子控件又是一個ViewGroup,那麼它又會重複的measure流程,一直向下傳遞這個過程,直接最後的那個是View爲止.由於View沒有子控件,它就不能向下傳遞了.到這裏其實咱們的View的measure流程已經走完了,哈哈,不知不覺.

下面簡單畫一個流程圖,方便理解上面的流程.

image

2.3 measure小結

從ViewRootImpl的performTraversals方法開始進入View的繪製過程,performTraversals方法裏面會有一個performMeasure方法.這個performMeasure方法是專門拿來測量View的大小的.並且會遍歷整個View樹,所有進行測量.

在performMeasure裏面會調用measure方法,而後measure會調用onMeasure方法,而在onMeasure方法中則會對全部的子元素進行measure過程.這至關於完成了一次從父元素到子元素的measure傳遞過程,若是子元素是一個ViewGroup,那麼繼續向下傳遞,直到全部的View都已測量完成.測量完成以後,咱們能夠根據getMeasureHeight和getMeasureWidth方法獲取該View的高度和寬度.

3. View 佈局流程

3.1 從performLayout開始佈局

咱們仍是從ViewRootImpl的performTraversals()開始着手

private void performTraversals() {
    ....
    //開始佈局流程
    performLayout(lp, mWidth, mHeight);
    .....
}

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
    .....
    //這裏的host實際上是根視圖(DecorView)
        //參數:left,top,right,bottom 這些位置都是相對於父容器而言的
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    .....
}

複製代碼

在performLayout方法裏面調用了DecorView的layout方法,而後我發現:layout方法實際上是View這個父類裏面的,而後ViewGroup繼承了View以後重寫了一下,只是調了一下super.layout(l, t, r, b);,至關於實現仍是在View的layout裏面.並且ViewGroup的layout方法是final修飾的,意味着子類不能再重寫這個方法了.

//如下是View的layout方法
public void layout(int l, int t, int r, int b) {
    ......
    onLayout(changed, l, t, r, b);
    ......
}
複製代碼

layout方法其實就是調用onLayout方法,若是這裏子控件是一個View的話,那麼onLayout實際上是空實現.onLayout在ViewGroup是一個抽象方法,若是是一個ViewGroup的話,好比FrameLayout,那麼onLayout是須要本身實現的.

//View中的定義
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}

//ViewGroup中的定義,沒錯,這是抽象方法,具體的實現交由實現類去實現
@Override
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
複製代碼

由於咱們的根視圖是DecorView,也就是FrameLayout,那麼咱們來看一下FrameLayout的onLayout實現:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {   
    //佈局子控件 我沒看懂這個changed參數是拿來幹什麼的,好像並無用上(這裏已是FrameLayout的onLayout方法的所有代碼了)
    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();
    
    //最左側
    final int parentLeft = getPaddingLeftWithForeground();
    //最右側
    final int parentRight = right - left - getPaddingRightWithForeground();
    //最頂部
    final int parentTop = getPaddingTopWithForeground();
    //最底部
    final int parentBottom = bottom - top - getPaddingBottomWithForeground();

    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            
            //由於已經measure流程走完了,因此這裏是能經過getMeasuredWidth方法獲取測量寬度的
            final int width = child.getMeasuredWidth();
            final int height = child.getMeasuredHeight();

            //實際子控件的left
            int childLeft;
            int childTop;

            int gravity = lp.gravity;
            if (gravity == -1) {
                gravity = DEFAULT_CHILD_GRAVITY;
            }

            final int layoutDirection = getLayoutDirection();
            final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
            final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                //水平居中
                case Gravity.CENTER_HORIZONTAL:
                    childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                    lp.leftMargin - lp.rightMargin;
                    break;
                case Gravity.RIGHT:  //子控件在父容器的最右側
                    if (!forceLeftGravity) {
                        childLeft = parentRight - width - lp.rightMargin;
                        break;
                    }
                case Gravity.LEFT: //子控件在父容器的最左側
                default:
                    childLeft = parentLeft + lp.leftMargin;
            }
            
            //豎直方向上的gravity
            switch (verticalGravity) {
                case Gravity.TOP:  //位於父容器的頂部
                    childTop = parentTop + lp.topMargin;
                    break;
                case Gravity.CENTER_VERTICAL:  //垂直居中
                    childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                    lp.topMargin - lp.bottomMargin;
                    break;
                case Gravity.BOTTOM:  //位於父容器底部
                    childTop = parentBottom - height - lp.bottomMargin;
                    break;
                default:
                    childTop = parentTop + lp.topMargin;
            }
            
            //最後給這個子控件一個最終的left,top,right,bottom值
            //把這個子控件放在這裏
            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}
複製代碼

不一樣的ViewGroup的實現類的onLayout方法實現是不同的,是根據自身狀況來決定將子控件放在那裏的,好比FrameLayout和LinearLayout的onLayout是不同的實現,可是onLayout這個方法最終是將各個子控件有條不紊的放在對應的位置上.

咱們看到在onLayout方法的最後,調用了子控件的layout方法,其實就是將layout流程向下進行傳遞了.若是子控件仍是ViewGroup的話,那麼它又會對它本身全部的子控件進行佈局,放置.最後一層一層的往下,直到所有都layout完成.每一個View都知道本身的left,top,right,bottom.這個時候是能夠經過View的getWidth和getHeight來獲取最終的寬高的.

下面的View的getWidth和getHeight方法的實現,能夠看到,就是經過這四個位置來肯定的寬高.

public final int getWidth() {
    return mRight - mLeft;
}

public final int getHeight() {
    return mBottom - mTop;
}
複製代碼

3.2 layout小結

layout主要是爲了肯定該控件以及其子控件的位置和大小.在performLayout中,主要是肯定每一個控件的left+top+right+bottom,performLayout以後它們的位置就已經被肯定了,就只剩下最後一步繪製了.

4. View 繪製流程

仍是從ViewRootImpl的performTraversals方法開始分析

private void performTraversals() {
    //開始繪畫流程
    performDraw();
}

private void performDraw() {
    ......
    draw(fullRedrawNeeded);
    ......
}

private void draw(boolean fullRedrawNeeded){
    .....
    drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty);
    .....
}

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) {
    ......
    mView.draw(canvas);
    ......
}

複製代碼

隨着方法的調用深刻,發現來到了View的draw方法

public void draw(Canvas canvas) {
    .....

    /* 注意了這是官方給的註釋,谷歌工程師還真是貼心,把draw步驟寫的詳詳細細,給力,點贊 * 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
    //1. 繪製背景
    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
        //3. 繪製本身的內容
        if (!dirtyOpaque) onDraw(canvas);

        // Step 4, draw the children
        //4. 繪製子控件 若是是View的話這個方法是空實現,若是是ViewGroup則繪製子控件
        dispatchDraw(canvas);

        drawAutofilledHighlight(canvas);

        // Overlay is part of the content and draws beneath Foreground
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }

        // Step 6, draw decorations (foreground, scrollbars)
        //6. 繪製裝飾和前景
        onDrawForeground(canvas);

        // Step 7, draw the default focus highlight
        //7. 繪製默認焦點高亮顯示
        drawDefaultFocusHighlight(canvas);

        if (debugDraw()) {
            debugDrawFocus(canvas);
        }

        // we're done...
        return;
    }
    .....
}
複製代碼

注意到,谷歌工程師將draw的步驟完徹底全的寫出來了的.還真是貼心啊.draw的基本步驟以下

  1. 繪製背景
  2. 繪製控件本身自己的內容
  3. 繪製子控件
  4. 繪製裝飾(好比滾動條)和前景

這裏簡單提一下dispatchDraw方法,在這個方法裏面會去調用drawChild方法,在drawChild裏面會調用子控件的draw方法,這至關於完成了draw的傳遞過程,通知子控件去繪製它本身. 而後若是子控件是ViewGroup,它又會重複上面這個遞推.

draw的流程比測量和佈局要簡單一些,可是須要注意的是,View繪製過程是經過dispatchDraw來傳遞的.

5. 結束語

寫一篇深刻(可能只是對我來講)的文章真的好不容易,期間遇到了不少坑,也學到了不少.以前其實這部分是學過的.可是隻有當本身去看源碼,一步步分析,輸出成文檔,才真正理解其中的原理,爲何代碼要這樣寫.

可能仍是太菜了吧,寫這麼一篇水文花了大概4天......

image
相關文章
相關標籤/搜索