android View的测量和绘制

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android View的测量和绘制相关的知识,希望对你有一定的参考价值。

本篇内容来源于android 群英传(徐易生著)

我写到这里,是觉得徐易生讲的确实很好, 另外加入了一些自己的理解,便于自己基础的提高.

如果要绘制一个View , 就需要先取测量它,也就是需要知道它的大小和位置. 这样我们就能在屏幕中滑出来它了.这个过程是在onMeasure()方法中完成的.

一.测量模式

测量view的大小时,需要用到MeasureSpec (测量规范)这个类来指定测量模式 ,一共有3种

EXACTLY (精确模式) , 系统默认值.
如果我们指定控件宽高为 xxdp, xxpx,match_parent(填充父view大小) 这3个中的任意一个那它就是精确模式.

AT_MOST (最大值模式)
这个最大值是啥意思呢? 迷茫很久, 比如父控件的子控件(1个或多个),而子控件的大小又是warp_content ,那么控件的大小就会随着它子控件的内容变化而变化, 此时子控件的尺寸只有不超过父控件允许的最大尺寸即可.
比如父控件指定大小为200 ,那么我们可以这么取值

result = 200;
if (specMode == MeasureSpec.AT_MOST) {
      result = Math.min(result, specSize);//取出 2者最小值,如果specSize(子控件大小)大于父控件规定的200,就使用200,否则使用specSize
}

UNSPECIFIED

这个属性用的不是太多. --它不指定其大小测量模式,View想多大就多大,通常情况下绘制自定义View才会使用.

 

二.什么时候使用onMeasure()

首先要说明的一点是, 这个方法不是必须重写的. View类默认的onMeasure()只支持EXACTLY(精确)模式,如果在自定义控件是不重写它,就只能使用EXACTLY模式. 控件可以响应你指定的具体宽高dp或px或match_parent属性.

而如果要想让自定义View支持wrap_content属性,那么就必须重写onMeasure方法来指定warp_content时的大小.

通过MeasureSpec这个类,我们就获取了View的"测量模式"和View想要绘制的大小. 有了这些信息,我们就可以控制View最终显示的大小.

首先来看一下onMeasure()的方法

/**
     * 测量View的大小
     * 首先这个方法不是必须要重写的.(只有控件使用wrap_content时,才必须重写该方法)
     * 在自定义view时, MeasureSpec 这个测量规范类,定义了3中测量规范: exactly, at_most,unspecified
     * 如果我们不重写onMeasure() ,系统默认测量规范是并且只能是exactly 
     * 重写了该方法,就可以使用以上3种模式的任意一个
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //父类onMeasure内部调用了setMeasuredDimension(宽,高) 将最终控件测量的宽高值填进去
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

 

三.View的绘制

当测量好一个View,就可以绘制他了.绘制需要在 onDraw(Canvas canvas)方法中绘制, 需要用到 Canvas 画布类和Paint 画笔类.

这个方法携带了一个Canvas参数,我们可以创建一个Paint对象就可以在这个画布上面绘制了. 当然如果其他地方需要用到画布, 我们一般会单独创建一个 Canvas canvas=new Canvas(bitmap);

/**
     * 创建canvas 画布时,一般都用这个构造方法.而不用无参构造方法.
     * 因为这个Bitmap是用来存储Canvas画布上面的像素信息的.
     * 这个过程我们称之为装载画布,所以这种方式创建画布后,后面调用的所有canvas.drawXxx();方法都发生在这个bitmap上.
     */
    Canvas canvas = new Canvas(bitmap);

四.ViewGroup的测量

ViewGroup ,比如LinearLayout它要去管理它的子View, 其中一个管理项目就是负责 内部子View的显示大小. 当LinearLayout大小为warp_content时,LinearLayout就需要对子View进行遍历, 一遍获取所有子View的大小,从而决定自己的大小. 而在其他模式下则会通过具体制定值来设置自身大小.

LinearLayout在测量时通过遍历所有子View,从而调用子View的Measure方法来获取每一个子View测量结果. 当测量完毕后,就要摆放这些子View的位置了, 需要重写onLayout , 遍历调用子view的onlayout 来确定子view位置.

注意: 在自定义ViewGroup时,通常会重写 onLayout来控制其子view显示的位置, 同时如果需要wrap_content属性,还需要重写onMeasure() 来测量子view的大小.

五.ViewGroup的绘制

通常情况下ViewGroup不需要绘制,因为它就是一个容器,本身没什么好绘制的,只有它指定了背景色,才会调用它的onDraw,否则不会调用.但是ViewGroup会使用dispatchDraw() 方法来绘制其子View, 过程和通过遍历所有子View,并调用子View的onDraw()方法来完成绘制是一样的.

六.自定义View

自定义View需要注意的几个回调方法

onFinishInflate()  xml 加载后回调

onSizeChanged()  大小改变后回调

onMeasure()  测量控件大小

onLayout()  设置控件位置

onTouchEvent()  控件触摸事件

自定义View并不需要重写以上所有回调,根据需要进行即可.

六. 什么样情况下才使用自定义View?

对现有控件扩展

通过组合来实现新控件

重写View来实现全新控件

6.1 对现有控件扩展

比如一个TextView,我们要给他绘制几层背景, 比如给他绘制2层背景,然后最上面是文字. 原生的TextView使用onDraw方法用于绘制要显示的文字,也就是 调用 super.onDraw(); 

所以我们在extends TextView之后,需要重写 onDraw, 然后我们可以在这里 绘制2个 矩形背景,最后要调用super.onDraw用于显示文字.

public class MyTextView extends TextView {

    private Paint mPaint1, mPaint2;

    public MyTextView(Context context) {
        super(context);
        initView();
    }

    public MyTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public MyTextView(Context context, AttributeSet attrs,
                      int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    private void initView() {
        mPaint1 = new Paint();
        mPaint1.setColor(getResources().getColor(
                android.R.color.holo_blue_light));
        mPaint1.setStyle(Paint.Style.FILL);
        mPaint2 = new Paint();
        mPaint2.setColor(Color.YELLOW);
        mPaint2.setStyle(Paint.Style.FILL);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // 绘制外层矩形
        canvas.drawRect(
                0,
                0,
                getMeasuredWidth(),
                getMeasuredHeight(),
                mPaint1);
        // 绘制内层矩形
        canvas.drawRect(
                25,
                25,
                getMeasuredWidth() - 25,
                getMeasuredHeight() - 25,
                mPaint2);
        canvas.save(); //在画布移动,旋转,缩放之前,需要先保存它的状态
        // 绘制文字前平移10像素
        canvas.translate(10, 0);
        // 调用父类完成的方法,即绘制文本
        super.onDraw(canvas);
        canvas.restore();//取出保存后的状态,他和Canvas.save是对应的.
    }
}

6.2创建复合控件

比如创建一个自定义的通用TopBar

...后续明天再写

 

以上是关于android View的测量和绘制的主要内容,如果未能解决你的问题,请参考以下文章

[译]Android view 测量布局和绘制的流程

android基础-viewgroup的测量,布局,绘制

Android面试收集录12 View测量布局及绘制原理

反思|Android View机制设计与实现:测量流程

Android绘制源码分析(下)

Android组件体系之视图绘制