Android中View繪制流程淺析

一個View,從無到有會走三個流程,也就是老生常談的measure,layout,draw三流程;

我們都知道Android視圖是由一層一層構(gòu)成的層級結(jié)構(gòu),直白點(diǎn)說,就是父View包含子View而子View又可以包含子View。所以繪制流程是由最外層的View開始,一步一步向內(nèi)傳遞執(zhí)行。而整個過程又是遞歸等待的,最外層的View需要等內(nèi)層所有的View執(zhí)行完繪制流程才結(jié)束,所以便有了"減少布局層級,可以有效提升App性能"這一經(jīng)典總結(jié)。

什么時候開始繪制?

而萬物有始才有終,你不惹他,他也不會動手打你。View的繪制流程是什么時候開始的?誰觸發(fā)的?明白這點(diǎn)后,才去考慮這個過程是怎樣執(zhí)行的。

我們都清楚Activity中onCreate()方法在setContentView()后,View的寬高是獲取不到的。同時我們知道Activity在onResume()才完全可見,并且初次在onResume()方法中也是拿不到View的尺寸的,這樣可以推算得出:View的繪制流程是在onResume()方法執(zhí)行結(jié)束后才開始的。那Activity的生命周期方法背后是由誰,又何時調(diào)用的?

答:ActivityManagerService
ActivityManagerService(以下簡稱AMS))是Androids上層系統(tǒng)中最核心的服務(wù)之一,主要負(fù)責(zé)系統(tǒng)中四大組件的啟動、切換、調(diào)度及應(yīng)用程序的管理和調(diào)度等工作。原文

相對而言ActivityThread的main方法是應(yīng)用程序的入口,main()方法里做一些初始化工作,其中包括和AMS建立起通信。

public class ActivityThread{

    final ApplicationThread mAppThread = new ApplicationThread();
    final Looper mLooper = Looper.myLooper();
    final H mH = new H();
    final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();

    
    public static void main(String[] args) {
        //初始化lopper
        Looper.prepareMainLooper();
        //初始化ActivityThread
        ActivityThread thread = new ActivityThread();
        //ApplicationThread和AMS建立聯(lián)系
        thread.attach(false);
        //取消息
        Looper.loop();
        //loop()方法如果執(zhí)行結(jié)束,未能取到消息,程序拋出異常退出。
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
}

ActivityThread會管理和用戶打交道的Activity,應(yīng)用所有的Activity都存在ActivityThread中的mActivities集合中,而ActivityThread響應(yīng)AMS的號召,需要借助ApplicationThread來接受這個詔令,點(diǎn)進(jìn)去看全都是生命周期方法。接著調(diào)用attach()方法讓ApplicationThread和AMS建立聯(lián)系。H類就是一個Handler類,用于發(fā)送生命周期改變的消息,通知響應(yīng)操作。

private class ApplicationThread extends IApplicationThread.Stub {
  
    //通知相應(yīng)的進(jìn)程執(zhí)行啟動Activity的操作
    public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
            ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
            CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
            int procState, Bundle state, PersistableBundle persistentState,
            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
            boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
    
        sendMessage(H.LAUNCH_ACTIVITY, r);
    }
    public final void scheduleResumeActivity(IBinder token, int processState,
            boolean isForward, Bundle resumeArgs) {

        sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0, 0, seq);
    }
    //.....

}

對于AMS我也不太懂在這兒提一下明白是怎么回事就行,以后再慢慢研究。當(dāng)Activity啟動時會先調(diào)用到scheduleLaunchActivity()方法,由Handler發(fā)送通知消息后執(zhí)行handleLaunchActivity()->performLaunchActivity()->callActivityOnCreate()->Activity.onCreate()。

private class H extends Handler {
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case LAUNCH_ACTIVITY: {
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                r.packageInfo = getPackageInfoNoCheck(
                        r.activityInfo.applicationInfo, r.compatInfo);
                //該方法中會執(zhí)行Activity的onCreate()方法。
                handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            } break;
            //onResume();
             case RESUME_ACTIVITY:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
                SomeArgs args = (SomeArgs) msg.obj;
                handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true,
                        args.argi3, "RESUME_ACTIVITY");
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
        }
    }
}

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ActivityClientRecord r = mActivities.get(token);
    
    //.............
    
    //執(zhí)行onResume()方法
    r = performResumeActivity(token, clearHide, reason);
    
     if (r != null) {
        final Activity a = r.activity;
         if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    //將DecorView添加到Window上
                    wm.addView(decor, l);
            } 
        }
    //說法二:執(zhí)行makeVisible()來添加View,但也是添加到Window里和上面一樣的操作。清楚的小伙伴可以告訴我下。
    if (r.activity.mVisibleFromClient) {
            r.activity.makeVisible();
        }
    }
}

onResume()時也一樣,當(dāng)Activity的狀態(tài)發(fā)生改變,經(jīng)過層層調(diào)用執(zhí)行到handleResumeActivity()方法,在方法中先調(diào)用Activity.onResume()方法,再執(zhí)行WindowManager的addView()方法將Activity的根View(DecorView)添加上去,進(jìn)而開始繪制流程。這就解釋了為什么初次在onResume()方法中獲取不到View的寬高。對DecorView不太明白的可以參考Activity中setContentView淺析。而WindowManager實(shí)現(xiàn)類為WindowManagerImpl,WindowManagerImpl中addView()方法又會調(diào)用WindowManagerGlobal的addView()方法。原文

 public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
    ······
    root = new ViewRootImpl(view.getContext(), display);
    
    view.setLayoutParams(wparams);
    
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);
    //即將開始流程繪制    
    root.setView(view, wparams, panelParentView);
    ·······
}

addView()方法中先創(chuàng)建ViewRootImpl對象,隨后執(zhí)行setView()方法將其和DecorView綁定起來,繪制流程也將由ViewRootImpl()來執(zhí)行。setView()方法中會執(zhí)行requestLayout()方法。

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
     if (mView == null) {
        mView = view;
         // Schedule the first layout -before- adding to the window
         // manager, to make sure we do the relayout before receiving
         // any other events from the system.
         requestLayout();
    }
}

requestLayout()方法走下去會異步執(zhí)行performTraversals()方法,View的三大流程都是在該方法中執(zhí)行的。到這兒我們算是明白View的繪制流程是從哪兒開始的,接下來分析這個過程到底是怎么做的。

private void performTraversals() {

    //計(jì)算DecorView根View的MeasureSpec
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
                 
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    
    performLayout(lp, mWidth, mHeight);
    
    performDraw();
}

measure流程

說到measure流程就不得提到一個類,MeausreSpec。使用該類用一個int值就能記錄View測量的寬高和寬高的測量模式,大大節(jié)約開銷。

public static class MeasureSpec {
    private static final int MODE_SHIFT = 30;
    
    //int類型占4個字節(jié),1個字節(jié)=8bit(位)。
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT; //11000000000000000000000000000000
    
    public static final int UNSPECIFIED = 0 << MODE_SHIFT; //00000000000000000000000000000000  聽說用于系統(tǒng)內(nèi)部,想要多大就給多大。平時也沒有用到過,下面不做分析。
    public static final int EXACTLY     = 1 << MODE_SHIFT; //01000000000000000000000000000000  精確值模式,對應(yīng)LayoutParams的match_parent或者固定尺寸
    public static final int AT_MOST     = 2 << MODE_SHIFT; //10000000000000000000000000000000  最大值模式,對應(yīng)LayoutParams的wrap_content
    
     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);
        }
    }
    public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
    }
    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }

}

對java位運(yùn)算符不太懂的小伙伴可以參考Java位運(yùn)算符淺析,用一句話解釋MeasureSepc:

用位運(yùn)算的方式來"壓縮"記錄View的測量寬高和測量模式,其中高(前)兩位代表測量模式后三十位代表測量后的尺寸。同時提供"解壓"的方法轉(zhuǎn)為我們需要的實(shí)際數(shù)值。

MeasureSpec = MeasureMode+MeasureSize

我們以int mMeausreWidth = makeMeasureSepc(720,MeasureSpec.EXACTLY)為例:

getSize.png

getMode亦是如此

//生成DecorView根View的MeasureSpec
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

measure流程開始執(zhí)行之前,會先計(jì)算出DecorView的MeasureSpec。此處mWidth和mHeight就為屏幕的寬高,LayoutParmas都為match_parent。

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

計(jì)算出DecorView的MeasureSpec后,執(zhí)行DecorView的measure()方法開始整個View樹的測量。

private void performMeasure()(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    if (mView == null) {
        return;
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

measure()方法是被final修飾了的,派生類都不能重寫,所有View都會執(zhí)行到View類的measure()方法。

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

onMeasure()方法意在二種:相對于ViewGroup來說
1.測量出子View的MeasureSpec后,再執(zhí)行子View的measure流程
2.給自己mMeasureWidth&Height賦值。
View的onMeasure()方法就只干第二件事。

我們以下xml布局為例,當(dāng)我們調(diào)用setContentView(R.layout.activity_main)后:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context=".MainActivity">
    <TextView
        android:layout_height="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World"/>
</LinearLayout>
view_tree.png

此時此處DecorView有實(shí)現(xiàn)onMeausre方法并且會執(zhí)行父類FrameLayout的onMeausre()方法。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            //core
            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);
            childState = combineMeasuredStates(childState, child.getMeasuredState());
            if (measureMatchParentChildren) {
                if (lp.width == LayoutParams.MATCH_PARENT ||
                        lp.height == LayoutParams.MATCH_PARENT) {
                    mMatchParentChildren.add(child);
                }
            }
        }
    }
    
    //設(shè)置的前景
    maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
    maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
    
    
    maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
    maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
    
    //設(shè)置的background
    final Drawable drawable = getForeground();
    if (drawable != null) {
    maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
    maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
    }
    //給自己的mMeasuredWidth和mMeasuredHeight賦值
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),resolveSizeAndState(maxHeight, heightMeasureSpec,childState <<MEASURED_HEIGHT_STATE_SHIFT));
}

onMeasure()方法中遍歷所有子View,通過執(zhí)行measureChildWithMargins()方法,先計(jì)算出子View的MeasureSpec再調(diào)用子View的measure()方法傳遞執(zhí)行measure流程。

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);
    //開始LinearLayout的measure流程
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

父View在幫助計(jì)算子View的MeasureSpec時有著固定的套路:
1.受父View的MeasureSpec影響
2.受子View自身的LayoutParams影響
3.計(jì)算父View剩下可用的區(qū)域,減去父View的padding和子View的margin距離和父View已經(jīng)使用(預(yù)定)的區(qū)域大小。

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    
    //父View的寬/高測量模式
    int specMode = MeasureSpec.getMode(spec);
    //父View的寬/高大小
    int specSize = MeasureSpec.getSize(spec);
    
    //父View剩下的可用區(qū)域
    int size = Math.max(0, specSize - padding);

    int resultSize = 0;
    int resultMode = 0;

    switch (specMode) {
    //父View_EXACTLY
    case MeasureSpec.EXACTLY:
        //如果子View寫si了寬/高
        if (childDimension >= 0) {
            //子View的MeasureSpec=EXACTLY+寫si的寬/高(si說多了不吉利)
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            //子View的MeasureSpec=EXACTLY+父View剩下的區(qū)域
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
        
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;
    //父View_AT_MOST
    case MeasureSpec.AT_MOST:
        //如果子View寫死了寬高
        if (childDimension >= 0) {
            //子View的MeasureSpec=EXACTLY+寫si的寬/高
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            //子View的MeasureSpec=AT_MOST+父View剩下的區(qū)域
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;
        
    //父View_UNSPECIFIED從來沒有用到,不做分析
    case MeasureSpec.UNSPECIFIED:
        if (childDimension >= 0) {
        
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
        
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
        
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        }
        break;
    }
    
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

getChildMeasureSpec()生產(chǎn)子View的MeasureSpec總結(jié)如下:
1.子View寫si寬高:測量模式不受父View影響,全都為EXACTLY,寬高為寫si的寬高
2.子View沒有寫si寬高:如果父View都為AT_MOST,子View想都別想還是為AT_MOST,如果父View為EXACTLY且子View的LayoutParams為match_parent,才為EXACTLY。寬高都為父View剩下的區(qū)域。這就很好的明白了為什么我們自定義View時,如果沒對View的寬高進(jìn)行處理,View即使是wrap_content也會撐滿整個屏幕了。

如果我們寫si的尺寸超過了有效范圍,比如超出了屏幕或者超過了父View的大小,最終的measureWidth/Height和實(shí)際寬高還是寫死的尺寸,只不過超出的區(qū)域看不見而已。

ViewGroup在所有子View的measure流程都執(zhí)行結(jié)束后,再調(diào)用setMeasuredDimension()方法給自己的mMeasureWidth/Height賦值。其實(shí)View在執(zhí)行onMeausre()方法之前,已經(jīng)由父View(DecorView除外)計(jì)算出了一個有效的MeasureSpec,比如在執(zhí)行performMeasure()方法之前就先一步計(jì)算出了DecorView的MeasureSpec,接著在measureChildWithMargins()方法中又先計(jì)算出LinearLayout的MeasureSpec,再執(zhí)行LinearLayout的measure()流程。并且View最終的大小都不會超過這個范圍,即使出現(xiàn)以下情況都是如此:
1.在720-1280屏幕下,給View設(shè)置了一張1500-1500的圖片
2.子View的大小已經(jīng)超過了自己
View最終的mMeasureWidth/Height,是由自身的測量模式,前/背景和子View的大小共同決定的。

setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),resolveSizeAndState(maxHeight, heightMeasureSpec,childState <<MEASURED_HEIGHT_STATE_SHIFT));
    
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
    final int specMode = MeasureSpec.getMode(measureSpec);
    
    final int specSize = MeasureSpec.getSize(measureSpec);
    switch (specMode) {
        case MeasureSpec.AT_MOST:
            if (specSize < size) {
                result = specSize | MEASURED_STATE_TOO_SMALL;
            } else {
                result = size;
            }
            break;
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        case MeasureSpec.UNSPECIFIED:
        default:
            result = size;
    }
    return result | (childMeasuredState & MEASURED_STATE_MASK);
}

我們以上面的布局xml為例,LinearLayout的LayoutParams無論是match_parent還是wrap_content,父ViewContentFrameLayout在計(jì)算LinearLayout的MeasureSize的時候,都是屏幕的大?。?/p>

<com.example.yangjie.application.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/ll_test"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    tools:context=".MainActivity">

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    Log.i("TAG","LinearLayoutWidth="+MeasureSpec.getSize(widthMeasureSpec));
    Log.i("TAG","LinearLayoutHeight="+MeasureSpec.getSize(heightMeasureSpec));
    Log.i("TAG","MeasureWidth="+getMeasuredWidth());
}
measureSize.png

當(dāng)LinearLayout的LayoutParams時match_parent時好說,LinearLayout的MeasureMode為EXACTLY,size就是父View幫其計(jì)算出的MeasureSize。如果LinearLayout的LayoutParams為warp_content,在執(zhí)行resolveSizeAndState()方法時會走到case MeasureSpec.AT_MOST:里面去。View最終的寬高會從自身的前/背景大小和子View的大小中選則一個最大值。在FrameLayout中會選出最大的子View的measureWidth/Height,因?yàn)镕rameLayout的子View都是重疊放在左上角的,所以選出最大的那一個就行了。而LinearLayout會累計(jì)所有子View的大小。當(dāng)然如果這個最大值超過了父View為其測量的MeasureSize,最終View的大小還是為父View為其測量的MeasureSize。specSize | MEASURED_STATE_TOO_SMALL;僅僅只是為了標(biāo)記一個這個View的測量狀態(tài),在getMeasureWidth/Height()時值還是不變的。

ViewGroup的onMeausre()方法明白之后,再看View的就簡單多了,給View的mMeasureWidth和Height賦值就行了。

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

如果我們有給View設(shè)置background,getSuggestedMinimumWidth()會獲取該大小,但是getDefaultSize()方法還是會選擇父View幫助測量的MeasureSize。

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

layout流程

相對于measure流程而言,layout和draw流程就簡單得多了,通過Layout流程來確定子View在父View中的位置。子View在父View中的位置,需要4個點(diǎn)來確定,同時也可以通過點(diǎn)的距離來計(jì)算出View的大小。

screen.png

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

public final int getHeight() {
    return mBottom - mTop;
}

performLayout方法中會執(zhí)行DecorView的layout()方法來開始整個View樹的layout流程。而DecorView包括其他的ViewGroup都沒有另外實(shí)現(xiàn)layout()方法,都會執(zhí)行到View的layout()方法。layout()方法中會先執(zhí)行setFrme()方法確定View自己在父View中的位置,接著再執(zhí)行onLayout()方法來遍歷所有的子View,計(jì)算出子View在自己心中的位置(4個點(diǎn))后,再執(zhí)行子View的layout流程。不同的ViewGroup有著不同的方式來安排子View在自己心中的位置。所以View類中的onLayout()是一個空方法,等著View們自己去實(shí)現(xiàn)。自定義ViewGroup的時候如果不在onLayout方法中安排子View的位置,將看不見子View。

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) {
    mLayoutRequested = false;
    mScrollMayChange = true;
    mInLayout = true;

    final View host = mView;
    
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}

public void layout(int l, int t, int r, int b) {
    if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
        onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }
    
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;
    
    boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);
        
    }
}

laout流程,相對于ViewGroup而言:
1.確定自己在父View中的位置
2.遍歷所有子View,計(jì)算出在自己心中的位置(4個點(diǎn))后,再執(zhí)行子View的layout流程
相對于View(單個View)而言只干第一件事。

draw流程

performDraw()方法中會執(zhí)行通過層層調(diào)用會執(zhí)行到View的draw()方法。

private void performDraw() {
    draw(fullRedrawNeeded);
}
private void draw(boolean fullRedrawNeeded) {
    if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                    return;
    }
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,boolean scalingRequired, Rect dirty) {
    mView.draw(canvas);
}
public void draw(Canvas canvas) {
    //繪制自己的背景
    drawBackground(canvas);
    //空實(shí)現(xiàn),繪制自己的內(nèi)容,自定義時重寫該方法
    onDraw(canvas)
    //繪制子View
    dispatchDraw(canvas);
    //繪制前景
    onDrawForeground(canvas);
  
}

draw()方法會繪制一些自己的東西。通過dispatchDraw()方法來傳遞執(zhí)行子View的draw流程。ViewGroup類中已經(jīng)實(shí)現(xiàn):

protected void dispatchDraw(Canvas canvas) {
    more |= drawChild(canvas, child, drawingTime);
}

View的繪制流程到此結(jié)束,不足支持多多包涵,指出共同探討。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,786評論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,964評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,354評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,554評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,106評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,918評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,093評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,342評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,839評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,107評論 2 375

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