View繪制流程解析

一斤寇、目錄

image

二感论、簡介

在理解View繪制流程焚鲜,之前我們先解析下View繪制中的幾大重要成員。
  • Window

    • 介紹:Window表示一個窗口雀费,用來承載View的抽象類干奢。不管是Activity、Dialog還是Toast,它們的視圖實際上都是附加在Window上的坐儿,因此Window實際是View的直接管理者律胀。他的唯一實現(xiàn)是PhoneWindow
    image
  • WindowManager

    • 簡介:WindowManager繼承自ViewManager的接口宋光。WindowManager主要用來管理窗口的一些狀態(tài)貌矿、屬性、view增加罪佳、刪除、更新、窗口順序第股、消息收集和處理等。
    image
  • ViewRootImpl

    • 簡介:ViewRootImpl是View中的最高層級克握,屬于所有View的根(但ViewRootImpl不是View,只是實現(xiàn)了ViewParent接口)枷踏,是實現(xiàn) View 的繪制的類菩暗,它實現(xiàn)了View和WindowManager之間的通信協(xié)議。大多數(shù)情況下旭蠕,他也是WindowManagerGloable的內(nèi)部功能具體實現(xiàn)
  • DecorView

    • DecorView是Activity內(nèi)所有View的父布局停团,DecorView 是 FrameLayout 的子類,F(xiàn)rameLayout 是 GroupView 的子類掏熬,所以DecorView 是一個 GroupView佑稠。內(nèi)部包含上面是標(biāo)題,下面是內(nèi)容欄(id是content)

三旗芬、Activity舌胶、PhoneWindow、DecorView三者之間的關(guān)系

每一個Acyiviy 包含一個Window對象疮丛,這個window對象是phoneWindow
PhoneWindow內(nèi)包含一個DecorView幔嫂。同時,PhoneWindow也是Activity和View系統(tǒng)交互的接口誊薄。DecorView是整個窗口內(nèi)的所有View的跟布局
DecorView將屏幕劃分兩個區(qū)域:1.titleView 2.ContentView
DecorView本質(zhì)上是一個FrameLayout婉烟,是Activity中所有View的祖先
而我們平時所寫的就是展示在ContentView中的,下圖表示Activity的構(gòu)成暇屋。
image

四似袁、DecorView的加載

從Activity的startActivity開始,最終調(diào)用到ActivityThread的handleLaunchActivity方法來創(chuàng)建Activity咐刨,相關(guān)核心代碼如下:
  • handleLaunchActivity
handleLaunchActivity的主要操作
1昙衅、初始化WindowManagerService
2、實例化Activity定鸟、初始化Activity的Window等參數(shù)而涉、回調(diào)Activity的onCreate()
3、執(zhí)行Acttivity的onResume()
 private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
        ...略
        // 初始化WindowManagerService
        WindowManagerGlobal.initialize();
        //實例化Activity联予,初始化Activity的Window等參數(shù)并回調(diào)Activity的onCreate啼县、onStart
        Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            reportSizeConfigurations(r);
            Bundle oldState = r.state;
            //執(zhí)行Activity的onResume
            handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
        ...略            
        } else {
            ...略
        }
    }
  • performLaunchActivity
performLaunchActivity的操作步驟:
1、獲取Activity信息
2沸久、通過反射獲取Activity的類季眷,并創(chuàng)建Activity
4、獲取Application
5卷胯、初始化Activity的成員變量包括Window子刮、Application、和mainThread等窑睁。初始化PhoneWindow的構(gòu)造函數(shù)的內(nèi)部初始化了PhoneWindow的DecorView挺峡。
6葵孤、回調(diào)Activity的onCreate方法
    通常Activity的onCreate的內(nèi)我們會調(diào)用setContentView
    而setContentView 內(nèi)部調(diào)用PhoneWindow的setContentView,
    PhoneWindow的setContentView通過LayoutInflater.inflate()將我們的布局加載到DecorView中
