Canvas draw, draw a line drawLine, draw a circle 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); }}Copy the code

Results the following

dp2px

Resources. GetSystem ().getxxx. There is a simpler way to getResources

class Utils { public static float dp2px(float value) { return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, Resources.getSystem().getDisplayMetrics()); }}Copy the code

You can continue to simplify extensions.kt by taking advantage of kotlin’s extension properties

val Float.px
  get() = TypedValue.applyDimension(
      TypedValue.COMPLEX_UNIT_DIP,
      this,
      Resources.getSystem().displayMetrics
  )
Copy the code

And then when you use it, you write dp directly

val RADIUS = 100f.px
Copy the code

Path draw, draw circles, draw squares

Synthetic filling

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {path.reset() // Draw a circle path.addCircle(width/2f,200f, RADIUS, Path, Direction CCW) / / painting side Path. AddRect (width / 2 f - the RADIUS, 200 f, width / 2 f + RADIUS, 200 f + 2 * the RADIUS, the Path, Direction. The CCW)} override fun onDraw(canvas: Canvas) { canvas.drawPath(path,parint) }Copy the code

Reverse, intersecting hollow out

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {path.reset() // Draw a circle path.addCircle(width/2f,200f, RADIUS, Path, Direction CCW) / / painting side Path. AddRect (width / 2 f - the RADIUS, 200 f, width / 2 f + RADIUS, 200 f + 2 * the RADIUS, the Path, Direction. The CW)}Copy the code

fillType

EVEN_ODD, intersecting, odd filling even hollowing

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {path.reset() // Draw a circle path.addCircle(width/2f,200f, RADIUS, Path, Direction CCW) / / painting side Path. AddRect (width / 2 f - the RADIUS, 200 f, width / 2 f + RADIUS, 200 f + 2 * the RADIUS, the Path, Direction. The CW) Path.addcircle (width/2f,200f, RADIUS*1.5f, path.direction.ccw) // Intersects odd to fill even to leave empty path.fillType = path.filltype. EVEN_ODD; }Copy the code

INVERSE_EVEN_ODD reverse, even fill, odd hollow

path.fillType = Path.FillType.EVEN_ODD;
Copy the code

PathMeasure

Fill in the Path object for specific calculation (e.g. graph perimeter) as follows. According to our calculation, the square is 400F perimeter and the printed result is 1100, indicating that the coefficient of dp to PX is 2.75 times

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 = 1100Copy the code

Draw a dashboard

  1. First draw a circle with an opening of 120 degrees

    • You can use canvas.drawArc as follows and draw a 150dp radius fan
    Canvas. DrawArc (width/f - 150 f. 2 px, height / 2-150 - f px, width/f + 150 f. 2 px, height/f + 150 f. 2 px, + OPEN_ANGLE / 2360-90 OPEN_ANGLE,false,paint)Copy the code
    • In order to add the scale effect later, we use path to draw, which is equivalent to the effect above
    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);Copy the code
  2. And then I’m going to set my brush style to hollow with a border

  3. Scale the dial

    • First define the width and height of the scale
    • Then add the dotted scale effect
    • We draw 20 scales for the dial, need to calculate the scale interval, use PathMeasure to measure
    Val pathMessure = PathMeasure(path,false) pathEffect = PathDashPathEffect(dash,(pathmessure.length-dash_width)/20,0f, ROTATE)Copy the code
    • Finally, the second time to draw, draw the scale effect
    SetPathEffect (pathEffect) canvas. DrawPath (path,paint); paint.pathEffect = null;Copy the code
  4. Draw a pointer

    • The coordinates of the pointer need to be calculated by trigonometric functions combining Angle and pointer length
  5. The complete code is as follows

Const val OPEN_ANGLE = 120F // The sector radius val CIRCLE_RADIUS = 150f.px val LENGTH = 120F.px //3.1 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; StrokeWidth = 3f.px paint. Style = paint. Style.STROKE //3.2 Add scale Effect rectangle scale bar 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 Set the calibration interval to 20 intervals 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, 0 f, ROTATE) } @RequiresApi(VERSION_CODES.LOLLIPOP) override fun onDraw(canvas: Canvas) { //1. // Canvas. DrawArc (width/2f-150f.px,height/2-150f.px,width/2f+150f.px,height/2f+150f.px, width/2f+150f.px,height/2f+150f.px, OPEN_ANGLE/2,360- OPEN_ANGLE,false,paint) canvas. DrawPath (path,paint); SetPathEffect (pathEffect) canvas. DrawPath (path,paint); paint.pathEffect = null; DrawLine (width / 2f, height / 2f, width / 2f + LENGTH * cos(markToRadians(5)).tofloat (), drawLine(width / 2f, height / 2f, width / 2f + LENGTH * cos(markToRadians(5)).tofloat (), 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()) }Copy the code

The effect that

Draw the pie chart

  • Draw the sector with drawArc()
  • Move the sector with canvas.translate (), and save and restore the position with canvas.save () and canvas.restore ()
  • Use the trigonometric functions cosine and sine to calculate the offset

Drawing process

  1. First define four sectors and their colors
  2. Then the for loop draws them in turn
  3. Finally, a sector is offset
  4. The complete code is as follows
//1. Private val RADIUS = 25f. Px private val ANGLES = 25f, 25f, 25f 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 loop draw for ((index, Angle)in ANGLES. WithIndex ()){paint. Color = COLORS[index]; 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 Reset canvas if (index==1){canvas.restore()}}}}Copy the code

Results the following