android 源码 —— View 的绘制流程

View 的绘制流程

目录

View 的绘制流程

测量Measure

布局Layout

绘制Draw

总结

思考


接着我们之前分析的 activity 启动流程,我们继续分析一下 activity启动后,View 的绘制流程

不清楚的可以看一下activity 启动流程:activity 启动流程

源码版本:android-27

上一篇的启动流程中,最后 走到了 ViewRootImpl 中的 setView() 方法,我们顺着这个继续看下去。

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            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.
                // 这里就是我们 View 绘制的入口了
                requestLayout();
    
                ......
            }
        }
    }

requestLayout() 就是 View 的入口,也是开始的地方,我们顺着 requestLayout() 看进去:

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

    // 进入 scheduleTraversals()
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // 进入 Callback 回调
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

    // postCallback 进入 mTraversalRunnable
   final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

    // 继续进入看 TraversalRunnable()
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            // 我们发现走进了 doTraversal() 方法
            doTraversal();
        }
    }
    
    // 进入 doTraversal()
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
            
            // 最后我们 进入了 View 绘制真正的入口,也是大家普遍分析的 performTraversals() 方法
            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }
我们继续看 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();
   }
   ......
       int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
       int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);      
   ......
   // Ask host how big it wants to be
   // View 的测量
   performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
   ......
   // View 的布局
   performLayout(lp, mWidth, mHeight);
   ......
   // View 的绘制
   performDraw();
}

这里先贴一段 View 绘制的示例代码,方便我们 可以根据自己写的 xml 布局 去看源码

<!-- 示例代码 -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    
    <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" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>

</LinearLayout>

可以看到 LinearLayout 里面 嵌了一层 LinearLayout ,之后 写了两个 TextView

测量Measure

performMeasure:View ——> Measure() ——> onMeaure()

现在我们继续看源码,从 performTraversals() 方法中,我们可以看到 三大流程,测量、布局、绘制。所以我们现在先看 测量。

跟踪源码,进入到performMeasure方法

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        ......
            
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        ......
    }

我们继续看 measure 方法,我们会发现调用了 onMeasure() 方法,也就是 我们重写的 测量方法:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ......

    // measure ourselves, this should set the measured dimension flag back
    onMeasure(widthMeasureSpec, heightMeasureSpec);
    ......
    
}

这个时候,根据我们的 实例代码,我们的根布局 是 LinearLayout ,所以我们去 LinearLayout 中看 OnMeasure() 方法:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 在这里,我们可以看到,onMeasure() 方法中,根据我们设置的 Orientation
        // 进入不同的方法,我们就根据示例,进入了 measureVertical() 方法
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }

进入 measureVertical() 方法,父 View 的 measure 的过程会先测量子 View,等子 View测量结果出来后,再来测量自己:

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
    ......
    final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    ......
    // See how tall everyone is. Also remember max width.
    // for 循环,遍历所有的子 View
    for (int i = 0; i < count; ++i) {
        final View child = getVirtualChildAt(i);
        if (child == null) {
            mTotalLength += measureNullChild(i);
            continue;
        }
        ......
        // Determine how big this child would like to be. If this or
        // previous children have given a weight, then we allow it to
        // use all available space (and we will shrink things later
        // if needed).
        final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
        // 测量子 View
        measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                 heightMeasureSpec, usedHeight);       

    ...... 
    
}

接着 我们进入 measureChildBeforeLayout()方法中:

    void measureChildBeforeLayout(View child, int childIndex,
            int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
            int totalHeight) {
        measureChildWithMargins(child, widthMeasureSpec, totalWidth,
                heightMeasureSpec, totalHeight);
    }

这里只有一个方法,就是 measureChildWidthMargins(),measureChildWithMargins就是用来测量某个子View的:

    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        // 子View的 LayoutParams
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        
        // 请注意 getChildMeasureSpec() 这个方法,这里就告诉我们具体的测量模式、大小是怎么
        // 得到的。它根据父 View 的测量规格和父 View 自己的 Padding,
        // 还有子 View 的 Margin 和已经用掉的空间大小(widthUsed),就能算出子View的MeasureSpec
        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);

        // 子View 用获取到的 MeasureSpec 去测量自己,        
        // 如果子View是 ViewGroup 那还会递归往下测量
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

下面,我们进入 getChildMeasureSpec() 来看一下是怎样测量的:

 
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    // spec:指的是 父 View 的 MeasureSpec 测量规格
    // childDimension:表示子 View 内部的 Layoutparams 属性的值
    int specMode = MeasureSpec.getMode(spec);  //获得父View的mode 
    int specSize = MeasureSpec.getSize(spec);  //获得父View的大小 
 
   //获取子View的大小
    int size = Math.max(0, specSize - padding);  
 
    int resultSize = 0;    
    int resultMode = 0;    
 
    switch (specMode) { 
    // Parent has imposed an exact size on us 
    // 父 View 测量模式是 EXACTLY:
    case MeasureSpec.EXACTLY:  
        // 子View的 width 或 height 是个精确值 比如 100dp 
        if (childDimension >= 0) {           
            resultSize = childDimension;         
            resultMode = MeasureSpec.EXACTLY;     
        }  
        // 子 View 的 width 或 height 为 MATCH_PARENT
        else if (childDimension == LayoutParams.MATCH_PARENT) { 
            // Child wants to be our size. So be it. 
            resultSize = size;                   
            resultMode = MeasureSpec.EXACTLY;    
        }  
        // 子 View 的 width 或 height 为 WRAP_CONTENT 
        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 
    // 父 View 测量模式是 AT_MOST:     
    case MeasureSpec.AT_MOST: 
        // 子 View的 width 或 height 是个精确值 比如 100dp 
        if (childDimension >= 0) { 
            // Child wants a specific size... so be it 
            resultSize = childDimension;        //childDimension 为子 View 的大小
            resultMode = MeasureSpec.EXACTLY;   
        } 
        // 子 View 的 width 或 height 为 MATCH_PARENT
        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;   
        } 
        // 子 View 的 width 或 height 为 WRAP_CONTENT 
        else if (childDimension == LayoutParams.WRAP_CONTENT) { 
            // Child wants to determine its own size. It can't be 
            // bigger than us. 
            resultSize = size;                  
            resultMode = MeasureSpec.AT_MOST;   
        } 
        break; 
 
    // Parent asked to see how big we want to be 
    // 父 View 测量模式是 AT_MOST(这个很少用到): 
    case MeasureSpec.UNSPECIFIED: 
        // 子 View的 width 或 height 是个精确值 比如 100dp 
        if (childDimension >= 0) { 
            // Child wants a specific size... let him have it 
            resultSize = childDimension;        
            resultMode = MeasureSpec.EXACTLY;    
        } 
        // 子 View 的 width 或 height 为 MATCH_PARENT 
        else if (childDimension == LayoutParams.MATCH_PARENT) { 
            // Child wants to be our size... find out how big it should 
            // be 
            resultSize = 0;                        
            resultMode = MeasureSpec.UNSPECIFIED;   
        }  
        // 子 View 的 width 或 height 为 WRAP_CONTENT 
        else if (childDimension == LayoutParams.WRAP_CONTENT) { 
            // Child wants to determine its own size.... find out how 
            // big it should be 
            resultSize = 0;                        //size大小为0 
            resultMode = MeasureSpec.UNSPECIFIED;   
        } 
        break; 
    } 

    //根据计算获取 mode 和 size 去创建 measureSpec 对象
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode); 
}

可以看出 模式 和 大小 是由 父布局和 自己决定的,通过 getChildMeasureSpec 方法判断的:

1、如果父 View 的 MeasureSpec 是 EXACTLY,说明父View的大小是 match_parent 或者是固定值(比如 100dp)

  1. 如果如果子 View 是确定的值(100dp),那么 MeasureSpec 的 mode = EXACTLY,大小 size = 你在子 View填的那个值(100dp)
  2. 如果子View 是MATCH_PARENT,那么子 View 的 size = 父 View 的 size,mode = EXACTLY
  3. 如果子View 是WRAP_CONTENT,那么子View MeasureSpec mode 应该是 AT_MOST,而 size 为父View的 size,表示的意思就是子 View 的大小没有确切的值,子 View 的大小最大为父 View的大小,不能超过父View的大小,也就是 AT_MOST 模式

2、如果父 View 的 MeasureSpec 是AT_MOST,说明父View的大小是不确定,为 wrap_content

  1. 如果如果子 View 是确定的值(100dp),那么 MeasureSpec 的 mode = EXACTLY,大小 size = 你在子 View填的那个值(100dp)
  2. 如果子View 是 MATCH_PARENT,由于父容器自己的大小不确定,导致子 View 的大小也不确定,就只知道最大就是父View的大小,所以子 View 的大小 size = 父 View 的 size,mode = AT_MOST
  3. 如果子View 是WRAP_CONTENT,那么子 View MeasureSpec mode 的就是AT_MOST,而size 暂时就是父View的 size。

经过一次次的测量子 View,最后会返回到 measureVertical() 中,等到所有子 View 测量完毕后,经过一系列的计算,通过 setMeasureDimension() 方法设置自己的宽高

对于我们的示例 LinearLayout 来说,就会  childHeight = child.getMeasuredHeight() + share; 不断的累计高度。

对于 RelativeLayout 来说,就会  height = Math.max(height, mLayoutParams.height); 不断比较找子 View 中最高的。

总的来说:onMeasure() 就是 父 View 不断的 for 循环测量子 View,等到子 View 测量完毕后,再去测量自己的宽高,最后通过 setMeasureDimension() 进行测量。所以我们重写 onMeasure 最后都需要调用 setMeasureDimension() 方法。

布局Layout

performLayout:View ——> layout() ——> onLayout()

layout 的作就是 ViewGroup 用来确定子 View 的位置。在 ViewGroup 确定位置后,会在 onLayout 中遍历所有的子 View,并调用child.layout 方法,在 layout 方法中又会调用 onLayout 方法。

绘制Draw

performDraw:View ——> draw() ——> onDraw()

drawBackground():画背景

onDraw(canvas):画自己

dispatchDraw(canvas):画子 View 不断循环调用子 View 的draw()

draw 中,会绘制背景,对 View 的内容进行绘制 以及其他的操作,但是注意一个,就是 ViewGroup 默认不执行 onDraw 方法

我们自定义ViewGroup 时,要执行onDraw 方法,有几种方式:

1、将onDraw 替换为 dispatchDraw  2、构造函数中设置背景透明  3、构造函数中调用 setWillNotDraw(false) 

public void setWillNotDraw(boolean willNotDraw) {
    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}

总结

View 绘制流程简述

第一步 performMeasure():用于指定和测量所有空间的宽高,对于 ViewGroup,先去测量里面的子 View,根据子 View 的宽高再来计算和指定自己的宽高,对于 View,它的宽高由自己和父布局实现的。

第二步 performlayout():用于摆放子布局,for 循环所有的子 View,用 child.layout(),摆放 childView

第三步 performDraw():用于绘制自己还有子 View,对于 ViewGroup,首先绘制自己的背景,然后 for 循环绘制子 View 调用子 View 的 draw() 方法。对于 view,绘制自己的背景,绘制自己显示的内容

思考

1、如果要获取 View 的宽高,前提肯定是需要调用测量方法,测量完毕之后才能获取宽高属性

     比如:activity 启动时,获取 view 的宽高,我们可以用下面的方式:

①、Activity 重写 onWindowFocusChanged:调用这个方法时,view 已经初始化完毕

②、调用 view.post(runnable):post只是将 runnable 添加到消息队列中,等到 Looper 的时候才调用,这是 view已经初始化好了

2、View 的绘制流程,是在 onResume 之后才开始

3、addView、setText、setVisibility 等等 会调用 requestLayout() invalidate(ture),重新走一遍 View 绘制流程

4、优化的时候,可以根据源码写代码的时候进行优化,比如 使用 invalidate(),我们可以去减少 ondraw() 调用次数

5、不要布局嵌套 等等

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值