Android Kotlin: How to draw a line on a Canvas

This code demonstrates how to draw a line on a Canvas in an Android application written in Kotlin. The code creates an activity with an ImageView, RadioGroup, and several SeekBars. The SeekBars allow users to control the line's starting and ending points, stroke width, and the cap style (how the ends of the line are drawn).

Breakdown

The MainActivity.kt file handles the core functionality:

  1. Initialization: It inflates the layout (activity_main.xml), retrieves references to UI elements (ImageView, RadioGroup, SeekBars, TextView), and sets initial values for the SeekBars.

  2. SeekBar Listener: A generic listener is set for all SeekBars. Whenever the progress of a SeekBar changes (due to user interaction), the updateDrawing function is called.

  3. updateDrawing: This function performs the following actions:

    • Determines the line cap style based on the selected radio button in the RadioGroup.
    • Creates a new Bitmap object to serve as the drawing surface.
    • Creates a Canvas object associated with the Bitmap.
    • Initializes a Paint object with desired properties (anti-aliasing, color, stroke style, stroke width, and cap style).
    • Draws the line on the Canvas using drawLine with the user-defined starting and ending points obtained from the SeekBar progress values.
    • Sets the newly created Bitmap as the image source for the ImageView.
    • Updates the TextView to display the coordinates of the starting and ending points.

The drawSingleLine function (defined separately) is responsible for creating the Bitmap, Canvas, Paint object, and drawing the line based on the provided parameters.

The activity_main.xml file defines the layout of the activity screen, including the ImageView to display the drawn line, a TextView to show the line's coordinates, a RadioGroup to select the line cap style, and several SeekBars to control the line properties.


MainActivity.kt

package com.cfsuman.kotlintutorials

import android.app.Activity
import android.graphics.*
import android.os.Bundle
import android.widget.ImageView
import android.widget.RadioGroup
import android.widget.SeekBar
import android.widget.TextView


class MainActivity : Activity() {
    lateinit var imageView: ImageView
    lateinit var radioGroup: RadioGroup
    lateinit var borderWidth: SeekBar
    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)
        radioGroup = findViewById(R.id.radioGroup)
        borderWidth = findViewById(R.id.borderWidth)
        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)


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

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

        updateDrawing()

        setSeekBarChangeListener(borderWidth)
        setSeekBarChangeListener(pointX)
        setSeekBarChangeListener(pointY)
        setSeekBarChangeListener(point2X)
        setSeekBarChangeListener(point2Y)

        radioGroup.setOnCheckedChangeListener { group, checkedId ->
            updateDrawing()
        }
    }


    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 cap:Paint.Cap = when(radioGroup.checkedRadioButtonId){
            R.id.square -> Paint.Cap.SQUARE
            R.id.round -> Paint.Cap.ROUND
            R.id.but -> Paint.Cap.BUTT
            else -> Paint.Cap.SQUARE
        }

        val bitmap = drawSingleLine(
            point = Point(pointX.progress,pointY.progress),
            point2 = Point(point2X.progress, point2Y.progress),
            strokeWidth = borderWidth.progress.toFloat(),
            cap = cap
        )
        imageView.setImageBitmap(bitmap)

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



// function to draw single line on canvas
fun drawSingleLine(
    point: Point = Point(150,400),
    point2: Point = Point(650, 400),
    strokeWidth : Float = 25F,
    cap : Paint.Cap = Paint.Cap.SQUARE
):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().apply {
        isAntiAlias = true
        color = Color.parseColor("#333399")
        style = Paint.Style.STROKE
        this.strokeWidth = strokeWidth
        strokeCap = cap
    }


    // finally, draw the line on canvas
    canvas.drawLine(
        point.x.toFloat(), // start x
        point.y.toFloat(), // start y
        point2.x.toFloat(), // stop x

        point2.y.toFloat(), // stop y
        paint // paint
    )

    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" />

    <RadioGroup
        android:id="@+id/radioGroup"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/textView">

        <RadioButton
            android:id="@+id/square"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Cap Square" />

        <RadioButton
            android:id="@+id/round"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Round"
            />

        <RadioButton
            android:id="@+id/but"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="But"
            />
    </RadioGroup>

    <SeekBar
        android:id="@+id/borderWidth"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:progressTint="#3F51B5"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/radioGroup" />

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

    <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_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