Last active
August 5, 2022 03:10
-
-
Save NathanWalker/5309671b8d80a10ea88b5da9730e3476 to your computer and use it in GitHub Desktop.
Shimmer ported to Kotlin
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 io.nstudio.ui | |
import android.animation.ValueAnimator | |
import android.animation.ValueAnimator.AnimatorUpdateListener | |
import android.annotation.TargetApi | |
import android.content.Context | |
import android.content.res.TypedArray | |
import android.graphics.Color | |
import android.graphics.RectF | |
import android.graphics.Canvas | |
import android.graphics.Paint | |
import android.graphics.ColorFilter | |
import android.graphics.LinearGradient | |
import android.graphics.Matrix | |
import android.graphics.PixelFormat | |
import android.graphics.PorterDuff | |
import android.graphics.PorterDuffXfermode | |
import android.graphics.RadialGradient | |
import android.graphics.Rect | |
import android.graphics.Shader | |
import android.graphics.drawable.Drawable | |
import android.os.Build | |
import android.util.AttributeSet | |
import android.view.View | |
import android.widget.FrameLayout | |
import android.view.animation.LinearInterpolator | |
import androidx.annotation.ColorInt | |
import androidx.annotation.FloatRange | |
import androidx.annotation.NonNull | |
import androidx.annotation.Nullable | |
class Shimmer internal constructor() { | |
/** | |
* The shape of the shimmer's highlight. By default LINEAR is used. | |
*/ | |
enum class Shape(private val value: Int) { | |
/** | |
* Linear gives a ray reflection effect. | |
*/ | |
LINEAR(0), | |
/** | |
* Radial gives a spotlight effect. | |
*/ | |
RADIAL(1); | |
} | |
/** | |
* Direction of the shimmer's sweep. | |
*/ | |
enum class Direction(private val value: Int) { | |
LEFT_TO_RIGHT(0), TOP_TO_BOTTOM(1), RIGHT_TO_LEFT(2), BOTTOM_TO_TOP(3); | |
} | |
@JvmField | |
val positions = FloatArray(COMPONENT_COUNT) | |
@JvmField | |
val colors = IntArray(COMPONENT_COUNT) | |
val bounds: RectF = RectF() | |
@JvmField | |
var direction = Direction.LEFT_TO_RIGHT | |
@ColorInt | |
var highlightColor: Int = Color.WHITE | |
@ColorInt | |
var baseColor = 0x4cffffff | |
@JvmField | |
var shape = Shape.LINEAR | |
var fixedWidth = 0 | |
var fixedHeight = 0 | |
var widthRatio = 1f | |
var heightRatio = 1f | |
var intensity = 0f | |
var dropoff = 0.5f | |
@JvmField | |
var tilt = 20f | |
@JvmField | |
var clipToChildren = true | |
@JvmField | |
var autoStart = false | |
@JvmField | |
var alphaShimmer = true | |
@JvmField | |
var repeatCount: Int = ValueAnimator.INFINITE | |
@JvmField | |
var repeatMode: Int = ValueAnimator.RESTART | |
@JvmField | |
var animationDuration = 1100L | |
@JvmField | |
var repeatDelay: Long = 0 | |
@JvmField | |
var startDelay: Long = 0 | |
fun width(width: Int): Int { | |
return if (fixedWidth > 0) fixedWidth else Math.round(widthRatio * width) | |
} | |
fun height(height: Int): Int { | |
return if (fixedHeight > 0) fixedHeight else Math.round(heightRatio * height) | |
} | |
fun updateColors() { | |
colors[0] = baseColor | |
colors[1] = highlightColor | |
colors[2] = highlightColor | |
colors[3] = baseColor | |
} | |
fun updatePositions() { | |
positions[0] = Math.max((1f - intensity - dropoff) / 2f, 0f) | |
positions[1] = Math.max((1f - intensity - 0.001f) / 2f, 0f) | |
positions[2] = Math.min((1f + intensity + 0.001f) / 2f, 1f) | |
positions[3] = Math.min((1f + intensity + dropoff) / 2f, 1f) | |
} | |
fun updateBounds(viewWidth: Int, viewHeight: Int) { | |
val magnitude: Int = Math.max(viewWidth, viewHeight) | |
val rad: Double = Math.PI / 2f - Math.toRadians(tilt % 90f) | |
val hyp: Float = magnitude / Math.sin(rad) | |
val padding: Float = 3 * Math.round((hyp - magnitude).toFloat() / 2f) | |
bounds.set(-padding, -padding, width(viewWidth) + padding, height(viewHeight) + padding) | |
} | |
abstract class Builder<T : Builder<T>?> { | |
val mShimmer = Shimmer() | |
// Gets around unchecked cast | |
protected abstract val `this`: T | |
/** | |
* Copies the configuration of an already built Shimmer to this builder | |
*/ | |
fun copyFrom(other: Shimmer): T { | |
setDirection(other.direction) | |
setShape(other.shape) | |
setFixedWidth(other.fixedWidth) | |
setFixedHeight(other.fixedHeight) | |
setWidthRatio(other.widthRatio) | |
setHeightRatio(other.heightRatio) | |
setIntensity(other.intensity) | |
setDropoff(other.dropoff) | |
setTilt(other.tilt) | |
setClipToChildren(other.clipToChildren) | |
setAutoStart(other.autoStart) | |
setRepeatCount(other.repeatCount) | |
setRepeatMode(other.repeatMode) | |
setRepeatDelay(other.repeatDelay) | |
setStartDelay(other.startDelay) | |
setDuration(other.animationDuration) | |
setBaseColor(other.baseColor) | |
setHighLightColor(other.highlightColor) | |
return `this` | |
} | |
open fun setBaseColor(@ColorInt color: Int): T { | |
mShimmer.baseColor = color | |
return `this` | |
} | |
fun setHighLightColor(@ColorInt color: Int): T { | |
mShimmer.highlightColor = color | |
return `this` | |
} | |
/** | |
* Sets the direction of the shimmer's sweep. See [Direction]. | |
*/ | |
fun setDirection(direction: Int): T { | |
mShimmer.direction = direction | |
return `this` | |
} | |
/** | |
* Sets the fixed width of the shimmer, in pixels. | |
*/ | |
fun setFixedWidth(fixedWidth: Int): T { | |
if (fixedWidth < 0) { | |
throw IllegalArgumentException("Given invalid width: $fixedWidth") | |
} | |
mShimmer.fixedWidth = fixedWidth | |
return `this` | |
} | |
/** | |
* Sets the fixed height of the shimmer, in pixels. | |
*/ | |
fun setFixedHeight(fixedHeight: Int): T { | |
if (fixedHeight < 0) { | |
throw IllegalArgumentException("Given invalid height: $fixedHeight") | |
} | |
mShimmer.fixedHeight = fixedHeight | |
return `this` | |
} | |
/** | |
* Sets the width ratio of the shimmer, multiplied against the total width of the layout. | |
*/ | |
fun setWidthRatio(widthRatio: Float): T { | |
if (widthRatio < 0f) { | |
throw IllegalArgumentException("Given invalid width ratio: $widthRatio") | |
} | |
mShimmer.widthRatio = widthRatio | |
return `this` | |
} | |
/** | |
* Sets the height ratio of the shimmer, multiplied against the total height of the layout. | |
*/ | |
fun setHeightRatio(heightRatio: Float): T { | |
if (heightRatio < 0f) { | |
throw IllegalArgumentException("Given invalid height ratio: $heightRatio") | |
} | |
mShimmer.heightRatio = heightRatio | |
return `this` | |
} | |
/** | |
* Sets the intensity of the shimmer. A larger value causes the shimmer to be larger. | |
*/ | |
fun setIntensity(intensity: Float): T { | |
if (intensity < 0f) { | |
throw IllegalArgumentException("Given invalid intensity value: $intensity") | |
} | |
mShimmer.intensity = intensity | |
return `this` | |
} | |
/** | |
* Sets how quickly the shimmer's gradient drops-off. A larger value causes a sharper drop-off. | |
*/ | |
fun setDropoff(dropoff: Float): T { | |
if (dropoff < 0f) { | |
throw IllegalArgumentException("Given invalid dropoff value: $dropoff") | |
} | |
mShimmer.dropoff = dropoff | |
return `this` | |
} | |
/** | |
* Sets the tilt angle of the shimmer in degrees. | |
*/ | |
fun setTilt(tilt: Float): T { | |
mShimmer.tilt = tilt | |
return `this` | |
} | |
/** | |
* Sets the base alpha, which is the alpha of the underlying children, amount in the range [0, | |
* 1]. | |
*/ | |
fun setBaseAlpha(@FloatRange(from = 0, to = 1) alpha: Float): T { | |
val intAlpha = (clamp(0f, 1f, alpha) * 255f).toInt() | |
mShimmer.baseColor = intAlpha shl 24 or (mShimmer.baseColor and 0x00FFFFFF) | |
return `this` | |
} | |
/** | |
* Sets the shimmer alpha amount in the range [0, 1]. | |
*/ | |
fun setHighlightAlpha(@FloatRange(from = 0, to = 1) alpha: Float): T { | |
val intAlpha = (clamp(0f, 1f, alpha) * 255f).toInt() | |
mShimmer.highlightColor = intAlpha shl 24 or (mShimmer.highlightColor and 0x00FFFFFF) | |
return `this` | |
} | |
/** | |
* Sets whether the shimmer will clip to the childrens' contents, or if it will opaquely draw on | |
* top of the children. | |
*/ | |
fun setClipToChildren(status: Boolean): T { | |
mShimmer.clipToChildren = status | |
return `this` | |
} | |
/** | |
* Sets whether the shimmering animation will start automatically. | |
*/ | |
fun setAutoStart(status: Boolean): T { | |
mShimmer.autoStart = status | |
return `this` | |
} | |
/** | |
* Sets how often the shimmering animation will repeat. See [ ][android.animation.ValueAnimator.setRepeatCount]. | |
*/ | |
fun setRepeatCount(repeatCount: Int): T { | |
mShimmer.repeatCount = repeatCount | |
return `this` | |
} | |
/** | |
* Sets how the shimmering animation will repeat. See [ ][android.animation.ValueAnimator.setRepeatMode]. | |
*/ | |
fun setRepeatMode(mode: Int): T { | |
mShimmer.repeatMode = mode | |
return `this` | |
} | |
/** | |
* Sets how long to wait in between repeats of the shimmering animation. | |
*/ | |
fun setRepeatDelay(millis: Long): T { | |
if (millis < 0) { | |
throw IllegalArgumentException("Given a negative repeat delay: $millis") | |
} | |
mShimmer.repeatDelay = millis | |
return `this` | |
} | |
/** | |
* Sets how long to wait for starting the shimmering animation. | |
*/ | |
fun setStartDelay(millis: Long): T { | |
if (millis < 0) { | |
throw IllegalArgumentException("Given a negative start delay: $millis") | |
} | |
mShimmer.startDelay = millis | |
return `this` | |
} | |
/** | |
* Sets how long the shimmering animation takes to do one full sweep. | |
*/ | |
fun setDuration(millis: Long): T { | |
if (millis < 0) { | |
throw IllegalArgumentException("Given a negative duration: $millis") | |
} | |
mShimmer.animationDuration = millis | |
return `this` | |
} | |
fun build(): Shimmer { | |
mShimmer.updateColors() | |
mShimmer.updatePositions() | |
return mShimmer | |
} | |
companion object { | |
private fun clamp(min: Float, max: Float, value: Float): Float { | |
return Math.min(max, Math.max(min, value)) | |
} | |
} | |
} | |
class AlphaHighlightBuilder : Builder<AlphaHighlightBuilder>() { | |
@get:Override | |
override val `this`: T | |
protected get() = this | |
init { | |
mShimmer.alphaShimmer = true | |
} | |
} | |
class ColorHighlightBuilder : Builder<ColorHighlightBuilder>() { | |
/** | |
* Sets the highlight color for the shimmer. | |
*/ | |
fun setHighlightColor(@ColorInt color: Int): ColorHighlightBuilder { | |
mShimmer.highlightColor = color | |
return `this` | |
} | |
/** | |
* Sets the base color for the shimmer. | |
*/ | |
override fun setBaseColor(@ColorInt color: Int): ColorHighlightBuilder { | |
mShimmer.baseColor = mShimmer.baseColor and -0x1000000 or (color and 0x00FFFFFF) | |
return `this` | |
} | |
@get:Override | |
override val `this`: T | |
protected get() = this | |
init { | |
mShimmer.alphaShimmer = false | |
} | |
} | |
companion object { | |
private const val COMPONENT_COUNT = 4 | |
} | |
} | |
class ShimmerDrawable : Drawable() { | |
private val mUpdateListener: ValueAnimator.AnimatorUpdateListener = | |
object : AnimatorUpdateListener() { | |
@Override | |
override fun onAnimationUpdate(animation: ValueAnimator?) { | |
invalidateSelf() | |
} | |
} | |
private val mShimmerPaint: Paint = Paint() | |
private val mDrawRect: Rect = Rect() | |
private val mShaderMatrix: Matrix = Matrix() | |
@Nullable | |
private var mValueAnimator: ValueAnimator? = null | |
@Nullable | |
private var mShimmer: Shimmer? = null | |
@get:Nullable | |
var shimmer: Shimmer? | |
get() = mShimmer | |
set(shimmer) { | |
mShimmer = shimmer | |
if (mShimmer != null) { | |
mShimmerPaint.setXfermode( | |
PorterDuffXfermode( | |
if (mShimmer!!.alphaShimmer) PorterDuff.Mode.DST_IN else PorterDuff.Mode.SRC_IN | |
) | |
) | |
} | |
updateShader() | |
updateValueAnimator() | |
invalidateSelf() | |
} | |
/** | |
* Starts the shimmer animation. | |
*/ | |
fun startShimmer() { | |
if (mValueAnimator != null && !isShimmerStarted && getCallback() != null) { | |
mValueAnimator!!.start() | |
} | |
} | |
/** | |
* Stops the shimmer animation. | |
*/ | |
fun stopShimmer() { | |
if (mValueAnimator != null && isShimmerStarted) { | |
mValueAnimator!!.cancel() | |
} | |
} | |
/** | |
* Return whether the shimmer animation has been started. | |
*/ | |
val isShimmerStarted: Boolean | |
get() = mValueAnimator != null && mValueAnimator!!.isStarted() | |
@Override | |
override fun onBoundsChange(bounds: Rect?) { | |
super.onBoundsChange(bounds) | |
mDrawRect.set(bounds) | |
updateShader() | |
maybeStartShimmer() | |
} | |
@Override | |
override fun draw(@NonNull canvas: Canvas) { | |
if (mShimmer == null || mShimmerPaint.getShader() == null) { | |
return | |
} | |
val tiltTan = Math.tan(Math.toRadians(mShimmer!!.tilt)) as Float | |
val translateHeight: Float = mDrawRect.height() + tiltTan * mDrawRect.width() | |
val translateWidth: Float = mDrawRect.width() + tiltTan * mDrawRect.height() | |
val dx: Float | |
val dy: Float | |
val animatedValue = if (mValueAnimator != null) mValueAnimator!!.getAnimatedValue() else 0f | |
dx = offset(-translateWidth, translateWidth, animatedValue) | |
dy = 0f | |
mShaderMatrix.reset() | |
mShaderMatrix.setRotate(mShimmer!!.tilt, mDrawRect.width() / 2f, mDrawRect.height() / 2f) | |
mShaderMatrix.postTranslate(dx, dy) | |
mShimmerPaint.getShader().setLocalMatrix(mShaderMatrix) | |
canvas.drawRect(mDrawRect, mShimmerPaint) | |
} | |
@Override | |
override fun setAlpha(alpha: Int) { | |
// No-op, modify the Shimmer object you pass in instead | |
} | |
@Override | |
override fun setColorFilter(@Nullable colorFilter: ColorFilter?) { | |
// No-op, modify the Shimmer object you pass in instead | |
} | |
@Override | |
override fun getOpacity(): Int { | |
if (mShimmer != null && (mShimmer!!.clipToChildren || mShimmer!!.alphaShimmer)) { | |
return PixelFormat.TRANSLUCENT | |
} else { | |
return PixelFormat.OPAQUE | |
} | |
} | |
//@get:Override | |
val opacity: Int | |
get() = if (mShimmer != null && (mShimmer!!.clipToChildren || mShimmer!!.alphaShimmer)) PixelFormat.TRANSLUCENT else PixelFormat.OPAQUE | |
private fun offset(start: Float, end: Float, percent: Float): Float { | |
return start + (end - start) * percent | |
} | |
private fun updateValueAnimator() { | |
if (mShimmer == null) { | |
return | |
} | |
val started: Boolean | |
if (mValueAnimator != null) { | |
started = mValueAnimator!!.isStarted() | |
mValueAnimator!!.cancel() | |
mValueAnimator!!.removeAllUpdateListeners() | |
} else { | |
started = false | |
} | |
mValueAnimator = ValueAnimator.ofFloat( | |
0f, | |
1f + (mShimmer!!.repeatDelay / mShimmer!!.animationDuration).toFloat() | |
) | |
mValueAnimator!!.setInterpolator(LinearInterpolator()) | |
mValueAnimator!!.setRepeatMode(mShimmer!!.repeatMode) | |
mValueAnimator!!.setStartDelay(mShimmer!!.startDelay) | |
mValueAnimator!!.setRepeatCount(mShimmer!!.repeatCount) | |
mValueAnimator!!.setDuration(mShimmer!!.animationDuration + mShimmer!!.repeatDelay) | |
mValueAnimator!!.addUpdateListener(mUpdateListener) | |
if (started) { | |
mValueAnimator!!.start() | |
} | |
} | |
fun maybeStartShimmer() { | |
if (mValueAnimator != null && !mValueAnimator!!.isStarted() | |
&& mShimmer != null && mShimmer!!.autoStart | |
&& getCallback() != null | |
) { | |
mValueAnimator!!.start() | |
} | |
} | |
private fun updateShader() { | |
val bounds: Rect = getBounds() | |
val boundsWidth: Int = bounds.width() | |
val boundsHeight: Int = bounds.height() | |
if (boundsWidth == 0 || boundsHeight == 0 || mShimmer == null) { | |
return | |
} | |
val width = mShimmer!!.width(boundsWidth) | |
val height = mShimmer!!.height(boundsHeight) | |
val shader: Shader | |
val vertical = false | |
val endX = if (vertical) 0 else width | |
val endY = if (vertical) height else 0 | |
shader = LinearGradient( | |
0, 0, endX, endY, mShimmer!!.colors, mShimmer!!.positions, Shader.TileMode.CLAMP | |
) | |
mShimmerPaint.setShader(shader) | |
} | |
init { | |
mShimmerPaint.setAntiAlias(true) | |
} | |
} | |
class ShimmerView : FrameLayout { | |
private val mContentPaint: Paint = Paint() | |
private val mShimmerDrawable: ShimmerDrawable? = ShimmerDrawable() | |
/** | |
* Return whether the shimmer drawable is visible. | |
*/ | |
var isShimmerVisible = true | |
private var mStoppedShimmerBecauseVisibility = false | |
constructor(context: Context) : super(context) { | |
init(context, null) | |
} | |
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { | |
init(context, attrs) | |
} | |
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( | |
context, | |
attrs, | |
defStyleAttr | |
) { | |
init(context, attrs) | |
} | |
@TargetApi(Build.VERSION_CODES.LOLLIPOP) | |
constructor( | |
context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int | |
) : super(context, attrs, defStyleAttr, defStyleRes) { | |
init(context, attrs) | |
} | |
private fun init(context: Context, @Nullable attrs: AttributeSet?) { | |
setWillNotDraw(false) | |
mShimmerDrawable.setCallback(this) | |
if (attrs == null) { | |
setShimmer(Shimmer.AlphaHighlightBuilder().build()) | |
return | |
} | |
} | |
fun setShimmer(@Nullable shimmer: Shimmer?): ShimmerView { | |
mShimmerDrawable!!.shimmer = shimmer | |
if (shimmer != null && shimmer.clipToChildren) { | |
setLayerType(LAYER_TYPE_HARDWARE, mContentPaint) | |
} else { | |
setLayerType(LAYER_TYPE_NONE, null) | |
} | |
return this | |
} | |
@get:Nullable | |
val shimmer: Shimmer? | |
get() = mShimmerDrawable!!.shimmer | |
private var mSpeed: Long = 1100 | |
fun setSpeed(speed: Long) { | |
if (speed > 0) { | |
mSpeed = speed | |
} | |
if (shimmer != null) { | |
val builder: Shimmer.Builder<*> = Shimmer.AlphaHighlightBuilder() | |
builder.copyFrom(shimmer!!) | |
builder.setDuration(speed) | |
setShimmer(builder.build()) | |
} | |
} | |
fun setLightColor(@ColorInt color: Int) { | |
if (shimmer != null) { | |
val builder: Shimmer.Builder<*> = Shimmer.AlphaHighlightBuilder() | |
builder.copyFrom(shimmer!!) | |
builder.setHighLightColor(color) | |
setShimmer(builder.build()) | |
} | |
} | |
fun setDarkColor(@ColorInt color: Int) { | |
if (shimmer != null) { | |
val builder: Shimmer.Builder<*> = Shimmer.AlphaHighlightBuilder() | |
builder.copyFrom(shimmer!!) | |
builder.setBaseColor(color) | |
setShimmer(builder.build()) | |
} | |
} | |
fun start( | |
speed: Long, | |
direction: Int, | |
repeatCount: Int, | |
@ColorInt lightColor: Int, | |
@ColorInt blackColor: Int | |
) { | |
if (shimmer != null) { | |
val builder: Shimmer.Builder<*> = Shimmer.AlphaHighlightBuilder() | |
builder.copyFrom(shimmer!!) | |
builder.setDuration(speed) | |
when (direction) { | |
0 -> builder.setDirection(0) | |
else -> {} | |
} | |
builder.setRepeatCount(repeatCount) | |
builder.setHighLightColor(lightColor) | |
builder.setBaseColor(blackColor) | |
setShimmer(builder.build()) | |
} | |
showShimmer(true) | |
} | |
/** | |
* Starts the shimmer animation. | |
*/ | |
fun startShimmer() { | |
mShimmerDrawable!!.startShimmer() | |
} | |
/** | |
* Stops the shimmer animation. | |
*/ | |
fun stopShimmer() { | |
mStoppedShimmerBecauseVisibility = false | |
mShimmerDrawable!!.stopShimmer() | |
} | |
/** | |
* Return whether the shimmer animation has been started. | |
*/ | |
val isShimmerStarted: Boolean | |
get() = mShimmerDrawable!!.isShimmerStarted | |
/** | |
* Sets the ShimmerDrawable to be visible. | |
* | |
* @param startShimmer Whether to start the shimmer again. | |
*/ | |
fun showShimmer(startShimmer: Boolean) { | |
isShimmerVisible = true | |
if (startShimmer) { | |
startShimmer() | |
} | |
invalidate() | |
} | |
/** | |
* Sets the ShimmerDrawable to be invisible, stopping it in the process. | |
*/ | |
fun hideShimmer() { | |
stopShimmer() | |
isShimmerVisible = false | |
invalidate() | |
} | |
@Override | |
fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { | |
super.onLayout(changed, left, top, right, bottom) | |
val width: Int = getWidth() | |
val height: Int = getHeight() | |
mShimmerDrawable.setBounds(0, 0, width, height) | |
} | |
@Override | |
protected fun onVisibilityChanged(@NonNull changedView: View?, visibility: Int) { | |
super.onVisibilityChanged(changedView, visibility) | |
// View's constructor directly invokes this method, in which case no fields on | |
// this class have been fully initialized yet. | |
if (mShimmerDrawable == null) { | |
return | |
} | |
if (visibility != VISIBLE) { | |
// GONE or INVISIBLE | |
if (isShimmerStarted) { | |
stopShimmer() | |
mStoppedShimmerBecauseVisibility = true | |
} | |
} else if (mStoppedShimmerBecauseVisibility) { | |
mShimmerDrawable.maybeStartShimmer() | |
mStoppedShimmerBecauseVisibility = false | |
} | |
} | |
@Override | |
override fun onAttachedToWindow() { | |
super.onAttachedToWindow() | |
mShimmerDrawable!!.maybeStartShimmer() | |
} | |
@Override | |
override fun onDetachedFromWindow() { | |
super.onDetachedFromWindow() | |
stopShimmer() | |
} | |
@Override | |
override fun dispatchDraw(canvas: Canvas?) { | |
super.dispatchDraw(canvas) | |
if (isShimmerVisible) { | |
mShimmerDrawable!!.draw(canvas) | |
} | |
} | |
@Override | |
protected override fun verifyDrawable(@NonNull who: Drawable): Boolean { | |
return super.verifyDrawable(who) || who === mShimmerDrawable | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment