@@ -28,41 +28,69 @@ import android.view.PointerIcon
2828import android.view.View
2929import androidx.annotation.DrawableRes
3030import androidx.appcompat.widget.TooltipCompat
31+ import androidx.dynamicanimation.animation.DynamicAnimation
3132import androidx.vectordrawable.graphics.drawable.Animatable2Compat
3233import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
3334import com.github.shadowsocks.R
3435import com.github.shadowsocks.bg.BaseService
3536import com.google.android.material.floatingactionbutton.FloatingActionButton
37+ import com.google.android.material.progressindicator.BaseProgressIndicator
38+ import com.google.android.material.progressindicator.DeterminateDrawable
3639import java.util.*
3740
3841class ServiceButton @JvmOverloads constructor(context : Context , attrs : AttributeSet ? = null , defStyleAttr : Int = 0 ) :
39- FloatingActionButton (context, attrs, defStyleAttr) {
42+ FloatingActionButton (context, attrs, defStyleAttr), DynamicAnimation .OnAnimationEndListener {
43+ companion object {
44+ private val springAnimator by lazy {
45+ DeterminateDrawable ::class .java.getDeclaredField(" springAnimator" ).apply { isAccessible = true }
46+ }
47+ }
48+
4049 private val callback = object : Animatable2Compat .AnimationCallback () {
4150 override fun onAnimationEnd (drawable : Drawable ) {
4251 super .onAnimationEnd(drawable)
4352 var next = animationQueue.peek() ? : return
44- if (next.current == drawable) {
53+ if (next.icon. current == drawable) {
4554 animationQueue.pop()
4655 next = animationQueue.peek() ? : return
4756 }
48- setImageDrawable(next)
4957 next.start()
5058 }
5159 }
5260
53- private fun createIcon (@DrawableRes resId : Int ): AnimatedVectorDrawableCompat {
54- val result = AnimatedVectorDrawableCompat .create(context, resId)!!
55- result.registerAnimationCallback(callback)
56- return result
61+ private inner class AnimatedState (@DrawableRes resId : Int ,
62+ private val onStart : BaseProgressIndicator <* >.() -> Unit = {
63+ hide()
64+ isIndeterminate = true
65+ show()
66+ }) {
67+ val icon: AnimatedVectorDrawableCompat = AnimatedVectorDrawableCompat .create(context, resId)!! .apply {
68+ registerAnimationCallback(this @ServiceButton.callback)
69+ }
70+ fun start () {
71+ setImageDrawable(icon)
72+ icon.start()
73+ progress.onStart()
74+ }
75+ fun stop () = icon.stop()
5776 }
5877
59- private val iconStopped by lazy { createIcon (R .drawable.ic_service_stopped) }
60- private val iconConnecting by lazy { createIcon (R .drawable.ic_service_connecting) }
61- private val iconConnected by lazy { createIcon (R .drawable.ic_service_connected) }
62- private val iconStopping by lazy { createIcon (R .drawable.ic_service_stopping) }
63- private val animationQueue = ArrayDeque <AnimatedVectorDrawableCompat >()
78+ private val iconStopped by lazy { AnimatedState (R .drawable.ic_service_stopped) { hide() } }
79+ private val iconConnecting by lazy { AnimatedState (R .drawable.ic_service_connecting) }
80+ private val iconConnected by lazy { AnimatedState (R .drawable.ic_service_connected) { setProgressCompat( 1 , true ) } }
81+ private val iconStopping by lazy { AnimatedState (R .drawable.ic_service_stopping) }
82+ private val animationQueue = ArrayDeque <AnimatedState >()
6483
6584 private var checked = false
85+ private lateinit var progress: BaseProgressIndicator <* >
86+ fun initProgress (progress : BaseProgressIndicator <* >) {
87+ this .progress = progress
88+ (springAnimator.get(progress.progressDrawable) as DynamicAnimation <* >).addEndListener(this )
89+ }
90+ override fun onAnimationEnd (animation : DynamicAnimation <out DynamicAnimation <* >>? , canceled : Boolean , value : Float ,
91+ velocity : Float ) {
92+ if (! canceled) progress.hide()
93+ }
6694
6795 override fun onCreateDrawableState (extraSpace : Int ): IntArray {
6896 val drawableState = super .onCreateDrawableState(extraSpace + 1 )
@@ -90,24 +118,20 @@ class ServiceButton @JvmOverloads constructor(context: Context, attrs: Attribute
90118 if (enabled) PointerIcon .TYPE_HAND else PointerIcon .TYPE_WAIT )
91119 }
92120
93- private fun changeState (icon : AnimatedVectorDrawableCompat , animate : Boolean ) {
94- fun counters (a : AnimatedVectorDrawableCompat , b : AnimatedVectorDrawableCompat ): Boolean =
121+ private fun changeState (icon : AnimatedState , animate : Boolean ) {
122+ fun counters (a : AnimatedState , b : AnimatedState ): Boolean =
95123 a == iconStopped && b == iconConnecting ||
96124 a == iconConnecting && b == iconStopped ||
97125 a == iconConnected && b == iconStopping ||
98126 a == iconStopping && b == iconConnected
99127 if (animate) {
100128 if (animationQueue.size < 2 || ! counters(animationQueue.last, icon)) {
101129 animationQueue.add(icon)
102- if (animationQueue.size == 1 ) {
103- setImageDrawable(icon)
104- icon.start()
105- }
130+ if (animationQueue.size == 1 ) icon.start()
106131 } else animationQueue.removeLast()
107132 } else {
108133 animationQueue.peekFirst()?.stop()
109134 animationQueue.clear()
110- setImageDrawable(icon)
111135 icon.start() // force ensureAnimatorSet to be called so that stop() will work
112136 icon.stop()
113137 }
0 commit comments