深入理解Android View绘制流程 源码详解

前言

一直都有阅读学习源码的习惯,但是没从来没有想过系统的对其进行一个梳理,每次只是看过了以后就过去了,然后过一段时间,就又会遗忘了,所以打算开始慢慢养成将其源码分析写出来,这样一个可以加深对源码的原理理解,另一个也方便以后进行一个回顾。
这里全文是在Android SDK 29的源码上进行分析理解的,众所周知,Android View的绘制流程有三大步骤 OnMeasure(对控件进行测量设置其宽高),OnLayout(根据对控件测量出来的宽高进行屏幕位置布局摆放),OnDraw(将控件内容绘制在屏幕之上),下面就带大家一步一步从源码分析:

ViewRootImpl 绘制入口分析

入口 — ActivityThread#handleResumeActivity,以下都是一些伪代码

class ActivityThread
{
	//绘制三大步骤的流程入口
	@Overrrid 
	public void handleResumeActivity(IBiner token,boolean finalStateRquest ,boolean isForward,boolean reason)
	{
		...
		//这里将会执行到Activity的onResume()方法
		final ActivityClientRecord r = performResumeActivity(token,finalStateRequest,reason);
		
		....
		
		 if (r.window == null && !a.mFinished && willBeVisible) 
		 {
		 	//获取当前Activity关联的Window窗口类,其真正的对象是一个PhoneWindow
            r.window = r.activity.getWindow();
            //然后获取当前Window的顶级父容器DecorView,这里的DecorView是一个FrameLayout
            View decor = r.window.getDecorView();
            //会先设置DecorView不可见,目前的绘制流程还未开始
            decor.setVisibility(View.INVISIBLE);
            
            //获取ViewManager类型的实例 它只是一个接口,定义了addView,removeView的一些行为
            //那么真的对象是哪一个呢? 
            //答:Activity#getWindowManager()
            // ---> mWindow.getWindowManager(); --Activity#attach()方法中
            // ---> ((WindowManagerImpl)wm).createLocalWindowManager(this); --- Window#setWindowManager方法中
            // --->  new WindowManagerImpl(mContext, parentWindow) ;  --- WindowManagerImpl#createLocalWindowManager方法中
            //故 这里的实际对象是WindowManagerImpl对象 ,感兴趣的可以按照此流程去查阅源码
            ViewManager wm = a.getWindowManager();

            //获取当前Window的LayoutParmas布局参数(该布局参数中包含了当前窗口的宽,高等属性)
            WindowManager.LayoutParams l = r.window.getAttributes();	
            		
			....
			
			 if (a.mVisibleFromClient) 
			 {
                if (!a.mWindowAdded) 
                {
                	//设置该属性,表示当前窗口已经附加到Activity之上了
                    a.mWindowAdded = true;
                    //将界面顶级布局容器DecoreView 和 Window 的布局参数传递给WindowManager进行添加布局
                    //这里就是的View的绘制流程开始
                    //从前文得知 wm 的真正类型是WindowManagerImpl,所以wm.addView将会执行到 WindowManagerImpl#addView方法中
                    //而WindowManagerImpl#addView --> WindowManagerGlobal#addView
                    // ---> ViewRootImpl#setView
                     wm.addView(decor, l);
                } 
            }
			
         }
	}
}

从上面源码分析可得知真正绘制流程逻辑是发生在WindowManagerGlobal#addView方法中的,然后在该方法中会创建一个ViewRootImpl对象,该对象是连接WindowManager和DecorView的纽带,View的三大流程就是通过ViewRootImpl来完成的。

总结一下,在ActivityThread中,Activity对象被创建完之后,DecorView会和Window进行绑定,然后通过ManagerManager这个中介去调用WindowManagerGlobal 来创建ViewRootImpl,自此ViewRootImpl就会和DecorView建立了关联,同时ViewRootImpl
对象也会被保存到WindowManagerGlobal对象中

public class RootViewImpl implements ViewParent
{
	/**
	*
	* @params view Decorview
	* @parmas attrs Window的布局参数
	*/
	 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) 
	 {
	 	  synchronized (this) 
	 	  {
            if (mView == null) 
            {
            	//持有当前传过来的DecorView
                mView = view;
				...
				//这里最终会执行到View的OnMeasure,OnLayout,OnDraw
				requestLayout();
				
				...
				
				//ViewRootImpl 与 DecorView 的绑定
				//该方法参数ViewParent 而ViewRootImpl正是实现了ViewParent接口的,所以这里就将DecorView和ViewRootImpl
				//进行了绑定。正如我们都知道每个Activity的根布局都是DecorView,也就是说实际上View的刷新都是由ViewRootImpl
				//来控制的。即使是界面上一个小小的View发起了重回请求,都要层层走到ViewRootImpl,然后由它发起重绘请求
				//然后再有它来开始遍历View树,一直遍历到这个需要重绘的View再调用它的onDraw方法进行绘制
				view.assignParent(this);
				
				...
            }
	 }
}

总结一下,再打开一个Activity后,当它的onCreate-onStart-onReumse走完了以后,才将它的DecorView与一个新建的ViewRootImpl 对象进行绑定,同时开始安排一次遍历View的任务 也就是View树的操作等待执行,然后再讲DecorView的parent
设置成ViewRootImpl对象,所以这就是为什么在onCreate-onStart-onResume里获取不到View的宽高原因,因为在这个时刻ViewRootImpl甚至都还没开始创建,更不用说是否已经执行过测量操作

public class RootViewImpl implements ViewParent
{
	@Override
    public void requestLayout() 
    {
        if (!mHandlingLayoutInLayoutRequest)
         {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

 	void scheduleTraversals() 
 	{
        if (!mTraversalScheduled) 
        {
        	//这里是为了过滤一帧内重复的刷新请求
            mTraversalScheduled = true;
            //这里发送一个同步屏障消息,防止app接收到屏幕刷新信号时,来不及第一时间就去执行刷新屏幕的操作,
            //等到下一帧刷新信号又到来时,就有可能造成丢帧的情况,所以这里采用发送同步屏障消息来防止掉帧
            //具体的实现原理(较为复杂) 后面可能会写一篇关于这里消息处理逻辑
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
			
			//这里将执行 doTraversal逻辑 包装成一个Runnable 等待 onVsync 屏幕刷新信号回调执行
			//这个具体实现原理(较为复杂),同上可能会一起写一篇原理逻辑,所以这里就不做过的解释
			//这里就可以直接理解成当底层发出VSync信号后,doTraversals就会被回调执行
            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) 
        {
        	//这里执行的都是和 scheduleTraversals的 相反的操作
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
       		...
       		//执行刷新逻辑(onMeasure,onLayout,onDraw)
            performTraversals();
			...
       
        }
    }
	
	private void performTravelsals()
	{
		...
		int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
		int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);		  
		 //开始测量
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
		...
		//开始位置布局
	    performLayout(lp, mWidth, mHeight);	
		... 
		//开始绘制内容
	    performDraw();
 		...
	}
}

OnMeasure

在理解Android如何对View进行测量之前,需要知道MeasureSpec这个类的含义。

int 类型 4个字节 3200 00 0000 0000 0000 0000 0000 0000 00002 位 代表的是当前View的测试模式
后 30 位 代表的是当前View的父控件给它指定的宽或者高( ps : 这个不一定代表的是最终的宽高值)

public static class MeasureSpec
{
   private static final int MODE_SHIFT = 30;
   
   //00 00 0000 0000 0000 0000 0000 0000 0011 左移 30位  ->11
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
   
   //该模式比较特殊,一般也只是Android FrameWork层使用到
   //父控件不规定子控件大小,它可以使任意大小
   // 00 00 0000 0000 0000 0000 0000 0000 0000  左移 30位  ->00
   public static final int UNSPECIFIED = 0 << MODE_SHIFT;
   
   //父控件规定子控件宽高的一个具体值
   // 00 00 0000 0000 0000 0000 0000 0000 0001 左移 30位  ->01
   public static final int EXACTLY     = 1 << MODE_SHIFT;
   
   //父控件规定子控件宽高的一个最大值
   // 00 00 0000 0000 0000 0000 0000 0000 0010  左移 30位  ->10
   public static final int AT_MOST     = 2 << MODE_SHIFT;
   
   
   public static int makeMeasureSpec( int size,int mode)
    {
   		//将2个int值 组合成一个int值
   		// size 宽高大小值
   		// mode 宽高测量模式
   		// 举个例子:size = 32   mode = EXACTLY     
   		// a
   		// 0000 0000 0000 0000 0000 0000 0010 0000  size 
   		// 1111 1111 1111 1111 1111 1111 1111 1100    ~MODE_MASK
   		// 0000 0000 0000 0000 0000 0000 0010 0000   size &  ~MODE_MASK
   	
   		//b
   		//0000 0000 0000 0000 0000 0000 0000 0001  mode 
   		//0000 0000 0000 0000 0000 0000 0000 0011   MODE_MASK
   		//0000 0000 0000 0000 0000 0000 0000 0001  mode & MODE_MASK
   	
   	    // a|b 
   		// 0000 0000 0000 0000 0000 00000010 0000
   		// 0000 0000 0000 0000 0000 0000 0000 0001 
   		// 0000 0000 0000 0000 0000 0000 0010 0001 	---> 33(十进制)
   	    return (size & ~MODE_MASK) | (mode & MODE_MASK);
   }
   
   //解析传入的measureSpec 获取其中的设置的测量模式
   public static int getMode(int measureSpec)
   {
   		//0000 0000 0000 0000 0000 0000 0010 0001
   		//0000 0000 0000 0000 0000 0000 0000 0011
   		//0000 0000 0000 0000 0000 0000 0000 0001   -> 1
   		return (measureSpec & MODE_MASK);
   }
   
   //解析传入的measureSpec 获取其中的设置的大小
   public static int getSize(int measureSpec) 
   {
   		//0000 0000 0000 0000 0000 0000 0010 0001
   		//1111 1111 1111 1111 1111 1111 1111 1100
   		//0000 0000 0000 0000 0000 0000 0010 0000 -> 32
   		return (measureSpec & ~MODE_MASK);
   }
}

在了解到了 MeasureSpec的工作模式以后,我们可以在上面的源码看到,在进行performMeasure之前 还要一个步骤是对DecorView的
的宽高MeasureSpec进行计算


public class ViewRootImpl
{
	
	//windowSize 当前窗口Window 的 宽/高
	//rootDimension DecorView 的LayoutParams参数中的宽高设置(就是相当于在xml中写的)
	 private static int getRootMeasureSpec(int windowSize, int rootDimension) 
	 {
        int measureSpec;
        switch (rootDimension) 
        {
	        case ViewGroup.LayoutParams.MATCH_PARENT:
	       		//当窗口的宽/高是 充满屏幕的, 则将 屏幕宽/高 + MeasureSpec.EXACTLY 组合起来
	            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
	            break;
	        case ViewGroup.LayoutParams.WRAP_CONTENT:
   	       		//当窗口的宽/高是 自适应的 则将 屏幕宽/高 + MeasureSpec.AT_MOST组合起来
   	       		// --> 这代表着允许子控件最大宽/高为 windowSize
	            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
	            break;
	        default:
	        	//当窗口的宽/高是 固定尺寸, 则将 固定尺寸 + MeasureSpec.EXACTLY 组合起来
	            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
	            break;
        }
        return measureSpec;
    }

 	private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) 
 	{
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
        	//开始测量 这里mView 就是顶级容器DecorView
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
}

因为View.meausre方法是被final修饰了,即代表了,其所有的子类不会去重写该方法,所以我们先看下View.measure方法
ps:注释中的测量参数 均代表的是MeasureSpec中计算出来的组合 int (mode + size)

public class View
{
	//当通过RootViewImpl#performMeasure()方法中调用此方法时
	//widthMeasureSpec 代表的是Window 对其关联的 DecorVIew 的一个宽的测量参数
	//heightMeasureSpec代表的是Window 对其关联的 DecorVIew 的一个高的测量参数
	public final void measure(int widthMeasureSpec, int heightMeasureSpec) 
	{
		...
		//是否强制进行重新测量
		final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
		//宽高测量参数是否都已经发生更改过
		final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
		     || heightMeasureSpec != mOldHeightMeasureSpec;
	    //宽高测量参数都否都是EXACTILY 精确模式
		final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
		     && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
		 //当前控件已经是否已经有测量过的宽高且其宽高都是和测量参数中的一样
		final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
		     && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
	     //	 这里会对上面的那些条件进行一个判断,判断是否需要进行测量逻辑 这样做就提高了测量的效率
		final boolean needsLayout = specChanged
		     && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
        if (forceLayout || needsLayout) 
        {
        	...
          //这里的情况要分为2种
          //如果当前控件是ViewGroup的话 ,就需要直接看ViewGroup中的onMeasure方法
          //如果当前控件是View的话,就需要直接看View的onMeasure方法
      	   onMeasure(widthMeasureSpec, heightMeasureSpec);
       	   ...
        }
        ...
		//将测量参数进行保存,下次重绘时,就用到了
 		mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;
		...
	}
}

因为DecorView是继承FrameLayout的 所以这里先看FrameLayout中的OnMeasure方法

public class FrameLayout extends ViewGroup
{
	 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
	 {
	 	//判断 宽/高 中有没有测量模式 不是不是精确模式
	 	 final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
                
         ...
    	 for (int i = 0; i < count; i++) 
    	 {
            final View child = getChildAt(i);
            //孩子控件可见即要进行测量
            if (mMeasureAllChildren || child.getVisibility() != GONE) 
            {
            	//测量孩子控件的边距,在该方法中进行第一次测量
                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);
                    }
                }
            }
        }
		
		//最大宽度 还需要加上左右两边前景padding
		maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
        
        //最大宽度 然后需要和背景图的宽度进行比较,取最大值
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
	
		//是否设置了最小宽度
		final Drawable drawable = getForeground();
        if (drawable != null) {
            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
        }
		
		//根据最大宽度/高度,父控件的测量模式,设置自身的宽高
      setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));
	
		//这里又进行了第二次的测量
		count = mMatchParentChildren.size();
        if (count > 1) 
        {
            for (int i = 0; i < count; i++) 
            {
                final View child = mMatchParentChildren.get(i);
                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

                final int childWidthMeasureSpec;
                //如果当前子控件xml中设置的是 MATCH_PARENT
                if (lp.width == LayoutParams.MATCH_PARENT) 
                {
                	//根据第一次测量出来的宽度 - 左右前景padding - 左右外边距
                    final int width = Math.max(0, getMeasuredWidth()
                            - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                            - lp.leftMargin - lp.rightMargin);
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                            width, MeasureSpec.EXACTLY);
                } 
                else 
                {
                	//重新进行子控件的测量模式判断 得出来新的 childWidthMeasureSpec 
                    childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                            lp.leftMargin + lp.rightMargin,
                            lp.width);
                }

                final int childHeightMeasureSpec;
                if (lp.height == LayoutParams.MATCH_PARENT) 
                {
                    final int height = Math.max(0, getMeasuredHeight()
                            - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
                            - lp.topMargin - lp.bottomMargin);
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            height, MeasureSpec.EXACTLY);
                } 
                else 
                {
                    childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                            getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                            lp.topMargin + lp.bottomMargin,
                            lp.height);
                }
	           	//子控件进行测量,在view的measure方法中,如果子控件是ViewGroup的话,就会重新再走一遍上述 onMeasure 方法流程
				//如果是View的话 ,就直接走View的 onMeausre方法了       
                child.measure(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,
        		//父控件的左右内边距 + 子控件的左右外边距 + 0
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, 
                //子控件xml中写的layout_width值
                lp.width);
          
          //这里同上,算出来的是 子控件的高 测量参数
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);
		//子控件进行测量,在view的measure方法中,如果子控件是ViewGroup的话,就会重新再走一遍上述 onMeasure 方法流程
		//如果是View的话 ,就直接走View的 onMeausre方法了 
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

	 public static int getChildMeasureSpec(int spec, int padding, int childDimension) 
	 {
	 	//获取父控件的测量模式
        int specMode = MeasureSpec.getMode(spec);
        //获取父控件规定的测量大小
        int specSize = MeasureSpec.getSize(spec);

		//这里的大小是 减去过padding的
        int size = Math.max(0, specSize - padding);

		//最终子控件的大小
        int resultSize = 0;
        //最终子控件的测量模式
        int resultMode = 0;

        switch (specMode)
         {
         	//如果父控件的测量模式是 精确模式
	        case MeasureSpec.EXACTLY:
    	       //子控件的xml中写了固定的大小值
	            if (childDimension >= 0) 
	            {
	            	//子控件大小 为 xml中写的固定值
	                resultSize = childDimension;
	                //子控件测量模式 为EXACTLY
	                resultMode = MeasureSpec.EXACTLY;
	            } 
	            //子控件的xml中写的是MATCH_PARENT
	            else if (childDimension == LayoutParams.MATCH_PARENT) 
	            {
	            	//子控件大小 就为父控件指定的大小 一般就是父控件自生的大小
	                resultSize = size;
	                //子控件测量模式 为EXACTLY
	             	resultMode = MeasureSpec.EXACTLY;
	            } 
	            //子控件的xml中写的是WRAP_CONTENT
	            else if (childDimension == LayoutParams.WRAP_CONTENT) 
	            {  
		            //子控件大小 就为父控件指定的大小   
	                resultSize = size;
                    //子控件测量模式 为AT_MOST
	                resultMode = MeasureSpec.AT_MOST;
	            }
	            break;
          //如果父控件的测量模式 为At_most
	        case MeasureSpec.AT_MOST:
	        	 //子控件的xml中写了固定的大小值
	            if (childDimension >= 0) 
	            {
	            	//子控件大小 为 xml中写的固定值
	                resultSize = childDimension;
	                 //子控件测量模式 为EXACTLY
	                resultMode = MeasureSpec.EXACTLY;
	            } 
                 //子控件的xml中写的是MATCH_PARENT
	            else if (childDimension == LayoutParams.MATCH_PARENT) 
	            {
		            //子控件大小 就为父控件指定的大小
	                resultSize = size;
	                 //子控件测量模式 为AT_MOST
	                resultMode = MeasureSpec.AT_MOST;
	            }
	             //子控件的xml中写的是WRAP_CONTENT 
	            else if (childDimension == LayoutParams.WRAP_CONTENT) 
	            {
	             	 //子控件大小 就为父控件指定的大小
	                resultSize = size;
	                 //子控件测量模式 为AT_MOST
	                resultMode = MeasureSpec.AT_MOST;
	            }
	            break;
	            // 这个日常开发都用不到 ,这里就不去分析了
	        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;
	        }
	    //将size mode进行组合
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }		
}

以上分析的是FrameLayout的 measure方法 即ViewGroup的onMeasure方法,而当子控件是View的时候,其onMeasure 又有些不一样

public class View
{
	 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
	 {
        setMeasuredDimension(
        		getDefaultSize(
        			//如果当前控件没有设置背景那么获取的就是  最小宽度值 minWidth( xml 中没有设置minWidth 或者代码中没有设置)
        			// minWidth 就为 0
        			//如果设置了背景 则获取背景的最小宽度
        			getSuggestedMinimumWidth(),
        			//父控件給的测量参数
        		 	widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
	
	  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;
	        //当测量模式是 AT_MOST 、EXACTLY 时
	        case MeasureSpec.AT_MOST:
	        case MeasureSpec.EXACTLY:
	        	//大小也就是父控件 给定的大小
	            result = specSize;
	            break;
        }
        return result;
    }
}

至此onMeasure的基本流程也介绍完了

onLayout

这里分析就比较简单了


public class ViewRootImpl 
{
	 private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) 
     {
		...
		 final View host = mView;
		 ....
	    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
     }
}

public class View 
{
	public void layout(int l, int t, int r, int b) 
	{
		....
	    onLayout(changed, l, t, r, b);
	    ....    
	}

}

对于View来说 他的onLayout方法是一个空方法,因为布局主要值针对其子控件的,View是没有子控件的,所以这里是空方法
而对于ViewGroup对来,不同的ViewGroup子类其实现也是不同的,但是主体逻辑都还是去遍历其子控件然后调用layout方法进行
子控件的测量,这里就不再过多去介绍了,感兴趣的可以自己去看看源码

onDraw


public class ViewRootImpl
{
	 private void performDraw() 
	 {
	 	...
 	   boolean canUseAsync = draw(fullRedrawNeeded);
 	    ...	 
	 }
	 
	 private boolean draw(boolean fullRedrawNeeded) 
	 {
	 	...
	 	if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)) {
                    return false;
                }
         ...
	 }

    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
           boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
           
           ...
            canvas = mSurface.lockCanvas(dirty);
            ...
            //这里后面就直接调用OnDraw
            mView.draw(canvas);	
			...
		   surface.unlockCanvasAndPost(canvas);		
            
       }	
}

这里只是简单的介绍了下调用流程,具体的View#draw中方法可以自行去了解看看 本文主要花大篇幅的去介绍了绘制的入口和
OnMeasure 流程。如有什么不对的地方,请指正~~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值