Android 自定义 View 三 (坐标系与 View 的绘制流程)

1,334 阅读4分钟
原文链接: www.jianshu.com

涉及知识

绘制过程涉及到主要方法

类别 API 描述
布局 onMeasure 测量View与Child View的大小
onLayout 确定Child View的位置
onSizeChanged 确定View的大小
绘制 onDraw 实际绘制View的内容
事件处理 onTouchEvent 处理屏幕触摸事件
重绘 invalidate 调用onDraw方法,重绘View中变化的部分

Canvas涉及到主要方法

类别 API 描述
绘制图形 drawPoint、drawPoints、drawLine、drawLines、drawRect、drawRoundRect、drawOval、drawCircle、drawArc 依次为绘制点、直线、矩形、圆角矩形、椭圆、圆、散形
绘制文本 drawText、drawPosText、drawTextOnPath 依次为绘制文字、指定每个字符位置绘制文字、根据路径绘制文字
画布变换 translate、scale、rotate、skew 依次为平移、缩放、旋转、倾斜(错切)
画布裁剪 clipPath、clipRect、clipRegion 依次为按路径、按矩形、按区域或对画布进行裁剪

Paint涉及到主要方法

类别 API 描述
颜色 setColor、setARGB、setAlpha 依次为设置画笔颜色、透明度
类型 setStyle 填充(FILL),描边(STROKE),填充加描边(FLIL_STROKE)
抗锯齿 setAntiAlias 画笔是否抗锯齿
字体大小 setTextSize 设置字体到大小
字体测量 getFontMetrics()、getFontMetricsInt() 返回字体测量,返回依次为float、int
文字宽度 measureText 返回文字到宽度
文字对齐方式 setTextAlign 左对齐(LEFT)、居中对齐(CENTER)、右对齐(RIGHT)
宽度 setStrokeWidth 设置画笔宽度
笔锋 setStrokeCap 默认(BUTT),半圆形(ROUND),方形(SQUARE)
(注:因API较多,只列出常用到方法,想了解更多,请查看官方文档)

一、坐标系

1、屏幕坐标系

屏幕坐标系以手机屏幕到左上角为坐标原点,过原点的水平直线为X轴,向右为正方向;过原点的垂线为Y轴,向下为正方向;


屏幕坐标系.png
2、View坐标系

View坐标系以父视图到左上角为坐标原点,过原点的水平直线为X轴,向右为正方向;过原点的垂线为Y轴,向下为正方向


View坐标系.png

View内部拥有四个函数,用于获取View到位置

getTop(); //View的顶边到其Parent View的顶边的距离,即View的顶边与View坐标系的X轴之间的距离
getLeft(); //View的左边到其Parent View的左边的距离,即View的左边与View坐标系的Y轴之间的距离
getBottom(); //View的底边到其Parent View的顶边的距离,即View的底边与View坐标系的X轴之间的距离
getRight(); //View的右边到其Parent View的左边的距离,即View的右边与View坐标系的Y轴之间的距离

如图所示


View位置图.png

二、绘制过程

1、自定义属性

参考Android 自定义View二(深入了解自定义属性attrs.xml)

2、onMeasure

参考Android 自定义View 一(初体验onDraw(),自定义属性,onMeasue()方法,测量换行)

3、onLayout

用于确定View以及其子View的布局位置,在ViewGroup中,当位置被确定后,它在onLayout中会遍历所有到child并调用其layout,然后layout内部会再调用child到onLayout确定child view到布局位置
layout放法如下

 public void layout(int l, int t, int r, int b) {
      ····
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

      ····
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

   ·····
    }

mLeft,mTop,mBottom,mRight四个参数分别通过getLeft(),getTop(),getBottom(),getRight(),四个函数获得,这一组old值会在位置改变时,调用onLayoutChanges时使用到

4、onSizeChanged

如其名,在View大小改变是调用此函数,用于确定View的大小。至于View大小为什么会改变,因为View的大小不仅由本身确定,同事还受父View到影响

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
}

这里到w、h就是确定后到高宽值,如果查看View中的onLayoutChange也会看到类似的情况,拥有l, t, r, b, oldL, oldT, oldR, oldB,新旧两组参数

5、onDraw

onDraw是View的绘制部分,给了我们一张空白的画布,使用Canvas进行绘制。也是后面几篇文章所要分享的内容。

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
}

6、onTouchEvent

@Override
public boolean onTouchEvent(MotionEvent event) {
    return super.onTouchEvent(event);
}

当返回true时,说明该View消耗了触摸事件,后续到触摸事件由它来进行处理。返回false时,说明该View对触摸事件不感兴趣,事件继续传递下去。触屏事件类型被封装在MotionEvent中,MotionEvent提供了很多类型事件,主要关系以下几种类型:

事件类型 描述
ACTION_DOWN 手指按下
ACTION_MOVE 手指移动
ACTION_UP 手指抬起

在MotionEvent中有两组可以获得触摸位置到函数

event.getX(); //触摸点相对于View坐标系的X坐标
event.getY(); //触摸点相对于View坐标系的Y坐标
event.getRawX(); //触摸点相对于屏幕坐标系的X坐标
event.getRawY(); //触摸点相对于屏幕坐标系的Y坐标

如图所示:


触摸位置.png

onWindowFocusChanged运行于onMeasure与onLayout之后,可以获取到正确的width、height、top、left等属性值。