Skip to content

Instantly share code, notes, and snippets.

@grandstaish
Last active May 22, 2024 09:54
Show Gist options
  • Save grandstaish/18b256b637c2aa0d85aa7e41e3cab9da to your computer and use it in GitHub Desktop.
Save grandstaish/18b256b637c2aa0d85aa7e41e3cab9da to your computer and use it in GitHub Desktop.
A raindrop shaped view
internal class RaindropView(context: Context) : View(context) {
private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.FILL
color = Color.WHITE
}
private val gradientPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.FILL
color = Color.WHITE
}
private val tmpMatrix = Matrix()
private val backgroundBounds = RectF()
private val insetBounds = RectF()
private val raindropPath = Path().apply {
// M 13.5 40
// C 20.956 40 27 34.03 27 26.667
// C 27 19.303 19.606 0 13.5 0
// C 7.394 0 0 19.303 0 26.667
// C 0 34.03 6.044 40 13.5 40
// Z
moveTo(13.5f, 40f)
cubicTo(20.956f, 40f, 27f, 34.03f, 27f, 26.667f)
cubicTo(27f, 19.303f, 19.606f, 0f, 13.5f, 0f)
cubicTo(7.394f, 0f, 0f, 19.303f, 0f, 26.667f)
cubicTo(0f, 34.03f, 6.044f, 40f, 13.5f, 40f)
close()
}
var colors: IntArray? = null
set(value) {
field = value
gradientPaint.updateColors(colors, width)
invalidate()
}
init {
elevation = 4.dp.toFloat()
outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
outline.setConvexPath(raindropPath)
}
}
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
// Update the fill paint because the width has changed
gradientPaint.updateColors(colors, width)
// Set the bounds for the white background and the gradient middle.
backgroundBounds.set(0f, 0f, w.toFloat(), h.toFloat())
insetBounds.apply {
set(backgroundBounds)
inset(2.dp.toFloat(), 2.dp.toFloat())
}
// Transform the raindrop path so that it fills the view.
val originalPathBounds = RectF(0f, 0f, 27f, 40f)
tmpMatrix.setRectToRect(originalPathBounds, backgroundBounds, Matrix.ScaleToFit.FILL)
raindropPath.transform(tmpMatrix)
invalidateOutline()
invalidate()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Draw the white background
// No need to scale because the path fills the entire view already!
canvas.drawPath(raindropPath, backgroundPaint)
// Draw the colorful fill in the middle.
// Use a matrix to zoom-out a bit on the canvas before drawing to inset the gradient from the view edges.
tmpMatrix.setRectToRect(backgroundBounds, insetBounds, Matrix.ScaleToFit.FILL)
canvas.withMatrix(tmpMatrix) { drawPath(raindropPath, gradientPaint) }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment