绘制(一)
最重要的是测量
本节目标
掌握基本绘制的三个重点:
- 图形的位置、尺寸、角度的计算
- Xfermode 的使用
- 文字的位置和尺寸计算(挪到下一节)
从而达到对于任意的图形、文字以及二者的结合都能轻松完成绘制。
绘制的 API 不是这一节内容的重点。
绘制的基本要素:
重写
onDraw(Canvas)
使用 Canvas 来绘制
使用 Paint 来配置(调粗细、颜色等)
坐标系
尺寸单位是像素,而不是 dp。转换方式:
1
2
3public static float dp2px(float dp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().getDisplayMetrics());
}
Canvas常用API
- drawColor()
- drawLine()
- drawRect()
- drawRoundRect()
- drawCircle()
- drawOval()
- drawArc()
- drawPoint()
- drawPath()
- drawBitmap()
- drawText()
dp转像素px
1 | public static float dp2px(float dp){ |
layout
结束后大小有变化才会调用onSizeChanged()
cw:clockwise顺时针
ccw:counter clockwise逆时针
Path 的方向以及封闭图形的内外判断:
Winding
(画的方向): 如果方向相反的穿插次数相等则为内部,不等则为外部:单圆:
双圆同向:
中间的是内部(同方向的Winding是1+1=2)
双圆不同向:
中间是外部(不同方向的Winding是1+(-1)=0)
Even Odd
:不考虑方向。穿插奇数次则为内部,偶数次则为外部:(镂空效果可考虑它)- 单圆:
- 双圆:
PathMeasure
把 Path 对象填入,用于对 Path 做针对性的计算(例如图形周长pathMeasure.getLength()
)。
图形一:仪表盘
1 | public Dashboard extends View{ |
用 drawArc() 绘制弧形
三角函数的计算 横向的位移是 cos,纵向的位移是 sin
PathDashPathEffect
加上 PathEffect 之后,就只绘制 effect,而不绘制原图形。所以需要弧线和刻度分别绘制,一共两次。
dash 的方向
绘制 dash 的轨迹是这样的:
而由于 x 轴的正向是轨迹的正向(这是规定),所以实际上的物理模型是这样的:
那么,如果你要加上刻度,就应该这样设置:
然后,你的刻度就会沿着轨迹绘制:
advance 计算
图形二:饼图
- 用 drawArc() 绘制扇形
- 用 Canvas.translate() 来移动扇形,并用 Canvas.save() 和 Canvas.restore() 来保存和恢复位置
- 用三角函数 cos 和 sin 来计算偏移
图形三:圆形图像
- Xfermode:
- 为什么要 Xfermode?为了把多次绘制进行「合成」,例如蒙版效果:用 A 的形状和 B 的图案
- 怎么做?
- Canvs.saveLayer() 把绘制区域拉到单独的离屏缓冲里
- 绘制 A 图形
- 用 Paint.setXfermode() 设置 Xfermode
- 绘制 B 图形
- 用 Paint.setXfermode(null) 恢复 Xfermode
- 用 Canvas.re
绘制(二)
文字的测量
- 绘制⽂字:
drawText()
文字测量难点之一:居中的纵向测量
- ⽅式⼀:
Paint.getTextBounds(
) 之后,使⽤(bounds.top + bounds.bottom) / 2
- ⽅式⼆:
Paint.getFontMetrics()
之后,使⽤(fontMetrics.ascend + fontMetrics.descend) / 2
文字测量难点之二:左对⻬
- 用
getTextBounds()
之后的left
来计算
文字测量难点 之三:换行
- 用
breakText()
来计算
Canvas 的范围裁切
clipRect()
clipPath()
:clipPath()
切出来的圆为什么没有抗锯齿效果?因为“强行切边”clipOutRect()/clipOutPath()
Canvas 的几何变换
translate(x, y)
route(degree)
scale(x, y)
skew(x, y)
重点:
Canvas
的集合变换参照的是View
的坐标系,而绘制方法(drawXxx()
)参照的是Canvas
自己的坐标系。
关于多重变换
Canvas
的变换方法多次调用的时候,由于 Canvas
的坐标系会整体被变换,一次当平移、旋转、放缩、错切等变换多重存在的时候,Canvas
的变换参数会非常难以计算,因此可以改用倒序的理解方式:
将 Canvas 的变换理解为 Canvas 的坐标系不变,每次变换是只对内部的绘制内容进行变换,同时把 Canvas 的变换顺序看作是倒序的(即写在下面的变换先执行),可以更加方便进行多重变换的参数计算。
Matrix的几何变换
preTranslate(x, y) / postTranslate(x, y)
preRotate(degree) / postRotate(degree)
preScale(x, y) / postScale(x, y)
preSkew(x, y) / postSkew(x, y)
其中preXxx()
效果和 Canvas
的准同名⽅法相同,postXxx()
效果和 Canvas
的准同名⽅法顺序相反。
注意
如果多次重复使⽤ Matrix
,在使⽤之前需要⽤Matrix.reset()
来把 Matrix
重置。
使⽤ Camera 做三维旋转
rotate() / rotateX() / rotateY() / rotateZ()
translate()
setLocation()
其中,⼀般只⽤
rotateX()
和rorateY()
来做沿 x 轴或 y 轴的旋转,以及使⽤setLocation()
来调整放缩的视觉幅度。
对 Camera 变换之后,要⽤Camera.applyToCanvas(Canvas)
来应⽤到Canvas
。
setLocation()
这个⽅法⼀般前两个参数都填 0
,第三个参数为负值。由于这个值的单位是硬编码写死的,因此像素密度越⾼的⼿机,相当于 Camera
距离 View
越近,所以最好把这个值写成与机器的 density
成正⽐的⼀个负值,例如 -6 * density
。