Skip to content

Instantly share code, notes, and snippets.

Created December 15, 2019 05:08
Show Gist options
  • Save CapnSpellcheck/a787f161523bfde174c424fbac4b74f9 to your computer and use it in GitHub Desktop.
Save CapnSpellcheck/a787f161523bfde174c424fbac4b74f9 to your computer and use it in GitHub Desktop.
package com.levelfillingvectordrawable
import android.content.res.Resources
import androidx.annotation.DrawableRes
import com.sdsmdg.harjot.vectormaster.models.VectorModel
import java.lang.reflect.Field
import kotlin.math.abs
* A Drawable that modifies a vector drawable by drawing a 'fill' controlled by the `level`
* starting at the bottom of the drawable, clipped by a path component of the vector.
* The vector can outlined or filled vector, doesn't really matter, but you may wish to control drawing order.
* You can change the draw order with fillOnTop.
* You can specify a specific path in the vector by name to fill.
* You can also provide a list of tint colors as an INCREASING list of Pairs <min level, color int>
* Theme integration is not supported.
class LevelFillingVectorDrawable(res: Resources,
@DrawableRes id: Int,
fillablePathname: String? = null)
: DrawableWrapper(res.getDrawable(id, null))
* Additional configurations
var fillAlphaMultipler = 1f
var fillOnTop: Boolean = false
val fillPaint: Paint = Paint()
* Don't provide too many tints, tint lookup is currently O(n)
var levelTints: Iterable<Pair<Int, Int>> = emptyList()
set(value) {
field = value
private val vectorModel: VectorModel
private var transformedPath = Path()
private val reflTintFilterField: Field
private val fillRect = RectF()
// Set the maximum level, at which this drawable will be drawn as completely filled.
// Note, the Android maximum level is 10000. You may set it below this if you wish.
var filledLevel = 100
init {
fillPaint.color = Color.BLACK
vectorModel = VectorMaster.buildVectorModel(res.getXml(id), false)
val density = res.displayMetrics.density
val fillablePath = fillablePathname?.let {
vectorModel.pathModels.first { == fillablePathname}?.path
} ?: vectorModel.fullpath
fillablePath.transform(Matrix().apply {setScale(density, density)}, transformedPath)
reflTintFilterField ="mTintFilter")
reflTintFilterField.isAccessible = true
override fun onLevelChange(level: Int): Boolean {
return true
override fun onBoundsChange(bounds: Rect) {
private fun updateTint() {
val tint = levelTints.lastOrNull { (minLevel, _) ->
minLevel <= this.level
tint?.let {
private fun updateFillRect() {
// filledLevel = 0: top<-bottom; filledLevel = 100: no change
val fillRatio: Float = Math.min(this.level, filledLevel)/filledLevel.toFloat()
val fillTop = bounds.bottom - fillRatio*abs( - bounds.bottom) // move up from bottom
fillRect.set(bounds.left + 0f, fillTop, bounds.right + 0f, bounds.bottom + 0f)
// Draw: draw the parent. Based on current level, add a fill clipped by the fillable Path
override fun draw(canvas: Canvas) {
if (fillOnTop) {
val tintFilter = reflTintFilterField.get(this.drawable)
val clearFilter =
if (tintFilter != null && tintFilter is ColorFilter && fillPaint.colorFilter == null) {
fillPaint.colorFilter = tintFilter
else false
canvas.withSave {
fillPaint.alpha = (fillAlphaMultipler * this@LevelFillingVectorDrawable.alpha).toInt()
canvas.drawRect(fillRect, fillPaint)
if (clearFilter)
fillPaint.colorFilter = null
if (!fillOnTop) {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment