美文网首页Android自定义Viewview绘制
View的绘制流程 - onMeasure()源码分析

View的绘制流程 - onMeasure()源码分析

作者: 世道无情 | 来源:发表于2018-03-27 08:26 被阅读25次

    前言

    View绘制流程系列文章
    View的绘制流程 - onMeasure()源码分析
    View的绘制流程 - onLayout()源码分析
    View的绘制流程 - onDraw()源码分析

    结论


    View的绘制流程都是从ViewRootImpl中的requestLayout()方法开始进去的,performMeasure()、performLayout()、performDraw(),而如果代码中又写了这样的代码:addView()、setVisibility()等方法,意思就是会重新执行requestLayout(),意思就是会重新执行View的绘制流程,这个时候执行View的绘制流程时不会和第一次一样去执行所有的逻辑,比如说你自己addView(),一次性添加了10个View,那么它有可能等你添加完毕之后才去执行 View的绘制流程的:

    测量是从外往里递归,也就是说:
    ViewRootImpl会把自己的测量模式传递给 -> DecorView,然后DecorView会把自己的测量模式传递给 activity_main中的LinearLayout ->
    然后LinearLayout通过for循环把自己的测量模式传递给 TextView,然后就会调用 TextView的onMeasure()方法,然后根据传递过来的LinearLayout的测量模式来指定 TextView的宽高,测量完毕后通过childHeight = child.getMeasuredHeight();获取到 子View的宽高,即就是获取到3个TextView的宽高后,来计算父布局,即就是LinearLayout
    自己的宽高 ,然后再把自己的宽高向外传递给DecorView ->
    然后DecorView根据 LinearLayout传回来的宽高,然后计算自己的宽高 , 把自己宽高计算好后,然后再把自己的宽高向外传递给 ViewRootImpl 。


    onMeasure()源码分析总结如下:
    测量是从外往里递归的:
    从外往里:

    首先从最外层的 ViewRootImpl开始,它把它的 测量模式传递给 DecorView,然后DecorView把自己的测量模式 传递给 父布局LinearLayout,然后LinearLayout再把自己的测量模式 传递给 子View;

    从里往外:

    等子View计算出自己宽高后,然后把自己宽高传递给父布局LinearLayout,然后LinearLayout根据 子View的宽高,来计算自己的宽高,这里计算方式就是:
    如果父布局是 LinearLayout,且是垂直方向,父布局高度就是累加子布局高度;
    如果父布局是RelativeLayout,那么父布局高度就是指定 子孩子中最高的;
    ,然后LinearLayout把自己的高度传递给 它的父布局,就是这样一路都把自己的高度传递给父布局,最后传递给 DecorView、传递给 ViewRootImpl,

    onMeasure()源码中就是这样测量的,如下图所示:
    onMeasure()源码分析.png

    下边进行分析,最下边的结论可以不看,因为和上边这个一样,下边仅用于分析流程。

    1. 说明


    这节课来看下View的绘制流程,我们由下边的套路来一步一步引出并分析View的绘制流程 —— 根据一个小示例,如何能获取mTextViewHeight高度,来引出setContentView到底做了什么、Activity的启动流程、最后引出View的绘制流程(即就是分析onMeasure()、onLayout()、onDraw());

    2. 代码如下

    public class MainActivity extends AppCompatActivity {
    
        private TextView text_view;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
    
            // 下边这个获取不到view的高度,因为参数3是null,即就是父布局是null,说明你还没有把activity_main添加到父布局中,所以不能获取到宽高
            View view = View.inflate(this, R.layout.activity_main, null);
    
    
            // 这个可以获取到宽高,因为 参数3ViewGroup表示父布局,下边代码就表示,你已经把activity_main布局添加到父布局中了,所以可以获取到宽高
    //        View view = View.inflate(this, R.layout.activity_main, ViewGroup);
    
    
            text_view = (TextView) findViewById(R.id.text_view);
            Log.e("TAG" , "height1 -> " + text_view.getMeasuredHeight()) ;   // 0
    
            text_view.post(new Runnable() {
                @Override
                public void run() {
                    Log.e("TAG" , "height2 -> " + text_view.getMeasuredHeight()) ;  // 高度:51
                }
            }) ;
        }
    
    
        @Override
        protected void onResume() {
            super.onResume();
            Log.e("TAG" , "height3 -> " + text_view.getMeasuredHeight()) ;  // 0
        }
    }
    

    View view = View.inflate(this, R.layout.activity_main, null)为什么获取不到高度?

    参数3表示父布局,而这里的参数3是null,表示没有把activity_main添加到父布局中,所以不能获取到宽高;

    View view = View.inflate(this, R.layout.activity_main, ViewGroup)为什么可以获取到高度?

    参数3表示父布局,这里的参数3是 ViewGroup,表示父布局,这里为了形象表示就直接把父布局写成了ViewGroup,其实只要是父布局就行。这里就表示把activity_main添加到父布局中,所以可以获取到高度;

    分析其余3个mTextViewHeight的高度:

    由以上可知:

    03-19 21:29:23.491 18696-18696/? E/TAG: height1 -> 0
    03-19 21:29:23.492 18696-18696/? E/TAG: height3 -> 0
    03-19 21:29:23.591 18696-18696/? E/TAG: height2 -> 51
    

    height1 = 0;height3 = 0 ;height2 = 51(高度)
    分析原因:
    我们需要知道,我们在onCreate()方法中只是调用了setContentView(),也需要知道setContentView()到底干了什么?
    在PhoneWindow中,setContentView只是new DecorView()

    之所以能够拿到控件的宽高,是因为调用了onMeasure()方法,而在我们之前写的那些自定义View效果的时候,其实都是在 onMeasure()方法中获取到宽高后,都会重新调用setMeasuredDimension(width , height);
    setContentView 只是创建DecorView,并且把我们的布局加载进DecorView,并没有调用onMeasure()方法;

    分析PhoneWindow的源码如下:

    @Override
        public void setContentView(int layoutResID) {
            // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
            // decor, when theme attributes and the like are crystalized. Do not check the feature
            // before this happens.
            if (mContentParent == null) {
                installDecor();
            } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
                mContentParent.removeAllViews();
            }
    
            if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
                final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                        getContext());
                transitionTo(newScene);
            } else {
                mLayoutInflater.inflate(layoutResID, mContentParent);
            }
            mContentParent.requestApplyInsets();
            final Callback cb = getCallback();
            if (cb != null && !isDestroyed()) {
                cb.onContentChanged();
            }
            mContentParentExplicitlySet = true;
        }
    

    只要installDecor()方法执行完,就会形成这样一个局面:


    图片.png
    onCreate()中为什么获取不到 mTextViewHeight 高度?

    因为在PhoneWindow中,setContentView()只是new DecorView(),然后把我们的布局加载到了DecorView(),其余什么都没做,也并没有调用onMeasure()方法,所以在onCreate()方法中不能获取到TextView的宽高;


    onResume()中为什么也获取不到 mTextViewHeight 高度?

    这个其实就涉及到Activity的启动流程的分析,通过下边对Activity启动流程的分析,即就是分析 ActivityThread源码,可以知道:
    Activity的启动流程是:
    先调用handleLaunchActivity(),在这个方法中调用performLaunchActivity(),在performLaunchActivity()中会调用onCreate() ->
    然后调用handleResumeActivity(),在这个方法中调用performResumeActivity() ->
    然后调用Activity的onResume() ->
    然后调用 wm.addView(decor , 1) ,这个时候才开始把我们的DecorView 加载到 WindowManager中,View的绘制流程在这个时候才刚刚开始,才开始onMeasure()(测量)、onLayout()(摆放)、onDraw()(绘制)draw()自己、draw()孩子;

    所以说View的绘制流程是在onResume()方法之后才开始,所以说在onResume()方法中也是不能获取 mTextViewHeight高度的,必须要等调用完onResume()之后,才可以获取宽高的。

    下边的text_view.post为什么可以获取到宽高?
    text_view.post(new Runnable() {
                @Override
                public void run() {
                    Log.e("TAG" , "height2 -> " + text_view.getMeasuredHeight()) ;  // 高度:51
                }
            }) ;
    

    源码分析:
    View中源码:

    public boolean post(Runnable action) {
            final AttachInfo attachInfo = mAttachInfo;
            if (attachInfo != null) {
                return attachInfo.mHandler.post(action);
            }
            getRunQueue().post(action);
            return true;
        }
    

    View中源码:

    public void post(Runnable action) {
            postDelayed(action, 0);
        }
    
        public void postDelayed(Runnable action, long delayMillis) {
            final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
    
            synchronized (this) {
                if (mActions == null) {
                    mActions = new HandlerAction[4];
                }
                mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
                mCount++;
            }
        }
    

    View中源码:

        /**
         * @param info the {@link android.view.View.AttachInfo} to associated with
         *        this view
         */
        void dispatchAttachedToWindow(AttachInfo info, int visibility) {
            mAttachInfo = info;
            if (mOverlay != null) {
                mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
            }
            mWindowAttachCount++;
            // We will need to evaluate the drawable state at least once.
            mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
            if (mFloatingTreeObserver != null) {
                info.mTreeObserver.merge(mFloatingTreeObserver);
                mFloatingTreeObserver = null;
            }
            // Transfer all pending runnables.
            if (mRunQueue != null) {
                mRunQueue.executeActions(info.mHandler);
                mRunQueue = null;
            }
            performCollectViewAttributes(mAttachInfo, visibility);
            onAttachedToWindow();
        }
    

    HandlerActionQueue源码:

    public void executeActions(Handler handler) {
            synchronized (this) {
                final HandlerAction[] actions = mActions;
                for (int i = 0, count = mCount; i < count; i++) {
                    final HandlerAction handlerAction = actions[i];
                    handler.postDelayed(handlerAction.action, handlerAction.delay);
                }
    
                mActions = null;
                mCount = 0;
            }
        }
    

    这里只是把Runnable保存到Queue中,什么都没干,run()方法会在dispatchAttachedToWindow()方法会在测量完毕然后调用executeActions()方法,即就是onMeasure()方法之后调用executeActions()方法,所以只要一调用text_view.post(new Runnable()) ,就马上可以获取宽高。

    3:Activity的启动流程?

    这是ActivityThread源码

    private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
            // If we are getting ready to gc after going to the background, well
            // we are back active so skip it.
            unscheduleGcIdler();
            mSomeActivitiesChanged = true;
    
            if (r.profilerInfo != null) {
                mProfiler.setProfiler(r.profilerInfo);
                mProfiler.startProfiling();
            }
    
            // Make sure we are running with the most recent config.
            handleConfigurationChanged(null, null);
    
            if (localLOGV) Slog.v(
                TAG, "Handling launch of " + r);
    
            // Initialize before creating the activity
            WindowManagerGlobal.initialize();
    
            Activity a = performLaunchActivity(r, customIntent);
    
            if (a != null) {
                r.createdConfig = new Configuration(mConfiguration);
                reportSizeConfigurations(r);
                Bundle oldState = r.state;
                handleResumeActivity(r.token, false, r.isForward,
                        !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
            } else {
            }
        }
    
    final void handleResumeActivity(IBinder token,
                boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
            ActivityClientRecord r = mActivities.get(token);
            if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
                return;
            }
    
            // TODO Push resumeArgs into the activity for consideration
            r = performResumeActivity(token, clearHide, reason);
    
            if (r != null) {
                final Activity a = r.activity;                 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
    
                if (!willBeVisible) {
                    try {
                        willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
                                a.getActivityToken());
                    } catch (RemoteException e) {
                        throw e.rethrowFromSystemServer();
                    }
                }
                    }
                    if (a.mVisibleFromClient && !a.mWindowAdded) {
                        a.mWindowAdded = true;
                        wm.addView(decor, l);
                    }
                }
        }
    

    分析ActivityThread源码可知:
    先调用handleLaunchActivity(),在这个方法中调用performLaunchActivity(),在performLaunchActivity()中会调用onCreate() ->
    然后调用handleResumeActivity(),在这个方法中调用performResumeActivity() ->
    然后调用Activity的onResume() ->
    然后调用 wm.addView(decor , 1) ,这个时候才开始把我们的DecorView 加载到 WindowManager中,View的绘制流程在这个时候才刚刚开始,也就是说在这个时候才开始onMeasure()(测量)、onLayout()(摆放)、onDraw()(绘制)draw()自己、draw()孩子;

    所以说View的绘制流程是在 onResume()之后才开始,如果我们以后想要获取控件的宽高的话,就必须等调用完onResume()之后,再去获取宽高就可以。

    自定义View的入口就是ViewRootImpl中的requestLayout()方法,所以先来看下ViewRootImpl的关系,如下图所示:

    ViewRootImpl包裹着DecorView.png

    在WindowManagerImpl源码中:

    public void addView(View view, ViewGroup.LayoutParams params,
                Display display, Window parentWindow) {
            if (!(params instanceof WindowManager.LayoutParams)) {
                throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
            }
    
            final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    
            ViewRootImpl root;
            View panelParentView = null;
    
                root = new ViewRootImpl(view.getContext(), display);
    
                view.setLayoutParams(wparams);
    
                mViews.add(view);
                mRoots.add(root);
                mParams.add(wparams);
            }
    
            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                }
                throw e;
            }
        }
    

    分析以上源码可知:
    wm.addView(decor , 1) ->
    调用WindowManagerImpl.addView() ->
    然后调用root.setView(view, wparams, panelParentView)方法 ->
    调用requestLayout() -> 调用scheduleTraversals() ->
    调用doTraversal() -> performTraversals() (网上的文章都是从这个方法开始讲解的)

    4. 开始View的绘制流程

    1>:onMeasure()源码分析:

    ViewRootImpl源码如下:

        /**
         * We have one child
         */
        public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
            synchronized (this) {
                if (mView == null) {
                    mView = view;
    
                    mAttachInfo.mDisplayState = mDisplay.getState();
                    mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
    
                    // 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();
                }
            }
        }
    
    
    @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);
                if (!mUnbufferedInputDispatch) {
                    scheduleConsumeBatchedInput();
                }
                notifyRendererOfFramePending();
                pokeDrawLockIfNeeded();
            }
        }
    
    final class TraversalRunnable implements Runnable {
            @Override
            public void run() {
                doTraversal();
            }
        }
    
    void doTraversal() {
            if (mTraversalScheduled) {
                mTraversalScheduled = false;
                mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
                performTraversals();
            }
        }
    
    private void performTraversals() {
            // cache mView since it is used so much below...
            final View host = mView;
    
            if (DBG) {
                System.out.println("======================================");
                System.out.println("performTraversals");
                host.debug();
            }
    
                        if (measureAgain) {
                            if (DEBUG_LAYOUT) Log.v(mTag,
                                    "And hey let's measure once more: width=" + width
                                    + " height=" + height);
                            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                        }
                }
            } else 
            if (didLayout) {
                performLayout(lp, mWidth, mHeight);
                }
    
                performDraw();
            } else {
                
            }
    
            mIsInTraversal = false;
        }
    

    LinearLayout的onMeasure()源码:

    @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            if (mOrientation == VERTICAL) {
                measureVertical(widthMeasureSpec, heightMeasureSpec);
            } else {
                measureHorizontal(widthMeasureSpec, heightMeasureSpec);
            }
        }
    

    LinearLayout的

    void measureChildBeforeLayout(View child, int childIndex,
                int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
                int totalHeight) {
            measureChildWithMargins(child, widthMeasureSpec, totalWidth,
                    heightMeasureSpec, totalHeight);
        }
    
     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);
        }
    

    由上边我们分析到 WindowManagerImpl的performTraversals()方法,这个时候就正式开始了View的绘制流程;

    第一个调用performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); 这个方法用于给控件指定宽高 ->

    调用mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); ->

    调用onMeasure(widthMeasureSpec, heightMeasureSpec); 这个时候测量正式开始 ->
    调用父布局,即就是LinearLayout.onMeasure()方法(因为这里是以LinearLayout包裹了3个TextView为例,当然你用RelativeLayout包裹,就调用RelativeLayout.onMeasure()测量方法也是可以的) ->

    调用 LinearLayout.onMeasure()中的measureVertical(widthMeasureSpec, heightMeasureSpec) (这个是activity_main文件中最外层根布局中的LinearLayout) ->

    measureChildWithMargins() ->

    调用child.measure(childWidthMeasureSpec, childHeightMeasureSpec); (这个是最外层根布局中的子孩子的LinearLayout,如果有多个LinearLayout的子孩子,那么就会一直调用这个方法) ->

    调用TextView的onMeasure()(这个就是子LinearLayout包裹的子孩子TextView)

    在上边涉及到2个测量模式

    childWidthMeasureSpec, childHeightMeasureSpec

    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) {
            int specMode = MeasureSpec.getMode(spec);
            int specSize = MeasureSpec.getSize(spec);
    
            int size = Math.max(0, specSize - padding);
    
            int resultSize = 0;
            int resultMode = 0;
    
            switch (specMode) {
            // Parent has imposed an exact size on us
            case MeasureSpec.EXACTLY:
                if (childDimension >= 0) {
                    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.
                    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;
    
            //noinspection ResourceType
            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    

    由以上源码可知:

    childWidthMeasureSpec, childHeightMeasureSpec这两个测量模式是通过getChildMeasureSpec()方法去计算的,具体计算是:

    在getChildMeasureSpec()中,先获取自己的测量模式和大小(即就是父布局),判断自己的测量模式是match_parent或者是一个固定的值,然后回去判断子孩子的测量模式和大小,具体判断方式如下:

    如果自己测量模式(即就是父布局)是 EXACTLY,并且子孩子的大小是match_parent,就给子孩子的测量模式EXACTLY;
    如果自己测量模式(即就是父布局)是 EXACTLY,并且子孩子的大小是wrap_content,就给子孩子的测量模式是 AT_MOST;
    如果自己测量模式(即就是父布局)是 AT_MOST,即使子孩子大小是match_parent,就给子孩子的测量模式 AT_MOST;
    如果自己测量模式(即就是父布局)是 AT_MOST,并且子孩子的大小是wrap_content,就给子孩子的测量模式 AT_MOST;

    在最后会把获取到的测量模式和大小,即就是resultSize, resultMode返回回去
    即就是返回到了measureChildWithMargins()方法中,如下图所示:


    图片.png

    返回这个测量模式和大小后,这个时候我们都会调用 setMeasuredDimesion()方法,这个时候我们的布局,才真正的指定了宽度和高度

    /**
     * Email: 2185134304@qq.com
     * Created by JackChen 2018/3/24 9:44
     * Version 1.0
     * Params:
     * Description:    测量模式计算方式
    */
    public class TextView extends View {
        public TextView(Context context) {
            super(context);
        }
    
        public TextView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
        }
    
        public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
            // 指定宽高
            // widthMeasureSpec = childWidthMeasureSpec
            // heightMeasureSpec = childHeightMeasureSpec
    
            // 我们之前讲的
            // wrap_content = AT_MOST
            // match_parent 、fill_parent、100dp = Exactly
    
            // 测量模式和大小是由父布局和它的孩子决定的,比方说:
    
            // 父布局是包裹内容,就算子布局是match_parent,这个是时候的测量模式还是 AT_MOST
            // 父布局是match_parent,就算子布局是match_parent,这个时候的测量模式是 EXACTLY
    
            setMeasuredDimension();
        }
    }
    
    
    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
            boolean optical = isLayoutModeOptical(this);
            if (optical != isLayoutModeOptical(mParent)) {
                Insets insets = getOpticalInsets();
                int opticalWidth  = insets.left + insets.right;
                int opticalHeight = insets.top  + insets.bottom;
    
                measuredWidth  += optical ? opticalWidth  : -opticalWidth;
                measuredHeight += optical ? opticalHeight : -opticalHeight;
            }
            setMeasuredDimensionRaw(measuredWidth, measuredHeight);
        }
    
    private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
            mMeasuredWidth = measuredWidth;
            mMeasuredHeight = measuredHeight;
    
            mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
        }
    
    而setMeasuredDimension();方法其实什么都没干,就是在setMeasuredDimensionRaw()方法中给宽高赋值,在这个时候 mMeasuredWidth和mMeasureHeight才真正的有值
    然后测量所有子孩子的宽高,源码中是通过for循环,获取所有子孩子,然后去调用child.measure(childWidthMeasureSpec, childHeightMeasureSpec)方法测量子View的高度,即就是TextView的高度,等测量出TextView的宽高,然后去测量自己的宽高(即就是父布局):
    如果自己(即就是父布局)是LinearLayout并且是垂直方向,那么自己高度就是不断的叠加子View的高度; childHeight = child.getMeasuredHeight();
    如果自己(即就是父布局)是RelativeLayout,那么父布局的高度是,指定子孩子中最高的;
    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
            android:id="@+id/text_view" />
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Hello World!"
                android:id="@+id/text_view2" />
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Hello World!"
                android:id="@+id/text_view3" />
        </LinearLayout>
    

    总结:

    测量是从外往里递归,也就是说:
    ViewRootImpl会把自己的测量模式传递给 -> DecorView,然后DecorView会把自己的测量模式传递给 activity_main中的LinearLayout ->
    然后LinearLayout通过for循环把自己的测量模式传递给 TextView,然后就会调用 TextView的onMeasure()方法,然后根据传递过来的LinearLayout的测量模式来指定 TextView的宽高,测量完毕后通过childHeight = child.getMeasuredHeight();获取到 子View的宽高,即就是获取到3个TextView的宽高后,来计算父布局,即就是LinearLayout
    自己的宽高 ,然后再把自己的宽高向外传递给DecorView ->
    然后DecorView根据 LinearLayout传回来的宽高,然后计算自己的宽高 , 把自己宽高计算好后,然后再把自己的宽高向外传递给 ViewRootImpl 。


    onMeasure()源码分析总结如下:
    测量是从外往里递归的:
    从外往里:

    首先从最外层的 ViewRootImpl开始,它把它的 测量模式传递给 DecorView,然后DecorView把自己的测量模式 传递给 父布局LinearLayout,然后LinearLayout再把自己的测量模式 传递给 子View;

    从里往外:

    等子View计算出自己宽高后,然后把自己宽高传递给父布局LinearLayout,然后LinearLayout根据 子View的宽高,来计算自己的宽高,这里计算方式就是:
    如果父布局是 LinearLayout,且是垂直方向,父布局高度就是累加子布局高度;
    如果父布局是RelativeLayout,那么父布局高度就是指定 子孩子中最高的;
    ,然后LinearLayout把自己的高度传递给 它的父布局,就是这样一路都把自己的高度传递给父布局,最后传递给 DecorView、传递给 ViewRootImpl,

    onMeasure()源码中就是这样测量的,如下图所示:
    onMeasure()源码分析.png

    代码已上传至github:
    https://github.com/shuai999/View_day08_2

    相关文章

      网友评论

        本文标题:View的绘制流程 - onMeasure()源码分析

        本文链接:https://www.haomeiwen.com/subject/izzfcftx.html