canvas绘制,画一条线 drawLine, 画一个圆 drawCircle
val RADIUS = 50f.px
class TestView(context:Context?,attrs:AttributeSet):View(context,attrs) {
private val parint = Paint(Paint.ANTI_ALIAS_FLAG)
override fun onDraw(canvas: Canvas) {
canvas.drawLine(100f,100f,200f,200f,parint)
canvas.drawCircle(width/2f,200f,
RADIUS,parint);
}
}
效果如下
dp2px
以前开发很多人用的都是context.getResources().getxxX的方式获取资源,其实有一种更简单的方式Resources.getSystem().getXxx,这样就不需要Context了
class Utils {
public static float dp2px(float value) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, Resources.getSystem().getDisplayMetrics());
}
}
利用kotlin的扩展属性,可以继续简化
Extensions.kt
val Float.px
get() = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
this,
Resources.getSystem().displayMetrics
)
然后使用的时候直接写dp值
val RADIUS = 100f.px
path绘制,画圆,画方形
同向填充
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
path.reset()
//画圆
path.addCircle(width/2f,200f, RADIUS,Path.Direction.CCW)
//画方
path.addRect(width/2f- RADIUS,200f,width/2f+ RADIUS,200f+2*RADIUS,Path.Direction.CCW)
}
override fun onDraw(canvas: Canvas) {
canvas.drawPath(path,parint)
}
反向,相交镂空
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
path.reset()
//画圆
path.addCircle(width/2f,200f, RADIUS,Path.Direction.CCW)
//画方
path.addRect(width/2f- RADIUS,200f,width/2f+ RADIUS,200f+2*RADIUS,Path.Direction.CW)
}
fillType
EVEN_ODD,相交,奇数填充 偶数镂空
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
path.reset()
//画圆
path.addCircle(width/2f,200f, RADIUS,Path.Direction.CCW)
//画方
path.addRect(width/2f- RADIUS,200f,width/2f+ RADIUS,200f+2*RADIUS,Path.Direction.CW)
path.addCircle(width/2f,200f, RADIUS*1.5f,Path.Direction.CCW)
//相交 奇数填充 偶数留空
path.fillType = Path.FillType.EVEN_ODD;
}
INVERSE_EVEN_ODD 反向,偶数填充,奇数镂空
path.fillType = Path.FillType.EVEN_ODD;
PathMeasure
把 Path 对象填入,用于对 Path 做针对性的计算(例如图形周⻓)
如下,根据我们的计算,方形是400f周长,打印结果是1100,说明dp转px的系数是2.75倍
var pathMeasure:PathMeasure
path.addRect(width/2f- RADIUS,200f,width/2f+ RADIUS,200f+2*RADIUS,Path.Direction.CW)
pathMeasure = PathMeasure(path,false)
Log.d(TAG, "onSizeChanged: "+pathMeasure.length)//100f*4 * 2.75 = 1100
画一个仪表盘
-
首先画一个开口为120度的圆形
- 可以使用canvas.drawArc方式,如下,画一个150dp半径的扇形
canvas.drawArc(width/2f-150f.px,height/2-150f.px,width/2f+150f.px,height/2f+150f.px,90 + OPEN_ANGLE/2,360- OPEN_ANGLE,false,paint)- 为了方便后续添加刻度效果,我们使用path绘制,和上面的效果是等效的
path.addArc(width/2f-CIRCLE_RADIUS,height/2-CIRCLE_RADIUS,width/2f+CIRCLE_RADIUS,height/2f+CIRCLE_RADIUS, 90 + OPEN_ANGLE/2,360- OPEN_ANGLE) canvas.drawPath(path,paint); -
然后画笔风格设置为空心有边框
-
为表盘画上刻度
- 首先定义刻度的宽高大小
- 然后添加虚线刻度效果
- 我们为表盘绘制20个刻度,需要算出刻度间隔,使用PathMeasure测量一下
val pathMessure = PathMeasure(path,false) pathEffect = PathDashPathEffect(dash,(pathMessure.length - DASH_WIDTH)/20,0f, ROTATE)- 最后第二遍绘制,绘制出刻度效果
//3.4 画刻度(第二遍绘制) paint.setPathEffect(pathEffect) canvas.drawPath(path,paint); paint.pathEffect = null; -
画指针
- 需要结合角度和指针长度,通过三角函数计算指针的坐标
-
完整代码如下
const val OPEN_ANGLE = 120F
//扇形半径
val CIRCLE_RADIUS = 150f.px
val LENGTH = 120f.px
//3.1 虚线宽高
val DASH_WIDTH = 2f.px
val DASH_LENGTH = 10f.px
class DashboardView (context: Context?,attrs: AttributeSet): View(context,attrs){
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val dash = Path();
private val path = Path();
private lateinit var pathEffect: PathDashPathEffect;
init {
//2.画笔风格设置为空心有边框
paint.strokeWidth = 3f.px
paint.style = Paint.Style.STROKE
//3.2 添加刻度效果 矩形刻度条
dash.addRect(0f,0f, DASH_WIDTH, DASH_LENGTH,Path.Direction.CCW)
}
@RequiresApi(VERSION_CODES.LOLLIPOP)
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
//3.3 设置刻度间隔 20个间隔
path.reset()
path.addArc(width/2f-CIRCLE_RADIUS,height/2-CIRCLE_RADIUS,width/2f+CIRCLE_RADIUS,height/2f+CIRCLE_RADIUS,
90 + OPEN_ANGLE/2,360- OPEN_ANGLE)
val pathMessure = PathMeasure(path,false)
pathEffect = PathDashPathEffect(dash,(pathMessure.length - DASH_WIDTH)/20,0f, ROTATE)
}
@RequiresApi(VERSION_CODES.LOLLIPOP)
override fun onDraw(canvas: Canvas) {
//1.首先画一个开口为120度的圆形(第一遍绘制)
// canvas.drawArc(width/2f-150f.px,height/2-150f.px,width/2f+150f.px,height/2f+150f.px,
// 90 + OPEN_ANGLE/2,360- OPEN_ANGLE,false,paint)
canvas.drawPath(path,paint);
//3.4 画刻度(第二遍绘制)
paint.setPathEffect(pathEffect)
canvas.drawPath(path,paint);
paint.pathEffect = null;
//4 画指针 指向第5格(第六个刻度)
canvas.drawLine(width / 2f, height / 2f,
width / 2f + LENGTH * cos(markToRadians(5)).toFloat(),
height / 2f + LENGTH * sin(markToRadians(5)).toFloat(), paint)
}
private fun markToRadians(mark: Int) =
Math.toRadians((90 + OPEN_ANGLE / 2f + (360 - OPEN_ANGLE) / 20f * mark).toDouble())
}
效果这样
画饼图
- 用 drawArc() 绘制扇形
- 用 Canvas.translate() 来移动扇形,并用 Canvas.save() 和 Canvas.restore() 来 保存和恢复位置
- 用三⻆函数 cos 和 sin 来计算偏移
绘制流程
- 首先定义四个扇形及其颜色
- 然后for循环依次绘制他们
- 最后对某个扇形进行偏移
- 完整代码如下
//1. 半径,角度,颜色设置
private val RADIUS = 150f.px
private val ANGLES = floatArrayOf(60f, 90f, 150f, 60f)
private val COLORS = listOf(Color.parseColor("#C2185B"), Color.parseColor("#00ACC1"), Color.parseColor("#558B2F"), Color.parseColor("#5D4037"))
private val OFFSET_LENGTH = 20f.px
class PieView (context: Context?,attrs: AttributeSet): View(context,attrs){
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
@RequiresApi(VERSION_CODES.LOLLIPOP)
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
}
@RequiresApi(VERSION_CODES.LOLLIPOP)
override fun onDraw(canvas: Canvas) {
//画弧
var startAngle = 0f;
//2 for循环绘制
for ((index,angle)in ANGLES.withIndex()){
paint.color = COLORS[index];
//3.1 偏移某个扇形
if (index==1){
canvas.save()
canvas.translate(
(OFFSET_LENGTH*Math.cos(Math.toRadians((startAngle+angle/2).toDouble()))).toFloat(),
(OFFSET_LENGTH*Math.sin(Math.toRadians((startAngle+angle/2).toDouble()))).toFloat()
)
}
canvas.drawArc(width/2f-RADIUS,height/2-RADIUS,width/2f+RADIUS,height/2f+RADIUS,
startAngle,angle,true,paint)
startAngle += angle;
//3.2 重置canvas
if (index==1){
canvas.restore()
}
}
}
}
效果如下