7橱赠、如果Activity沒有Finish則調(diào)用Activity的onStart()
8尤仍、設(shè)置Activity的savedInstanceState
9、設(shè)置Window的title
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
       
        //1狭姨、獲取Activity信息
        ActivityInfo aInfo = r.activityInfo;
        if (r.packageInfo == null) {
            r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                    Context.CONTEXT_INCLUDE_CODE);
        }

       ...略
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            //2吓著、通過反射獲取到activity類,并創(chuàng)建Activity實例
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            //3、如果沒在清單里注冊就會出現(xiàn)這個錯誤
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }

        try {
            //4送挑、獲取Application
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
            if (activity != null) {
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
                ...略
                appContext.setOuterContext(activity);
                //5绑莺、初始化Activity的成員變量包括Window、Application惕耕、和mainThread等
                //         初始化PhoneWindow的構(gòu)造函數(shù)的內(nèi)部初始化了PhoneWindow的DecorView纺裁。

                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);

                if (customIntent != null) {
                    activity.mIntent = customIntent;
                }
                r.lastNonConfigurationInstances = null;
                checkAndBlockForNetworkAccess();
                activity.mStartedActivity = false;
                int theme = r.activityInfo.getThemeResource();
                if (theme != 0) {
                    activity.setTheme(theme);
                }

                activity.mCalled = false;
                //6、回調(diào)Activity的onCreate方法
                // 通常Activity的onCreate的內(nèi)我們會調(diào)用setContentView
                // 而setContentView 內(nèi)部調(diào)用PhoneWindow的setContentView司澎,
                // PhoneWindow的setContentView通過LayoutInflater.inflate()將我們的布局加載到DecorView中
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
                if (!activity.mCalled) {
                    throw new SuperNotCalledException(
                        "Activity " + r.intent.getComponent().toShortString() +
                        " did not call through to super.onCreate()");
                }
                r.activity = activity;
                r.stopped = true;
                //7欺缘、如果Activity沒有Finish則調(diào)用Activity的onStart()
                if (!r.activity.mFinished) {
                    activity.performStart();
                    r.stopped = false;
                }
                // 8、設(shè)置Activity的savedInstanceState
                if (!r.activity.mFinished) {
                    if (r.isPersistable()) {
                        if (r.state != null || r.persistentState != null) {
                            mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
                                    r.persistentState);
                        }
                    } else if (r.state != null) {
                        mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
                    }
                }
                //9挤安、設(shè)置Window的title
                if (!r.activity.mFinished) {
                    activity.mCalled = false;
                    if (r.isPersistable()) {
                        mInstrumentation.callActivityOnPostCreate(activity, r.state,
                                r.persistentState);
                    } else {
                        mInstrumentation.callActivityOnPostCreate(activity, r.state);
                    }
                    if (!activity.mCalled) {
                        throw new SuperNotCalledException(
                            "Activity " + r.intent.getComponent().toShortString() +
                            " did not call through to super.onPostCreate()");
                    }
                }
            }
            r.paused = true;

            mActivities.put(r.token, r);

        } catch (SuperNotCalledException e) {
            throw e;

        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to start activity " + component
                    + ": " + e.toString(), e);
            }
        }

        return activity;
    }
  • handleResumeActivity
handleResumeActivity操作步驟:
1谚殊、執(zhí)行Activity的onResume
2、調(diào)用 WindowManager的addView(decor, l);
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) {
    unscheduleGcIdler();
    mSomeActivitiesChanged = true;
    // 調(diào)用Activity的onResume方法
    ActivityClientRecord r = performResumeActivity(token, clearHide);
    if (r != null) {
        final Activity a = r.activity;
        ...
        if (r.window == null &&& !a.mFinished && willBeVisible) {
            //獲取Activity的Window
            r.window = r.activity.getWindow();
            //獲取Window的DecorView
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            //獲取Activity的WindowManagerImpl
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            //將Window的DecorView賦值個Activity的mDecor
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (a.mVisibleFromClient) {
                a.mWindowAdded = true;
                // WindowManager的實現(xiàn)類是WindowManagerImpl蛤铜,
                // 所以實際調(diào)用的是WindowManagerImpl的addView方法
                wm.addView(decor, l);
            }
        }
    }
}
  • WindowManager的addView(View view, ViewGroup.LayoutParams params)

實際上WindowManager的實現(xiàn)類時WindowManagerImpl嫩絮,而WindowManagerImpl的addView()方法內(nèi)部,調(diào)用了WindowManagerGlobal的addView围肥。所以接下來分析WindowManagerGlobal的addView剿干。源碼如下:

// WindowManagerGlobal的addView方法
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
    ...
    ViewRootImpl root;
    View pannelParentView = null;
    synchronized (mLock) {
        ...
        // 創(chuàng)建ViewRootImpl實例
        root = new ViewRootImpl(view..getContext(), display);
        view.setLayoutParams(wparams);
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
    }
    try {
        // 把DecorView加載到Window中,觸發(fā)View的測量穆刻、布局置尔、繪制
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
        synchronized (mLock) {
            final int index = findViewLocked(view, false);
            if (index >= 0) {
                removeViewLocked(index, true);
            }
        }
        throw e;
    }
}
// ViewRootImpl的方法
 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
        ...
            requestLayout();        
        ...
            
        }
    }

public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
           ...
            scheduleTraversals();
        }
    }
 void scheduleTraversals() {
        if (!mTraversalScheduled) {
            ...
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
           ...
        }
    }
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
 void doTraversal() {
        if (mTraversalScheduled) {
            ...
            performTraversals();
            ...
        }
    }

最終ViewRootImpl的setView方法利用執(zhí)行Runable的方式執(zhí)行了performTraversals();

  • DecorView加載程序流執(zhí)行程圖
image

五、View繪制整體流程

View的繪制最終是從ViewRootImpl的performTraversals()方法開始氢伟,從上到下遍歷整個視圖樹榜轿,每個View控件負(fù)責(zé)繪制自己,而ViewGroup還需要負(fù)責(zé)通知自己的子View進(jìn)行繪制操作朵锣。ViewRootImpl的performTraversals()方法的主要源碼如下:
private void performTraversals() {
    ...
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    ...
    //執(zhí)行測量流程
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
    //執(zhí)行布局流程
    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    ...
    //執(zhí)行繪制流程
    performDraw();
}

流程圖如下:


image
  • 注:preformLayout和performDraw的傳遞流程和performMeasure是類似的谬盐,唯一不同的是,performDraw的傳遞過程是在draw方法中通過dispatchDraw來實現(xiàn)的猪勇,不過這并沒有本質(zhì)區(qū)別设褐。
  • 獲取content:
ViewGroup content = (ViewGroup)findViewById(android.R.id.content);

六、MeasureSpec

MeasureSpec是View類的一個靜態(tài)內(nèi)部類泣刹,用來說明應(yīng)該如何測量這個View助析。MeasureSpec代表一個32位的int值,前倆位代表SpecMode椅您,后30位代表SpecSize.
其中:SpecMode代表測量的模式外冀,SpecSize值在某種測量模式下的規(guī)格大小。
  • 三個測量模式說明

    • EXACTLY:英文含義是精確的掀泳。精確測量模式雪隧,這時候View的大小是SpecSize的大小。對應(yīng)布局參數(shù)是 MATCH_PARENT或精確值大小

    • AT_MOST:英文含義是至多员舵。最大值測量模式脑沿,View的尺寸是不超過父布局最大尺寸的任意尺寸。對應(yīng)布局參數(shù)wrap_content马僻。

    • UNSPECIFIED:英文含義未指定的庄拇。不指定測量模式,父視圖沒有限制子視圖的大小韭邓,子視圖可以是想要的任何尺寸措近,通常用于系統(tǒng)內(nèi)部,應(yīng)用開發(fā)中很少用到女淑。

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

    // 根據(jù)指定的大小和模式創(chuàng)建一個MeasureSpec
    public static int makeMeasureSpec(int size, int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }
    //當(dāng) mode == UNSPECIFIED 時返回0的MeasureSpec瞭郑,內(nèi)部調(diào)用makeMeasureSpec,
    public static int makeSafeMeasureSpec(int size, int mode) {
            if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
                return 0;
            }
            return makeMeasureSpec(size, mode);
        }
    //獲取測量模式
    public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
    }

    獲取測量大小
    public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
    }

    // 微調(diào)某個MeasureSpec的大小
    static int adjust(int measureSpec, int delta) {
        final int mode = getMode(measureSpec);
        if (mode == UNSPECIFIED) {
            // No need to adjust size for UNSPECIFIED mode.
            return makeMeasureSpec(0, UNSPECIFIED);
        }
        int size = getSize(measureSpec) + delta;
        if (size < 0) {
            size = 0;
        }
        return makeMeasureSpec(size, mode);
    }
}
  • DecorView的MeasureSpec的創(chuàng)建
DecorView的MeasureSpec的創(chuàng)建是在ViewRootImpl在測量DecorView之前獲取的的鸭你。獲取源碼如下:
performTraversals(){
    ...
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    ...
    
}

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

總結(jié):DecorView的MeasureSpec是由窗口大小和其自身的LayoutParams共同決定

  • 普通View的MeasureSpec的創(chuàng)建
普通View的MeasureSpec的創(chuàng)建是在父View在測量子View時獲取
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);
    }
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        //父View獲取自身的測量模式和大小
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
        //獲取在父View的大小中除了自身padding屈张、margin的大小
        int size = Math.max(0, specSize - padding);
    
        int resultSize = 0;
        int resultMode = 0;
        
        switch (specMode) {
        // 如何父布局大小是精確的大小或者是MATCH_PARENT
        case MeasureSpec.EXACTLY:
            //如果子View自身的LayoutParam的大小>0
            //則子View大大小是子View自身的LayoutParam的大小
            //測量模式是EXACTLY
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // 如果子View的大小是MATCH_PARENT
                //則 子View的大小是父View的除去padding和margin的大小
                //測量模式是EXACTLY
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                //如果子View的大小是WRAP_CONTENT
                //則子View的大小是子View的大小是父View的除去padding和margin的大小
               //測量模式是AT_MOST
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        //  // 如何父布局大小是精確的大小或者是WARP_CONTENT
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                //如果子View自身的LayoutParam的大小>0
            //則子View大大小是子View自身的LayoutParam的大小
            //測量模式是EXACTLY
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // 如果子View的大小是MATCH_PARENT
                //則 子View的大小是父View的除去padding和margin的大小
                //測量模式是AT_MOST
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                //如果子View的大小是WRAP_CONTENT
                //則子View的大小是子View的大小是父View的除去padding和margin的大小
               //測量模式是AT_MOST
                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);
    }

總結(jié):對于普通的View,它的MeasureSpec由父視圖的MeasureSpec和其自身的LayoutParams共同決定袱巨。UNSPECIFIED模式主要用于系統(tǒng)內(nèi)部多次Measure的情形袜茧,一般不需關(guān)注。

七瓣窄、View的measure流程

private void perormMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    ...
    // 具體的測量操作分發(fā)給ViewGroup
    mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
}

// 在ViewGroup中的measureChildren()方法中遍歷測量ViewGroup中所有的View
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        // 當(dāng)View的可見性處于GONE狀態(tài)時笛厦,不對其進(jìn)行測量
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}

// 測量某個指定的View
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();

    // 根據(jù)父容器的MeasureSpec和子View的LayoutParams等信息計算
    // 子View的MeasureSpec
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height);
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

// View的measure方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    // ViewGroup沒有定義測量的具體過程,因為ViewGroup是一個
    // 抽象類俺夕,其測量過程的onMeasure方法需要各個子類去實現(xiàn)
    onMeasure(widthMeasureSpec, heightMeasureSpec);
    ...
}

// 不同的ViewGroup子類有不同的布局特性裳凸,這導(dǎo)致它們的測量細(xì)節(jié)各不相同,如果需要自定義測量過程劝贸,則子類可以重寫這個方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // setMeasureDimension方法用于設(shè)置View的測量寬高
    setMeasureDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), 
    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

// 如果View沒有重寫onMeasure方法姨谷,則會默認(rèn)調(diào)用getDefaultSize來獲得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:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = sepcSize;
            break;
    }
    return result;
}

  • 對getSuggestMinimumWidth的分析
protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinmumWidth());
}

protected int getSuggestedMinimumHeight() {
    return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}

public int getMinimumWidth() {
    final int intrinsicWidth = getIntrinsicWidth();
    return intrinsicWidth > 0 ? intrinsicWidth : 0;
}

如果View沒有設(shè)置背景,那么返回android:minWidth這個屬性所指定的值映九,這個值可以為0梦湘;如果View設(shè)置了背景,則返回android:minWidth和背景的最小寬度這兩者中的最大值。

  • 在Activity中獲取某個View的寬高

    由于View的measure過程和Activity的生命周期方法不是同步執(zhí)行的捌议,如果View還沒有測量完畢哼拔,那么獲得的寬/高就是0。所以在onCreate瓣颅、onStart倦逐、onResume中均無法正確得到某個View的寬高信息。解決方式如下:

    1宫补、Activity/View#onWindowFocusChanged

    // 此時View已經(jīng)初始化完畢
    // 當(dāng)Activity的窗口得到焦點和失去焦點時均會被調(diào)用一次
    // 如果頻繁地進(jìn)行onResume和onPause檬姥,那么onWindowFocusChanged也會被頻繁地調(diào)用
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if (hasFocus) {
            int width = view.getMeasureWidth();
            int height = view.getMeasuredHeight();
        }
    }
    

    2、view.post(runnable)

    // 通過post可以將一個runnable投遞到消息隊列的尾部粉怕,// 然后等待Looper調(diào)用此runnable的時候健民,View也已經(jīng)初
    // 始化好了
    protected void onStart() {
    super.onStart();
    view.post(new Runnable() {
    
        @Override
        public void run() {
            int width = view.getMeasuredWidth();
            int height = view.getMeasuredHeight();
        }
    });
    }
    

    3、ViewTreeObserver

    // 當(dāng)View樹的狀態(tài)發(fā)生改變或者View樹內(nèi)部的View的可見// 性發(fā)生改變時贫贝,onGlobalLayout方法將被回調(diào)
    protected void onStart() {
        super.onStart();
    
        ViewTreeObserver observer = view.getViewTreeObserver();
        observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
    
            @SuppressWarnings("deprecation")
            @Override
            public void onGlobalLayout() {
                view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                int width = view.getMeasuredWidth();
                int height = view.getMeasuredHeight();
            }
        });
    }
    

    4秉犹、View.measure(int widthMeasureSpec, int heightMeasureSpec)

八、View的Layou過程

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

// View.java
public void layout(int l, int t, int r, int b) {
    ...
    // 通過setFrame方法來設(shè)定View的四個頂點的位置平酿,即View在父容器中的位置
    boolean changed = isLayoutModeOptical(mParent) ? 
    set OpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

    ...
    onLayout(changed, l, t, r, b);
    ...
}

// 空方法凤优,子類如果是ViewGroup類型,則重寫這個方法蜈彼,實現(xiàn)ViewGroup
// 中所有View控件布局流程
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

}

九筑辨、View的Draw過程

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

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

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

// 繪制基本上可以分為六個步驟
public void draw(Canvas canvas) {
    ...
    // 步驟一:繪制View的背景
    drawBackground(canvas);

    ...
    // 步驟二:如果需要的話,保持canvas的圖層幸逆,為fading做準(zhǔn)備
    saveCount = canvas.getSaveCount();
    ...
    canvas.saveLayer(left, top, right, top + length, null, flags);

    ...
    // 步驟三:繪制View的內(nèi)容
    onDraw(canvas);

    ...
    // 步驟四:繪制View的子View
    dispatchDraw(canvas);

    ...
    // 步驟五:如果需要的話棍辕,繪制View的fading邊緣并恢復(fù)圖層
    canvas.drawRect(left, top, right, top + length, p);
    ...
    canvas.restoreToCount(saveCount);

    ...
    // 步驟六:繪制View的裝飾(例如滾動條等等)
    onDrawForeground(canvas)
}
  • setWillNotDraw的作用

    // 如果一個View不需要繪制任何內(nèi)容,那么設(shè)置這個標(biāo)記位為true以后还绘,
    // 系統(tǒng)會進(jìn)行相應(yīng)的優(yōu)化楚昭。
    public void setWillNotDraw(boolean willNotDraw) {
        setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
    }
    
    • 認(rèn)情況下,View沒有啟用這個優(yōu)化標(biāo)記位拍顷,但是ViewGroup會默認(rèn)啟用這個優(yōu)化標(biāo)記位抚太。

    • 件繼承于ViewGroup并且本身不具備繪制功能時,就可以開啟這個標(biāo)記位從而便于系統(tǒng)進(jìn)行后續(xù)的優(yōu)化昔案。

    • 個ViewGroup需要通過onDraw來繪制內(nèi)容時尿贫,我們需要顯示地關(guān)閉WILL_NOT_DRAW這個標(biāo)記位

[原文參考鏈接]https://jsonchao.github.io/2018/10/28/Android%20View%E7%9A%84%E7%BB%98%E5%88%B6%E6%B5%81%E7%A8%8B/)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市踏揣,隨后出現(xiàn)的幾起案子庆亡,更是在濱河造成了極大的恐慌,老刑警劉巖捞稿,帶你破解...
    沈念sama閱讀 222,865評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件又谋,死亡現(xiàn)場離奇詭異拼缝,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)彰亥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,296評論 3 399
  • 文/潘曉璐 我一進(jìn)店門咧七,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人剩愧,你說我怎么就攤上這事猪叙〗空叮” “怎么了仁卷?”我有些...
    開封第一講書人閱讀 169,631評論 0 364
  • 文/不壞的土叔 我叫張陵,是天一觀的道長犬第。 經(jīng)常有香客問我锦积,道長,這世上最難降的妖魔是什么歉嗓? 我笑而不...
    開封第一講書人閱讀 60,199評論 1 300
  • 正文 為了忘掉前任丰介,我火速辦了婚禮,結(jié)果婚禮上鉴分,老公的妹妹穿的比我還像新娘哮幢。我一直安慰自己,他們只是感情好志珍,可當(dāng)我...
    茶點故事閱讀 69,196評論 6 398
  • 文/花漫 我一把揭開白布橙垢。 她就那樣靜靜地躺著,像睡著了一般伦糯。 火紅的嫁衣襯著肌膚如雪柜某。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,793評論 1 314
  • 那天敛纲,我揣著相機(jī)與錄音喂击,去河邊找鬼。 笑死淤翔,一個胖子當(dāng)著我的面吹牛翰绊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播旁壮,決...
    沈念sama閱讀 41,221評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼监嗜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了寡具?” 一聲冷哼從身側(cè)響起秤茅,我...
    開封第一講書人閱讀 40,174評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎童叠,沒想到半個月后框喳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體课幕,經(jīng)...
    沈念sama閱讀 46,699評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,770評論 3 343
  • 正文 我和宋清朗相戀三年五垮,在試婚紗的時候發(fā)現(xiàn)自己被綠了乍惊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,918評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡放仗,死狀恐怖润绎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情诞挨,我是刑警寧澤莉撇,帶...
    沈念sama閱讀 36,573評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站惶傻,受9級特大地震影響棍郎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜银室,卻給世界環(huán)境...
    茶點故事閱讀 42,255評論 3 336
  • 文/蒙蒙 一涂佃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蜈敢,春花似錦辜荠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,749評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至辐宾,卻和暖如春狱从,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背叠纹。 一陣腳步聲響...
    開封第一講書人閱讀 33,862評論 1 274
  • 我被黑心中介騙來泰國打工季研, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人誉察。 一個月前我還...
    沈念sama閱讀 49,364評論 3 379
  • 正文 我出身青樓与涡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親持偏。 傳聞我的和親對象是個殘疾皇子驼卖,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,926評論 2 361

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