Search code examples
androidkotlinviewbottom-sheetandroid-bottomsheetdialog

Custom View is not drawing in BottomSheetDialogFragment, when sometimes onSizeChanged is called after invalidate


I'm developing a custom graph view, displayed in a bottom sheet dialog fragment and it is not drawing, when I add data and call invalidate() before onSizeChanged() is called. This happens some times and I don't know how to solve that.

I add the Data in onViewCreated(...) of my BottomSheetDialogFragment, which is LiveData, so it surely takes some time until the Observer calls setDataPoints() of the ChartView.

I also use the Chart in two normal fragments, and it works fine there.

My custom view:

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

    //region Variables

    private val data = mutableListOf<DateDataPoint>()
    private val points = mutableListOf<PointF>()

    private val path = Path()

    private val pathPaint: Paint

    //endregion

    //region Constructor

    init {
        pathPaint = Paint().apply {
            isAntiAlias = true
            color = Color.WHITE
            strokeWidth = 5.0f
            style = Paint.Style.STROKE
        }

        setWillNotDraw(false)
    }

    //endregion

    //region View

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

        println("MB: onSizeChanged: ${data.size}")
    }

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

        computePath()

        canvas?.drawPath(path, pathPaint)
    }

    //endregion

    //region Methods

    fun setDataPoints(dataPoints: List<DateDataPoint>) = GlobalScope.launch(Dispatchers.Main) {
        val min = dataPoints.minByOrNull { it.value }?.value ?: 0.0f

        val positiveDataPoints: List<DateDataPoint> = List(dataPoints.size) { i -> DateDataPoint(dataPoints[i].date, dataPoints[i].value - min) }

        data.clear()
        data.addAll(positiveDataPoints)

        computePoints()

        postInvalidate()
        println("MB: invalidate: ${data.size}")
    }

    private fun computePoints() {
        points.clear()

        val graphHeight = height.toFloat()

        val maxValue: Float = data.maxByOrNull { it.value }!!.value

        val xStep: Float = width.toFloat() / (data.size - 1)

        for (i in 0 until data.size) {
            val xVal = xStep * i
            val yVal = graphHeight - data[i].value / maxValue * graphHeight

            points.add(PointF(xVal, yVal))
        }
    }

    private fun computePath() {
        if (points.isEmpty()) return

        with(path) {
            reset()

            moveTo(points.first().x, points.first().y)

            for (i in 0 until points.size) {
                lineTo(points[i].x, points[i].y)
            }
        }
    }

    //endregion

    companion object {

    }
}

This is the output:

2021-02-19 18:13:25.210 29400-29400/at.guger.moneybook.dev I/System.out: MB: invalidate: 28
2021-02-19 18:13:30.146 29400-29400/at.guger.moneybook.dev I/System.out: MB: onSizeChanged: 0
2021-02-19 18:13:30.153 29400-29400/at.guger.moneybook.dev I/System.out: MB: invalidate: 28
2021-02-19 18:13:32.424 29400-29400/at.guger.moneybook.dev I/System.out: MB: onSizeChanged: 0
2021-02-19 18:13:32.429 29400-29400/at.guger.moneybook.dev I/System.out: MB: invalidate: 28
2021-02-19 18:13:34.897 29400-29400/at.guger.moneybook.dev I/System.out: MB: onSizeChanged: 0
2021-02-19 18:13:34.900 29400-29400/at.guger.moneybook.dev I/System.out: MB: invalidate: 28
2021-02-19 18:13:37.077 29400-29400/at.guger.moneybook.dev I/System.out: MB: onSizeChanged: 0
2021-02-19 18:13:37.083 29400-29400/at.guger.moneybook.dev I/System.out: MB: invalidate: 28
2021-02-19 18:13:38.689 29400-29400/at.guger.moneybook.dev I/System.out: MB: invalidate: 28  <---- this is the case when it doesn't draw
2021-02-19 18:13:38.697 29400-29400/at.guger.moneybook.dev I/System.out: MB: onSizeChanged: 28 <---- doesn't draw

I already tried calling invalidate() in onSizeChanged(), setting setWillNotDraw(false) or delaying the call of invalidate() with postInvalidateDelayed(100).


Solution

  • I found a workaround for that issue:

    To check whether onSizeChanged was already called, I check for height * width > 0 in my method setDataPoints.

    If this condition is not true, I set a flag to recompute the drawing data on size change.

    In onSizeChanged, I check for that flag and call my setDataPoints method again and this works now.