Skip to content

Instantly share code, notes, and snippets.

@darnmason
Created March 20, 2018 23:44
Show Gist options
  • Save darnmason/e9c33d5c310d4699e742099f5319d27f to your computer and use it in GitHub Desktop.
Save darnmason/e9c33d5c310d4699e742099f5319d27f to your computer and use it in GitHub Desktop.
A custom Android view to render a circular shadow. The radius of the gradient intersects both edges of the view and the startColor stop is the first visible point of the gradient. Customisable depth and gravity via xml.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CircularShadowView">
<attr name="gravity">
<flag name="bottom" value="80" />
<flag name="left" value="3" />
<flag name="right" value="5" />
<flag name="top" value="48" />
</attr>
<attr name="depth" format="dimension" />
<attr name="startColor" format="color" />
<attr name="endColor" format="color" />
</declare-styleable>
</resources>
import android.content.Context
import android.graphics.Color
import android.graphics.RadialGradient
import android.graphics.Shader
import android.graphics.drawable.PaintDrawable
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.RectShape
import android.util.AttributeSet
import android.view.Gravity.*
import android.widget.FrameLayout
class CircularShadowView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : FrameLayout(context, attrs, defStyleAttr) {
private var customDepth: Float? = null
private var endColor = Color.TRANSPARENT
private var startColor = Color.BLACK
private var gravity = BOTTOM
private val shaderFactory = object : ShapeDrawable.ShaderFactory() {
override fun resize(width: Int, height: Int): Shader {
val x: Double
val y: Double
val radius: Double
val depth: Float
when (gravity) {
BOTTOM, TOP -> {
depth = customDepth ?: height.toFloat()
x = width.toDouble() / 2
radius = ((Math.pow(depth.toDouble(), 2.0) + Math.pow(x, 2.0)) / (2 * depth))
y = if (gravity == BOTTOM) height + radius - depth else -radius + depth
}
else -> {
depth = customDepth ?: width.toFloat()
y = height.toDouble() / 2
radius = ((Math.pow(depth.toDouble(), 2.0) + Math.pow(y, 2.0)) / (2 * depth))
x = if (gravity == LEFT) -radius + depth else width + radius - depth
}
}
val start = (radius - depth) / radius
return RadialGradient(x.toFloat(), y.toFloat(), radius.toFloat(),
intArrayOf(startColor, startColor, endColor),
floatArrayOf(0f, start.toFloat(), 1f),
Shader.TileMode.CLAMP)
}
}
init {
if (attrs != null) {
val a = context.obtainStyledAttributes(attrs, R.styleable.CircularShadowView)
if (a.hasValue(R.styleable.CircularShadowView_depth)) {
customDepth = a.getDimension(R.styleable.CircularShadowView_depth, 0f)
}
endColor = a.getColor(R.styleable.CircularShadowView_endColor, endColor)
startColor = a.getColor(R.styleable.CircularShadowView_startColor, startColor)
gravity = a.getInt(R.styleable.CircularShadowView_gravity, gravity)
a.recycle()
}
val drawable = PaintDrawable()
drawable.shape = RectShape()
drawable.shaderFactory = shaderFactory
background = drawable
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment