Anand A
Anand A

Reputation: 93

onDraw gets called before onMeasure in a custom view

I have two method in custom view, onMeasure and onDraw. I have a function that is used to set the data from the source and use invalidate the view function to redraw the view. I use the onMeasure to get the width of the view that is needed for my calculation. But when i use the invalidate function onDraw get called first and then my onMeasure gets called. Hence my view width is always 0px.

I have tried calling requestLayout() and then invalidate() to redraw the view

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

    val pointY = 15.px.toFloat()
    var pointX = oneCellWidth.toFloat() / 2f

    totalDays.forEach { day ->
        val isWorkedDay = workedDays.filter { it.date == day.date }.size
        if (isWorkedDay > 0) {
            canvas?.drawCircle(pointX, pointY, 8f, circlePaint)
        }
        pointX += oneCellWidth
    }
}

fun submitData(totalDays: List<Day>, workedDays: List<WorkedDateAndTime>, color: Int) {
    this.totalDays = totalDays
    this.workedDays = workedDays
    circlePaint.color = color
    oneCellWidth = viewWidth / totalDays.size
    invalidate()
}

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)

    val widthMode = MeasureSpec.getMode(widthMeasureSpec)
    val widthSize = MeasureSpec.getSize(widthMeasureSpec)

    if (widthMode == MeasureSpec.EXACTLY) {
        viewWidth = widthSize

    }
}

Need the viewWidth not to be have the view width value.

Upvotes: 2

Views: 717

Answers (1)

guipivoto
guipivoto

Reputation: 18677

I guess the problem is happening because you are reading viewWidth before calling View.invalidate(). So, when you read the viewWidth, it still has the old value.

So, I suggest following changes:

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

    oneCellWidth = viewWidth / totalDays.size // Add this
    val pointY = 15.px.toFloat()
    var pointX = oneCellWidth.toFloat() / 2f

    totalDays.forEach { day ->
        val isWorkedDay = workedDays.filter { it.date == day.date }.size
        if (isWorkedDay > 0) {
            canvas?.drawCircle(pointX, pointY, 8f, circlePaint)
        }
        pointX += oneCellWidth
    }
}

fun submitData(totalDays: List<Day>, workedDays: List<WorkedDateAndTime>, color: Int) {
    this.totalDays = totalDays
    this.workedDays = workedDays
    circlePaint.color = color
    // oneCellWidth = viewWidth / totalDays.size --> Remove this
    requestLayout() // Add this. invalidate only request re-draw. requestLayout will request to re-measure
    invalidate()
}

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)

    val widthMode = MeasureSpec.getMode(widthMeasureSpec)
    val widthSize = MeasureSpec.getSize(widthMeasureSpec)

    if (widthMode == MeasureSpec.EXACTLY) {
        viewWidth = widthSize
    }
}

This way, you avoid the issue of reading viewWidth before onMeasure is re-executed. After those changes, you read viewWidth during onDraw which is always executed after onMeasure (if you call requestLayout() of course.

Upvotes: 2

Related Questions