Android Kotlin: Draw arc between two points on a Canvas

Android Kotlin: Draw Arc Between Two Points

This code demonstrates how to draw an arc between two points on an Android Canvas in Kotlin. It provides an interactive interface where users can adjust the positions of the two points using SeekBars, and the drawn arc updates accordingly.

Key Components:

  • Activity: The MainActivity class manages the UI and logic for drawing the arc.
  • Widgets:
    • ImageView: Displays the generated bitmap with the drawn arc.
    • SeekBar (x4): Allow users to define the X and Y coordinates of the two points (point & point2).
    • TextView: Shows the current coordinates of both points.
  • drawArcBetweenTwoPoints Function: This function takes two Point objects as input and performs the following actions:
    • Creates a bitmap with a background color.
    • Defines a Paint object for drawing shapes on the canvas.
    • Determines the "top" and "bottom" points based on their Y coordinates.
    • Calculates the center point between the top and bottom points.
    • Handles special cases for straight vertical and horizontal lines.
    • Calculates the rectangle (RectF) that defines the size and position of the arc.
    • Determines the starting and sweep angles for the arc based on the points' positions.
    • Draws a filled arc and a stroked border around it using canvas.drawArc.

Summary

This code provides a user-friendly way to visualize an arc drawn between two points on an Android device. Users can adjust the points' positions dynamically and see the corresponding changes in the arc's shape and location.


MainActivity.kt

package com.cfsuman.kotlintutorials

import android.app.Activity
import android.graphics.*
import android.os.Bundle
import android.widget.ImageView
import android.widget.SeekBar
import android.widget.TextView
import kotlin.math.min
import kotlin.math.max


class MainActivity : Activity() {
    lateinit var imageView: ImageView
    lateinit var pointX: SeekBar
    lateinit var point2X: SeekBar
    lateinit var pointY: SeekBar
    lateinit var point2Y: SeekBar
    lateinit var textView: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)


        // get the widgets reference from XML layout
        imageView = findViewById(R.id.imageView)
        pointX = findViewById(R.id.pointX)
        pointY = findViewById(R.id.pointY)
        point2X = findViewById(R.id.point2X)
        point2Y = findViewById(R.id.point2Y)
        textView = findViewById(R.id.textView)


        pointX.max = 1500
        pointY.max = 850
        point2X.max = 1500
        point2Y.max = 850

        pointX.progress = 100
        pointY.progress = 100
        point2X.progress = 600
        point2Y.progress = 500

        updateDrawing()

        setSeekBarChangeListener(pointX)
        setSeekBarChangeListener(pointY)
        setSeekBarChangeListener(point2X)
        setSeekBarChangeListener(point2Y)
    }


    private fun setSeekBarChangeListener(seekBar: SeekBar){
        seekBar.setOnSeekBarChangeListener(
            object: SeekBar.OnSeekBarChangeListener{
                override fun onProgressChanged(
                    seekBar: SeekBar?, progress: Int,
                    fromUser: Boolean) {
                    updateDrawing()
                }

                override fun onStartTrackingTouch(seekBar: SeekBar?) {
                }

                override fun onStopTrackingTouch(seekBar: SeekBar?) {
                }
            })
    }


    private fun updateDrawing(){
        val bitmap = drawArcBetweenTwoPoints(
            point = Point(pointX.progress,pointY.progress),
            point2 = Point(point2X.progress, point2Y.progress)
        )
        imageView.setImageBitmap(bitmap)

        textView.text = "( ${pointX.progress} " +
                ": ${pointY.progress} )"
        textView.append("   ( ${point2X.progress} " +
                ": ${point2Y.progress} )")
    }
}



// function to draw arc between two points on canvas
fun drawArcBetweenTwoPoints(
    point: Point = Point(150,400),
    point2: Point = Point(650, 400)
):Bitmap?{
    val bitmap = Bitmap.createBitmap(
        1500,
        850,
        Bitmap.Config.ARGB_8888
    )

    // canvas for drawing
    val canvas = Canvas(bitmap).apply {
        drawColor(Color.parseColor("#A2A2D0"))
    }

    // paint to draw
    val paint = Paint()

    // manage points
    var topPoint = point
    var bottomPoint = point2
    if (point.y >= point2.y) {
        topPoint = point2
        bottomPoint = point
    }
    var centerPoint = Point(topPoint.x,bottomPoint.y)


    // manage strait vertical and horizontal line
    var isVerticalLine = false
    var isHorizontalLine = false

    if (bottomPoint.x == topPoint.x){isVerticalLine = true}
    if (bottomPoint.y == topPoint.y){isHorizontalLine = true}

    if (isVerticalLine){
        val lineLength = bottomPoint.y - topPoint.y
        centerPoint = Point(bottomPoint.x,
            topPoint.y + lineLength/2)
    }

    if (isHorizontalLine){
        val lineLength = max(topPoint.x,bottomPoint.x) -
                min(topPoint.x,bottomPoint.x)
        centerPoint = Point(bottomPoint.x
                + lineLength/2, topPoint.y)
    }


    // mark points
    canvas.drawCircle(
        topPoint.x.toFloat(),
        topPoint.y.toFloat(),
        30F,
        paint.apply {
            color = Color.parseColor("#7BB661")
        }
    )

    canvas.drawCircle(
        centerPoint.x.toFloat(),
        centerPoint.y.toFloat(),
        50F,
        paint.apply {
            color = Color.parseColor("#483D8B")
        }
    )

    canvas.drawCircle(
        bottomPoint.x.toFloat(),
        bottomPoint.y.toFloat(),
        30F,
        paint.apply {
            color = Color.parseColor("#FF5470")
        }
    )


    // manage rectF
    var xLength = max(centerPoint.x,bottomPoint.x) -
            min(centerPoint.x,bottomPoint.x)

    var yLength = centerPoint.y - topPoint.y

    if (isVerticalLine){
        yLength = bottomPoint.y - centerPoint.y
        xLength = yLength
    }

    if (isHorizontalLine){
        xLength = centerPoint.x -
                min(topPoint.x,bottomPoint.x)
        yLength = xLength
    }

    val rectF = RectF(
        centerPoint.x - xLength.toFloat(),
        centerPoint.y - yLength.toFloat(),
        centerPoint.x + xLength.toFloat(),
        centerPoint.y + yLength.toFloat()
    )


    // manage angle for ark
    var startAngle = 270F
    var sweepAngle = 90F
    if (bottomPoint.x < centerPoint.x){
        startAngle = 180F
    }
    if (isHorizontalLine){
        startAngle = 180F
        sweepAngle = 180F
    }
    if (isVerticalLine){
        startAngle = 270F
        sweepAngle = 180F
    }


    // finally, draw ark between two points
    canvas.drawArc(
        rectF,
        startAngle,
        sweepAngle,
        true,
        paint.apply {
            color = Color.parseColor("#333399")
        }
    )


    val paintStroke = Paint().apply {
        isAntiAlias = true
        color = Color.parseColor("#F0FFFF")
        style = Paint.Style.STROKE
        strokeWidth = 10F
    }


    // draw arc border
    canvas.drawArc(
        rectF,
        startAngle,
        sweepAngle,
        true,
        paintStroke
    )

    return bitmap
}
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/rootLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#DCDCDC"
    android:padding="24dp">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="TextView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/imageView" />

    <SeekBar
        android:id="@+id/pointX"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:progressTint="#7BB661"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

    <SeekBar
        android:id="@+id/pointY"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:progressTint="#7BB661"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/pointX" />

    <SeekBar
        android:id="@+id/point2X"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:progressTint="#FF5470"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/pointY" />

    <SeekBar
        android:id="@+id/point2Y"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:progressTint="#FF5470"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/point2X" />

</androidx.constraintlayout.widget.ConstraintLayout>
More android kotlin tutorials