11package com.algolia.instantsearch.voice.ui
22
33import android.animation.AnimatorSet
4+ import android.annotation.SuppressLint
45import android.content.Context
56import android.graphics.Canvas
6- import android.os.Looper
7+ import android.os.Build
78import android.util.AttributeSet
89import android.view.View
9- import kotlinx.coroutines.experimental.*
10- import kotlinx.coroutines.experimental.android.Main
11- import kotlinx.coroutines.experimental.android.UI
12- import kotlinx.coroutines.experimental.android.awaitFrame
1310
1411/* * A View displaying a ripple effect. */
1512class RippleView : View {
13+
1614 enum class State {
1715 None ,
1816 Playing ,
@@ -29,60 +27,69 @@ class RippleView : View {
2927 init (attrs)
3028 }
3129
30+ private val fps = 1000L / 60L
31+
3232 /* * All circles used in this RippleView, generated during [init]. */
3333 private var circles = listOf<DrawableSprite >()
3434 /* * Current animations, filled when [State.Playing] triggers [animation] and emptied when [State.Stopped]. */
3535 private val animations = mutableMapOf<Int , AnimatorSet >()
3636
37+ /* * This runnable invalidate the view at 60 fps. */
38+ private val runnableFps = object : java.lang.Runnable {
39+
40+ override fun run () {
41+ invalidate()
42+ postRunnableFps(this , fps)
43+ }
44+ }
45+
46+ /* * This runnable generates an animation at each [interval][circleCount]. */
47+ private val runnableAnimation = object : Runnable {
48+
49+ private var index = 0
50+
51+ override fun run () {
52+ animations[index] = circles[index].circleAnimation().also { it.start() }
53+ index = if (index == circles.lastIndex) 0 else index + 1
54+ postRunnableAnimation(this )
55+ }
56+ }
57+
3758 private var delay: Long = 0L
3859 private var duration: Long = 0L
3960 private var circleCount: Long = 0L
4061 private var radius: Float = 0f
4162 private var size: Int = 0
4263
43- private var jobAnimation: Job ? = null
44- private var jobFps : Job ? = null
45-
4664 private var state = State .None
4765 set(value) {
4866 field = value
4967 when (value) {
5068 State .Playing -> {
5169 animations.values.forEach { it.end() }
5270 animations.clear()
53- jobAnimation = animation()
54- }
55- State .Stopped -> try {
56- jobAnimation?.cancel()
57- jobAnimation = null
58- } catch (e: UninitializedPropertyAccessException ) {
59- // cancel() was called before start()
71+ postRunnableAnimation(runnableAnimation)
6072 }
73+ State .Stopped -> removeCallbacks(runnableAnimation)
6174 State .None -> Unit
6275 }
6376 }
6477
6578 override fun onAttachedToWindow () {
6679 super .onAttachedToWindow()
67- jobFps = GlobalScope .launch(Dispatchers .Main ) {
68- while (isActive) {
69- awaitFrame()
70- invalidate()
71- }
72- }
80+ postRunnableFps(runnableFps, fps)
7381 }
7482
7583 override fun onDetachedFromWindow () {
7684 super .onDetachedFromWindow()
77- jobFps?.cancel()
78- jobFps = null
79- jobAnimation?.cancel()
80- jobAnimation = null
85+ removeCallbacks(runnableAnimation)
86+ removeCallbacks(runnableFps)
8187 }
8288
89+ @SuppressLint(" Recycle" )
8390 private fun init (attrs : AttributeSet ) {
8491 context.obtainStyledAttributes(attrs, R .styleable.RippleView , 0 , 0 ).also {
85- val drawable = it.getDrawable(R .styleable.RippleView_drawable )
92+ val drawable = it.getDrawable(R .styleable.RippleView_drawable )!!
8693 delay = it.getInt(R .styleable.RippleView_delay , 500 ).toLong()
8794 duration = it.getInt(R .styleable.RippleView_duration , 500 ).toLong()
8895 size = it.getDimensionPixelSize(R .styleable.RippleView_size , 0 )
@@ -93,13 +100,19 @@ class RippleView : View {
93100 }.recycle()
94101 }
95102
96- /* * This coroutine generates an animation at each [interval][circleCount]. */
97- private fun animation (): Job = GlobalScope .launch(Dispatchers .Main ) {
98- var index = 0
99- while (isActive) {
100- animations[index] = circles[index].circleAnimation().also { it.start() }
101- index = if (index == circles.lastIndex) 0 else index + 1
102- delay(delay)
103+ private fun postRunnableAnimation (runnable : Runnable ) {
104+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .JELLY_BEAN ) {
105+ postOnAnimationDelayed(runnable, delay)
106+ } else {
107+ postDelayed(runnable, delay)
108+ }
109+ }
110+
111+ private fun postRunnableFps (runnable : Runnable , fps : Long ) {
112+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .JELLY_BEAN ) {
113+ postOnAnimation(runnable)
114+ } else {
115+ postDelayed(runnable, fps)
103116 }
104117 }
105118
0 commit comments