自定义View绘制过程源码分析

写在前面

自定义View的绘制流程:onMeasure() -> onLayout() ->onDraw(),在分析源码之前需要了解一下MeasureSpec类。

MeasureSpec代表一个32位int值,高2位代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式,而specSize是指在某种测量模式下的规格大小。

其作用是测量一个视图View的宽/高。每个MeasureSpec代表一组宽高的测量规格。
查看MeasureSpec类源码

 public static class MeasureSpec {

        //进位大小为2的30次方
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

        //测量模式specMode的三种类型:UNSPECIFIED ,EXACTLY,AT_MOST          
        //UNSPECIFIED的模式 0向左 进位30
        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;

        //根据提供的size和mode创建测量规格
        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

        //通过MeasureSpec获取测量模式mode
        @MeasureSpecMode
        public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }
        //通过MeasureSpec获取测量大小size
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
        //根据size或mode调整测量规格
        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);
        }

    }

使用MeasureSpec的目的是减少对象内存分配

源码分析

1. 单一View的onMeasure()过程

measure: 计算View的宽/高

measure测量过程的入口为measure()
查看View.java$measure方法核心源码

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

            ......//代码省略

            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                //根据View的宽/高测量规格计算View的宽/高值 
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } 

            ......//代码省略

    }

该方法定义为final类,即子类中不能重写该方法
查看View.java$onMeasure方法源码

  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

   //继续查看setMeasuredDimension方法源码
   protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        //View是否使用了视觉边界布局
        boolean optical = isLayoutModeOptical(this);
        //mParent为ViewParent对象,为该View的父类
        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) {
        // 将测量后View的宽 / 高值进行传递
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

    //查看getDefaultSize方法源码
    public static int getDefaultSize(int size, int measureSpec) {
        //设置默认大小
        int result = size;
      // 获取View的宽/高测量规格模式mode,测量大小size
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        //模式为UNSPECIFIED时,使用默认大小
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        //模式为AT_MOST,EXACTLY时,使用View测量后的宽/高值
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

   //默认大小size = getSuggestedMinimumWidth()/getSuggestedMinimumHeight()
   //分析getSuggestedMinimumHeight()
   protected int getSuggestedMinimumHeight() {
        //若View无设置背景则View的高度为mMinHeight 
        //mMinHeight为android:minHeight属性值,若未设置则为0
        //若设置了View的背景,把背景图高度与mMinHeight相比取最大值,
        return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());

    }

  //继续查看Drawable.java$getMinimumHeight方法源码
  public int getMinimumHeight() {
        //获取背景图Drawable的原始高度
        final int intrinsicHeight = getIntrinsicHeight();
        return intrinsicHeight > 0 ? intrinsicHeight : 0;
    }

以上是单一View的onMeasure测量过程,下面给出其流程图。


单一View的measure过程.jpg
2. 单一View的onLayout() 过程

layout过程: 计算本身View的的位置(left、right、top和bottom)

计算View的位置为Layout方法 查看View.java$layout方法核心源码

 public void layout(int l, int t, int r, int b) {

        ......//代码省略
        
        //当前视图View的四个顶点
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        //判断视图View大小和位置是否发生了变化
        //islayoutModeOptical判断是否使用View的视图边界布局
        //若使用则调用setOpticalFrame方法根据传入的值设置View的四个顶点位置
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            //若View大小和位置发生改变则调用该方法
            //在单一View中因无子View该方法为空方法
            onLayout(changed, l, t, r, b);
          
            ......//代码省略
        
         
    }

 //继续分析setFrame()方法核心源码
 protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;

        ......//代码省略
        
        //判断View位置是否发生改边
        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            changed = true;

            ......//代码省略
        
            //设置View的四个顶点位置
            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

            ......//代码省略
        
        }
        return changed;
    }

  //查看setOpticalFrame方法源码
  private boolean setOpticalFrame(int left, int top, int right, int bottom) {
        Insets parentInsets = mParent instanceof View ?
                ((View) mParent).getOpticalInsets() : Insets.NONE;
        Insets childInsets = getOpticalInsets();
        //内部调用的是setFrame方法
        return setFrame(
                left   + parentInsets.left - childInsets.left,
                top    + parentInsets.top  - childInsets.top,
                right  + parentInsets.left + childInsets.right,
                bottom + parentInsets.top  + childInsets.bottom);
    }
//查看onLayout方法源码
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

    //该方法为空方法,单一View中在layout()中已经对自身View进行了位置计算
    }

layout的整个计算流程


单一View的layout过程.png
3. 单一View的onDraw过程

draw过程: 仅绘制视图View本身
View的绘制入口为draw()方法 查看View.java$draw方法核心源码

  public void draw(Canvas canvas) {
      
        ......//代码省略

        // 步骤1:绘制View自身背景
        int saveCount;
        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        ......//代码省略

        // 步骤2:保存画布图层
        int solidColor = getSolidColor();
        if (solidColor == 0) {
            final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
            if (drawTop) {
                canvas.saveLayer(left, top, right, top + length, null, flags);
            }
            if (drawBottom) {
                canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
            }
            if (drawLeft) {
                canvas.saveLayer(left, top, left + length, bottom, null, flags);
            }
            if (drawRight) {
                canvas.saveLayer(right - length, top, right, bottom, null, flags);
            }
        } else {
            scrollabilityCache.setFadeColor(solidColor);
        }

        // 步骤3:绘制View内容
        if (!dirtyOpaque) onDraw(canvas);

        // 步骤4:绘制子View视图
        dispatchDraw(canvas);

        // 步骤5:绘制淡入淡出效果并还原图层
        final Paint p = scrollabilityCache.paint;
        final Matrix matrix = scrollabilityCache.matrix;
        final Shader fade = scrollabilityCache.shader;

        if (drawTop) {
            matrix.setScale(1, fadeHeight * topFadeStrength);
            matrix.postTranslate(left, top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(left, top, right, top + length, p);
        }

        if (drawBottom) {
            matrix.setScale(1, fadeHeight * bottomFadeStrength);
            matrix.postRotate(180);
            matrix.postTranslate(left, bottom);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(left, bottom - length, right, bottom, p);
        }

        if (drawLeft) {
            matrix.setScale(1, fadeHeight * leftFadeStrength);
            matrix.postRotate(-90);
            matrix.postTranslate(left, top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(left, top, left + length, bottom, p);
        }

        if (drawRight) {
            matrix.setScale(1, fadeHeight * rightFadeStrength);
            matrix.postRotate(90);
            matrix.postTranslate(right, top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(right - length, top, right, bottom, p);
        }

        canvas.restoreToCount(saveCount);

        // Overlay is part of the content and draws beneath Foreground
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }

        // 步骤6:绘制装饰(比如前景色,滑动条)
        onDrawForeground(canvas);
    }

 //查看步骤1 drawBackground方法源码
 private void drawBackground(Canvas canvas) {
        //获取背景图
        final Drawable background = mBackground;
        if (background == null) {
            return;
        }
        //根据layout过程中得到的View的位置参数,来设置背景的边界
        setBackgroundBounds();

        ......// 代码省略
        
        
        final int scrollX = mScrollX;
        final int scrollY = mScrollY;
        if ((scrollX | scrollY) == 0) {
            //调用draw方法绘制背景
            background.draw(canvas);
        } else {
            //若scrollX或scrollY不为0 则对canvas的坐标进行旋转
            canvas.translate(scrollX, scrollY);
            background.draw(canvas);
            canvas.translate(-scrollX, -scrollY);
        }
    }

 //查看setBackgroundBounds方法
 void setBackgroundBounds() {
        if (mBackgroundSizeChanged && mBackground != null) {
            //根据layout计算获取的View的顶点位置,对背景图设置边界
            mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
            mBackgroundSizeChanged = false;
            rebuildOutline();
        }
    }

 //查看步骤3 onDraw方法源码
 protected void onDraw(Canvas canvas) {

    //该方法为空实现需要复写该方法 绘制View自身内容

    }

 //查看步骤4 dispatchDraw方法源码
 protected void dispatchDraw(Canvas canvas) {

    //由于单一View无子View所以该方法为空实现
    //在ViewGroup中该方法为绘制子View

    }

 //查看步骤6 onDrawForeground方法源码
 public void onDrawForeground(Canvas canvas) {
        //绘制滑动bar
        onDrawScrollIndicators(canvas);
        onDrawScrollBars(canvas);

        final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
        if (foreground != null) {

            ......//代码省略

            //绘制前景色
            foreground.draw(canvas);
        }
    }

单一View的draw绘制过程


单一View的draw过程.png

考虑到分析ViewGroup源码会导致篇幅较长,View和ViewGroup绘制过程分开分析。
下一篇为ViewGroup绘制过程源码分析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容