androidandroid-canvasandroid-custom-viewcustom-view

How to draw a path with rounded corners


I should draw a CustomView like this shot.enter image description here

but they are not the same. corner strokes are different.

I use 2 separated Path to draw the top shape: first one for yellow background:

   private val paint = Paint().apply {
        isAntiAlias = false                    // pass true does not make change
        color = Color.YELLOW
        style = Paint.Style.FILL_AND_STROKE    // pass only FILL does not make change
        }

and the second is:

private val strokePaint = Paint().apply {
        isAntiAlias = false                   // pass true does not make change
        color = Color.BLACK
        strokeWidth = 2.toPx().toFloat()
        style = Paint.Style.STROKE
    }

and in onDraw() function I draw by them:

override fun onDraw(canvas: Canvas) {
        drawPath()

        canvas.drawPath(path, paint)
        canvas.drawPath(path, strokePaint)

        // at the end, draw text and default things to avoid overlapping with background
        super.onDraw(canvas)

    }

Update: now I drew this that has 2 sides for its pointer. enter image description here

and used this path to draw:

 private fun drawPath() {
    path.run {
        moveTo(left + radius, top)

        if (_side == SIDE_TOP) {
            lineTo(pointerX - pointerSize / 2, top)
            lineTo(pointerX, rect.top)
            lineTo(pointerX + pointerSize / 2, top)
        }
        lineTo(right - radius, top)

        arcTo(topRightRect, 270F, 90F, false)
        lineTo(right, bottom - radius)
        arcTo(bottomRightRect, 0F, 90F, false)

        if (_side == SIDE_BOTTOM) {
            lineTo(pointerX + pointerSize / 2, bottom)
            lineTo(pointerX, rect.bottom)
            lineTo(pointerX - pointerSize / 2, bottom)
        }
        lineTo(left + radius, bottom)

        arcTo(bottomLeftRect, 90F, 90F, false)
        lineTo(left, top + radius)
        arcTo(topLeftRect, 180F, 90F, false)
        close()
    }
}

Solution

  • Canvas has some pre-defined methods for drawing common shapes like circle and rectangle. In your scenario, you can use drawRoundRect which needs a RectF to draw a rectangle.

    Here is an example:

    enter image description here

    class RoundedRect @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
    ) : View(context, attrs, defStyleAttr) {
    
        private val roundCorner = 32f
    
        private val paint = Paint().apply {
            color = Color.YELLOW
            style = Paint.Style.FILL
            isAntiAlias = true
        }
    
        private val strokePaint = Paint().apply {
            color = Color.BLACK
            strokeWidth = 4f
            style = Paint.Style.STROKE
            isAntiAlias = true
        }
    
        private var rect = RectF(0f, 0f, 0f, 0f)
    
        override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
            super.onSizeChanged(w, h, oldw, oldh)
    
            rect = RectF(0f, 0f, w.toFloat(), h.toFloat())
        }
    
        override fun onDraw(canvas: Canvas) {
            super.onDraw(canvas)
    
            canvas.drawRoundRect(rect, roundCorner, roundCorner, paint)
            canvas.drawRoundRect(rect, roundCorner, roundCorner, strokePaint)
        }
    }
    

    BTW, If you want to draw rounded corners using a path, you must set pathEffect with CornerPathEffect.

    Here is an example: enter image description here

    
    class RoundedRectUsingPath @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
    ) : View(context, attrs, defStyleAttr) {
    
        private val roundCorner = 32f
    
        private val paint = Paint().apply {
            color = Color.YELLOW
            isAntiAlias = true
            pathEffect = CornerPathEffect(roundCorner)
            strokeCap = Paint.Cap.ROUND
        }
    
        private val strokePaint = Paint().apply {
            color = Color.BLACK
            strokeWidth = 4f
            isAntiAlias = true
            style = Paint.Style.STROKE
            pathEffect = CornerPathEffect(roundCorner)
        }
    
        private var path = Path()
        private val offset = 50f
    
        override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
            super.onSizeChanged(w, h, oldw, oldh)
            path = Path().apply {
                moveTo(offset, offset)
                lineTo(w.toFloat() - offset, offset)
                lineTo(w.toFloat() - offset, h.toFloat() - offset)
                lineTo(offset, h.toFloat() - offset)
            }
            path.close()
    
        }
    
        override fun onDraw(canvas: Canvas) {
            super.onDraw(canvas)
    
            canvas.drawPath(path, paint)
            canvas.drawPath(path, strokePaint)
        }
    }