Skip to content

Instantly share code, notes, and snippets.

@tunjid
Last active February 13, 2020 08:07
Show Gist options
  • Save tunjid/baddc377f5fad819b8c45307f9b2ffd0 to your computer and use it in GitHub Desktop.
Save tunjid/baddc377f5fad819b8c45307f9b2ffd0 to your computer and use it in GitHub Desktop.
class SpeedDialClickListener(
@ColorInt private val tint: Int = Color.BLUE,
private val items: List<Pair<CharSequence?, Drawable>>,
private val runGuard: (View) -> Boolean,
private val dismissListener: (Int?) -> Unit
) : View.OnClickListener {
override fun onClick(button: View?) {
if (button !is MaterialButton || !runGuard(button)) return
val rotationSpring = button.spring(DynamicAnimation.ROTATION)
if (rotationSpring.isRunning) return
val flipRange = 90F..180F
val context = button.context
val colorFrom = context.themeColorAt(R.attr.colorPrimary)
val colorTo = context.themeColorAt(R.attr.colorAccent).run {
Color.argb(20, Color.red(this), Color.green(this), Color.blue(this))
}
button.strokeColor = ColorStateList.valueOf(context.themeColorAt(R.attr.colorAccent))
rotationSpring.apply {
doInRange(flipRange) { button.icon = button.context.drawableAt(R.drawable.ic_unfold_less_24dp) }
animateToFinalPosition(225F) // re-targeting will be idempotent
}
val animators = button.haloEffects(colorFrom, colorTo, context)
speedDial(anchor = button, tint = tint, items = items, dismissListener = dismiss@{ index ->
animators.forEach(ValueAnimator::cancel)
rotationSpring.apply {
if (!isRunning) {
doInRange(flipRange) { button.icon = button.context.drawableAt(R.drawable.ic_unfold_more_24dp) }
withOneShotEndListener { dismissListener(index) }
}
animateToFinalPosition(0F)
}
if (index == null) button.haloEffects(colorFrom, colorTo, context)
})
}
private fun MaterialButton.haloEffects(colorFrom: Int, colorTo: Int, context: Context) = listOf(
roundAbout(colorFrom, colorTo, ArgbEvaluator(), { backgroundTintList!!.defaultColor }) { backgroundTintList = ColorStateList.valueOf(it as Int) },
roundAbout(0, context.resources.getDimensionPixelSize(R.dimen.quarter_margin), IntEvaluator(), this::getStrokeWidth, this::setStrokeWidth)
)
private inline fun <reified T> roundAbout(
originalPosition: T,
nextPosition: T,
evaluator: TypeEvaluator<T>,
crossinline getter: () -> T,
crossinline setter: (T) -> Unit
) = ValueAnimator.ofObject(evaluator, getter(), nextPosition).apply {
duration = 200L
addUpdateListener { setter(it.animatedValue as T) }
doOnEnd {
setObjectValues(getter(), originalPosition)
removeAllListeners()
start()
}
start()
}
private fun SpringAnimation.doInRange(range: ClosedRange<Float>, action: () -> Unit) = apply {
var flipped = false
withOneShotUpdateListener update@{ value, _ ->
if (flipped || !range.contains(value)) return@update
action()
flipped = true
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment