Placing a Custom View inside a ConstraintLayout

Viewed 2517

I am new to implementing a custom view in Android. I have implemented a custom view (a simple rectangle). Here the code:

class CustomView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        // Paint styles used for rendering are initialized here. This
        // is a performance optimization, since onDraw() is called
        // for every screen refresh.
        style = Paint.Style.FILL
        textAlign = Paint.Align.CENTER
        textSize = 55.0f
        typeface = Typeface.create("", Typeface.BOLD)
        color = Color.GREEN
    }

    private val rectangle = RectF(100f, 100f, 500f, 120f)

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        canvas?.drawRect(rectangle, paint)
    }
}

Due to my layout file, it should be placed in the middle of the screen:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".CustomViewFragment">

        <com.example.learningcustomviewimplementation.CustomView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

But it is placed near the top left corner. From what I see in the output, the constraints are not used. What do I need to change so that the custom view is placed based on the constraints I use in the layout file ?

2 Answers

As you said, your view is not correctly laid into the layout because its dimensions are not specified. When android:layout_width and/or android:layout_height of a view are defined wrap_content, the view itself should calculate its dimensions. In this way, the container layout knows the child's width and height. However, you can do this by overriding onMeasure like the following to achieve this.


CustomView.kt

import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View


class CustomView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private var viewWidth = 0

    var text: String = ""
        set(value) {
            field = value
            calculateSize()
            invalidate()
        }

    var textSize = 20f
        set(value) {
            field = value
            paint.textSize = value
            calculateSize()
            invalidate()
        }

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).also {
        // Paint styles used for rendering are initialized here. This
        // is a performance optimization, since onDraw() is called
        // for every screen refresh.
        it.style = Paint.Style.FILL
        it.textAlign = Paint.Align.CENTER
        it.textSize = textSize
        it.typeface = Typeface.create("", Typeface.BOLD)
        it.color = Color.GREEN
    }

    init {
        calculateSize()
        if (isInEditMode) {
            invalidate()
        }
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val width = paddingLeft + viewWidth + paddingRight
        val height = paddingTop + textSize.toInt() + paddingBottom
        setMeasuredDimension(width, height)
    }

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

    private fun calculateSize() {
        val bounds = Rect()
        paint.getTextBounds(text, 0, text.length, bounds)
        viewWidth = bounds.width()
    }

    private fun drawText(canvas: Canvas) {
        val x = paddingLeft + viewWidth / 2f
        var y = paddingTop + textSize / 2f
        paint.run {
            y -= ((descent() + ascent()) / 2)
            canvas.drawText(text, x, y, this)
        }
    }
}

MainActivity.kt

class MainActivity : AppCompatActivity() {

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

        customView.text = "Hello World!"
        customView.textSize = 80f
    }
}

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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.learningcustomviewimplementation.CustomView
        android:id="@+id/customView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#AEAEAE"
        android:padding="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Result:

enter image description here

Related