Created
December 15, 2019 05:08
-
-
Save CapnSpellcheck/a787f161523bfde174c424fbac4b74f9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.levelfillingvectordrawable | |
import android.content.res.Resources | |
import android.graphics.* | |
import android.graphics.drawable.DrawableWrapper | |
import android.graphics.drawable.VectorDrawable | |
import androidx.annotation.DrawableRes | |
import androidx.core.graphics.withSave | |
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 | |
*/ | |
@Suppress("MemberVisibilityCanBePrivate") | |
var fillAlphaMultipler = 1f | |
@Suppress("MemberVisibilityCanBePrivate") | |
var fillOnTop: Boolean = false | |
@Suppress("MemberVisibilityCanBePrivate") | |
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 | |
updateTint() | |
} | |
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 {it.name == fillablePathname}?.path | |
} ?: vectorModel.fullpath | |
fillablePath.transform(Matrix().apply {setScale(density, density)}, transformedPath) | |
reflTintFilterField = VectorDrawable::class.java.getDeclaredField("mTintFilter") | |
reflTintFilterField.isAccessible = true | |
} | |
override fun onLevelChange(level: Int): Boolean { | |
super.onLevelChange(level) | |
updateFillRect() | |
updateTint() | |
return true | |
} | |
override fun onBoundsChange(bounds: Rect) { | |
super.onBoundsChange(bounds) | |
updateFillRect() | |
} | |
private fun updateTint() { | |
val tint = levelTints.lastOrNull { (minLevel, _) -> | |
minLevel <= this.level | |
}?.second | |
tint?.let { | |
this.setTint(tint) | |
} | |
} | |
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.top - bounds.bottom) // move up from bottom | |
fillRect.set(bounds.left + 0f, fillTop, bounds.right + 0f, bounds.bottom + 0f) | |
invalidateSelf() | |
} | |
// Draw: draw the parent. Based on current level, add a fill clipped by the fillable Path | |
override fun draw(canvas: Canvas) { | |
if (fillOnTop) { | |
super.draw(canvas) | |
} | |
val tintFilter = reflTintFilterField.get(this.drawable) | |
val clearFilter = | |
if (tintFilter != null && tintFilter is ColorFilter && fillPaint.colorFilter == null) { | |
fillPaint.colorFilter = tintFilter | |
true | |
} | |
else false | |
canvas.withSave { | |
fillPaint.alpha = (fillAlphaMultipler * this@LevelFillingVectorDrawable.alpha).toInt() | |
canvas.clipPath(transformedPath) | |
canvas.drawRect(fillRect, fillPaint) | |
} | |
if (clearFilter) | |
fillPaint.colorFilter = null | |
if (!fillOnTop) { | |
super.draw(canvas) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment