zoukankan      html  css  js  c++  java
  • Android View的绘制流程

    Android View的绘制流程

    概述:   

            在项目开发过程中,经常存在需要实现自定义控件的情况,对于比较简单的需要,通过组合系统提供的原生控件即可完成,但是碰到设计师脑洞大开的时候,通过简单的组合方式显然满足不了需求,

    这时候往往需要技术人员自己实现控件测量、布局和绘画等操作,而这一切的前提是你需要熟练掌握View的绘制流程:

      Android 中 Activity 是作为应用程序的载体存在的,它代表着一个完整的用户界面,提供了一个窗口来绘制各种视图,当Activity启动时,我们通过setContentView方法来设置一个内容视图,

    这个内容视图就是用户看到的界面;

    首先先了解一下Android的UI管理系统的层次关系,如下图:

    image.png

    •     PhoneWindow 是Android 系统中最基本的窗口系统,每个Activity会创建一个.

    •     PhoneWindow 是Android 和View 系统交互的接口. 

    •     DecorView本质是一个FrameLayout,是Activity中所有的祖先;

    1.绘制的整体流程

         当一个应用启动时,会启动一个主Activity,Android系统会根据Activity 的布局来对他进行绘制.绘制的会从根视图ViewRoot 的performTraversals()方法开始,从上到下遍历整个视图树,

    每个View控件负责绘制自己,而ViewGroup还需要负责通知自己的子View进行绘制操作.

    视图绘制的过程可以分为三个步骤,分别是测量(Measure)、布局(Layout)、和绘制(Draw).performTraversals()的核心代码如下: 

    private void performTraversals(){
        ...
        int childWidthMeasureSpec = getRootMeasureSpec(mWidth,lp.width);
        int childHeightMeasureSpec = getRootMeasureSpec(mHeight,lp.height);
        ...
        //执行测量流程
        performMeasure(childWidthMeasureSpec ,childWidthMeasureSpec);
        ...
        //执行布局流程
        performLayout(lp,desiredWindowWidth, desiredWindowHeight);
        ...
        //执行绘制流程
        performDraw();
    }

    2.MeasureSpec

            MeasureSpec 表示的是一个32位的整形值,它的高2位表示测试模式SpecMode,低30位表示某种测量模式下的规格大小SpecSize. MeasureSepc是View的类的一个静态内部类,用来说明应该如何测量这个View,其核心代码如下:

    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;
        
        //根据指定的大小和模式创建一个MeasureSpec
        public static int makeMeasureSpec(int size,int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }
        
        //微调某个MeasureSpec的大小
        static int adjust(int measureSpec, int delta) {
            final int mode = getMode(measureSpec);
            int size = getSize(measureSpec);
            if (mode == UNSPECIFIED) {
                // No need to adjust size for UNSPECIFIED mode.
                return makeMeasureSpec(size, UNSPECIFIED);
            }
            size += delta;
            if (size < 0) {
                Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
                        ") spec: " + toString(measureSpec) + " delta: " + delta);
                size = 0;
            }
            return makeMeasureSpec(size, mode);
        }
    
    }

          我们需要重点关注代码中的以下三种测量模式, 这个在后面的Measure阶段会用到.

      • UNSPECIFIED: 不指定测量模,父视图没有限制子视图的大小,子视图可以是想要的任何尺寸,通常用于系统的内部,应用开发中很少使用到.

      • EXACTLY: 精确测量模式,当该视图的layout_width 或者 layout_height 指定为具体数值或者match_parent 时生效,表示父视图已经决定了子视图的精确大小,这种模式下View的测量值就是SpecSize的值

      • AT_MOST: 最大值模式,当该视图的layout_width或者layout_height 指定wrap_content时生效,此时子视图的尺寸可以是不超过父视图允许的最大尺寸的任何尺寸.                              

            对DecorView 而言,它的MeasureSpec由窗口尺寸和其自身的LayoutParams 共同决定; 对于普通的View,它的MeasureSpec由父视图的MeasureSpec 和其自身的LayoutParams 共同决定.

    3.Measure

      Measure操作用来计算View的实际大小,由于前面的分析可知,页面的测量流程是从performMeasure方法开始的, 核心代码如下:

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

       可以看到,具体的测量操作时分发给ViewGroup的,由ViewGroup 在它的measureChild 方法中传递给子View, 代码如下. ViewGroup 通过遍历自身的子View,并逐个调用子View的measure方法实现测量操作.

    //遍历测量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];
            //当View的可见性处于GONE状态时,不对其进行测量
            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();
    
        //根据父容器的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(ViewGroup) 的measure 方法,最终的测量时通过回调onMeasure 方法实现的,这个通过由View的特定子类自己实现,开发者也可以通过重写这个方法实现自定义View.

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    
        ...
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        ...
        
    }
    
    // 如果需要自己自定义的=测量过程,则子类可以重写这个方法
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //setMeasuredDimension方法用于设置View的测量的宽高
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
    
    // 如果View没有重写 onMeasure 方法,则默认会直接调用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 = specSize;
            break;
        }
        return result;
    }

    4.Layout

        Layout过程用来确定View在父容器中的布局位置,它是由父容器获取子View的位置参数后,调用子View的layout方法并将位置参数传入实现的, ViewRootImpl的 performLayout 代码如下:

    // ViewRootImpl.java
    private void performLayout(WindowManager.LayoutParams lp, int 
        desiredWindowWidth, 
                       int desiredWindowHeight){
    
        ...
        host.layout(0,0,host.getMeasureWidth() , host.getMeasureHeight());
        ...                
    }
    
    //View,java
    
    public void layout(int l, int t, int r, int b) {
    
        ...
        onLayout(changed, l, t, r, b);
        ...
    }
    
    //空方法,子类如果是ViewGroup 类型,则重写这个方法,实现ViewGroup中所有View控件布局流程
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

    5.Draw

            Draw操作用来控件绘制出来的, 绘制的流程从performDraw 方法开始, 核心代码如下:

    private void performDraw(){
    
        ...
        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) {
            
            ...
            mView.draw(canvas);
            ...
            
    }

      可以看出最终调用到每个View的draw方法绘制每个具体的View ,绘制基本上的可以分为六个步骤,代码如下:

    public void draw(Canvas canvas) {
        
        ...
        //步骤一: 绘制View的背景
        drawBackground(canvas);
        
        ...
        //步骤二: 如果需要的话,保存Canvas的图层,为fading做准备
        saveCount = canvas.getSaveCount();
        ...
        canvas.saveLayer(left, top, right, top + length,null ,flags);
        
        ...
        //步骤三: 绘制View的内容
        onDraw(canvas);
        
        ...
        //步骤四: 绘制View的子view
        dispatchDraw(canvas);
        
        ...
        //步骤五: 如果需要的话,绘制View的fading边缘恢复图层
        canvas.drawRect(left, top, right, top + length, p);
        ...
        canvas.restoreToCount(saveCount);
        
        ...
        //步骤六: 绘制View的装饰(例如滚动条)
        onDrawForeground(canvas);
        
    }
  • 相关阅读:
    C++ Base64 编码 解码
    C语言 线性表 链式表结构 实现
    C语言 线性表 顺序表结构 实现
    Python基于共现提取《釜山行》人物关系
    SciPy
    操作 numpy 数组的常用函数
    Numpy
    Python 科学计算-介绍
    使用Python定制词云
    事件驱动-协程实现爬虫
  • 原文地址:https://www.cnblogs.com/Mr-East/p/11530645.html
Copyright © 2011-2022 走看看