Drawing Custom Views Using XML Canvas

In Android development, creating custom views allows you to build reusable UI components tailored to your application’s specific needs. While it’s common to draw custom views programmatically using the Canvas API, leveraging XML for defining these views can simplify the process and improve maintainability. In this post, we’ll explore how to draw custom views using XML and Canvas in Android, focusing on defining shapes, paths, and attributes.

What are Custom Views?

Custom views are UI components that you create to extend or modify existing Android View classes. This allows you to implement unique designs, animations, or interactive elements that are not available through the standard Android widgets.

Why Draw Custom Views Using XML and Canvas?

  • Separation of Concerns: Define the structure and attributes of your view in XML and handle the drawing logic in your custom view class.
  • Improved Readability: XML provides a clear, declarative way to define view attributes.
  • Reusability: Easily reuse custom views in different layouts.
  • Maintainability: Simplifies modifications and updates to the view’s structure and appearance.

How to Draw Custom Views Using XML Canvas

To create custom views with XML and Canvas, follow these steps:

Step 1: Create a Custom View Class

First, create a custom view class that extends View and override the necessary methods such as the constructor and onDraw().


import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View

class CustomView(context: Context, attrs: AttributeSet?) : View(context, attrs) {

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.BLUE
        style = Paint.Style.FILL
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        // Draw a rectangle
        canvas.drawRect(100f, 100f, 500f, 500f, paint)
    }
}

In this example:

  • The CustomView class extends View and takes a Context and AttributeSet in its constructor.
  • The Paint object is created to define the color and style of the drawing.
  • The onDraw() method is overridden to draw a blue rectangle on the canvas.

Step 2: Define Attributes in attrs.xml

Create a file named attrs.xml in the res/values directory to define custom attributes for your view.


<resources>
    <declare-styleable name="CustomView">
        <attr name="rectColor" format="color"/>
    </declare-styleable>
</resources>

This defines an attribute rectColor that allows you to specify the color of the rectangle in XML.

Step 3: Update Custom View Class to Use Attributes

Modify your custom view class to read and use the custom attributes defined in attrs.xml.


import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View

class CustomView(context: Context, attrs: AttributeSet?) : View(context, attrs) {

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.FILL
    }

    private var rectColor: Int = Color.BLUE

    init {
        attrs?.let {
            val typedArray = context.obtainStyledAttributes(it, R.styleable.CustomView)
            rectColor = typedArray.getColor(R.styleable.CustomView_rectColor, Color.BLUE)
            paint.color = rectColor
            typedArray.recycle()
        }
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        // Draw a rectangle
        canvas.drawRect(100f, 100f, 500f, 500f, paint)
    }
}

In this updated code:

  • Inside the constructor, we obtain the styled attributes using context.obtainStyledAttributes().
  • The rectColor attribute is read using typedArray.getColor().
  • The color of the Paint object is set based on the value of rectColor.
  • The typedArray is recycled to free resources.

Step 4: Use Custom View in XML Layout

Now, use your custom view in your layout XML file and specify the rectColor attribute.


<?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:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.myapp.CustomView
        android:id="@+id/customView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:rectColor="@color/red" />

</androidx.constraintlayout.widget.ConstraintLayout>

In this XML:

  • The CustomView is declared using its fully qualified name.
  • The rectColor attribute is set to @color/red, which should be defined in your colors.xml.

Step 5: Drawing Shapes, Paths, and Text

Inside the onDraw() method, you can use the Canvas object to draw various shapes, paths, and text.


import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import android.util.AttributeSet
import android.view.View

class CustomView(context: Context, attrs: AttributeSet?) : View(context, attrs) {

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.FILL
        color = Color.GREEN
    }

    private val path = Path().apply {
        moveTo(100f, 700f)
        lineTo(300f, 900f)
        lineTo(500f, 700f)
        close()
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        // Draw a circle
        canvas.drawCircle(200f, 200f, 150f, paint)

        // Draw a rectangle
        canvas.drawRect(400f, 100f, 600f, 300f, paint)

        // Draw a path
        canvas.drawPath(path, paint)

        // Draw text
        paint.color = Color.WHITE
        paint.textSize = 48f
        canvas.drawText("Hello, Custom View!", 100f, 600f, paint)
    }
}

In this extended example:

  • A circle is drawn using canvas.drawCircle().
  • A rectangle is drawn using canvas.drawRect().
  • A path (triangle) is drawn using canvas.drawPath().
  • Text is drawn using canvas.drawText().

Code Samples

Basic Example

Here’s a full, basic example that draws a customizable rectangle:


// CustomView.kt
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View

class CustomView(context: Context, attrs: AttributeSet?) : View(context, attrs) {

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.FILL
    }

    private var rectColor: Int = Color.BLUE

    init {
        attrs?.let {
            val typedArray = context.obtainStyledAttributes(it, R.styleable.CustomView)
            rectColor = typedArray.getColor(R.styleable.CustomView_rectColor, Color.BLUE)
            paint.color = rectColor
            typedArray.recycle()
        }
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.drawRect(100f, 100f, 500f, 500f, paint)
    }
}

<!-- res/values/attrs.xml -->
<resources>
    <declare-styleable name="CustomView">
        <attr name="rectColor" format="color"/>
    </declare-styleable>
</resources>

<!-- layout XML -->
<com.example.myapp.CustomView
    android:id="@+id/customView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:rectColor="@color/red" />

Advanced Example

This advanced example combines shapes, paths, text, and attributes for greater customization:


// CustomView.kt
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import android.util.AttributeSet
import android.view.View

class CustomView(context: Context, attrs: AttributeSet?) : View(context, attrs) {

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.FILL
    }

    private var rectColor: Int = Color.BLUE
    private var textColor: Int = Color.WHITE

    init {
        attrs?.let {
            val typedArray = context.obtainStyledAttributes(it, R.styleable.CustomView)
            rectColor = typedArray.getColor(R.styleable.CustomView_rectColor, Color.BLUE)
            textColor = typedArray.getColor(R.styleable.CustomView_textColor, Color.WHITE)
            paint.color = rectColor
            typedArray.recycle()
        }
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        // Draw a rectangle
        canvas.drawRect(100f, 100f, 500f, 500f, paint)

        // Draw text
        paint.color = textColor
        paint.textSize = 48f
        canvas.drawText("Custom View", 150f, 300f, paint)
    }
}

<!-- res/values/attrs.xml -->
<resources>
    <declare-styleable name="CustomView">
        <attr name="rectColor" format="color"/>
        <attr name="textColor" format="color"/>
    </declare-styleable>
</resources>

<!-- layout XML -->
<com.example.myapp.CustomView
    android:id="@+id/customView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:rectColor="@color/green"
    app:textColor="@color/black" />

Conclusion

Drawing custom views using XML Canvas offers a flexible and organized approach to Android UI development. By defining attributes in XML and implementing drawing logic in your custom view class, you can create reusable and maintainable UI components. Whether you’re drawing basic shapes, complex paths, or text, combining XML with Canvas provides the power and flexibility needed to build rich and engaging user interfaces.