I am making a school project for Android in Kotlin, which is a breathwork exercise application. I want to iterate through a 2D array, which contains the steps and the duration of each step in a certain exercise, and animate a progress bar for each step, with an animation length of the current step length in seconds.
An example of the 2D array:
[
["inhale", "10"],
["exhale", "10"],
["inhale", "5"],
["hold", "10"],
["exhale", "5"],
etc..
]
I tried the following Kotlin code:
var currentExerciseType: String = ""
var currentExerciseDuration: Long = 0
var i = 0
while (i < exerciseList.size) {
motivation_text.append(exerciseList[i][0] " \n")
currentExerciseType = exerciseList[i][0]
currentExerciseDuration = exerciseList[i][1].toLong() * 1000
exercise_type.text = currentExerciseType
val animation = ObjectAnimator.ofInt(progressBar, "progress", 0, 100)
animation.duration = currentExerciseDuration
animation.interpolator = DecelerateInterpolator()
animation.start()
animation.doOnEnd {
i
}
}
This results in the screen blacking out when this function is running. I tried iterating through the array with a for loop too, but it did not seem to wait for each animation to finish, and just displayed the last step on the screen while the first animation was still running.
Any idea how can I solve this issue? Thanks in advance.
CodePudding user response:
You could use a recursive function, with yours code the while will keep cycling since you aren't waiting the animation to end. Try something like :
private fun animate(i : Int){
//do your things
animation.doOnEnd {
if(i < exerciseList.size)
animate(i 1)
}
.
.
.
//where you need to start the animation
animate(0)
}
CodePudding user response:
The reason your code doesn't work is that animations are run asynchronously. Once you call start()
, the animation is queued to begin, but your current while loop code continues immediately. The animations that you create inside your loop will not start until your current code is completely done so the main thread will be released to start those animations. The doOnEnd
code will not run until some time in the future when the animation is finished, so i
is never called and your while loop runs forever without releasing the main thread so any of your many duplicate animations can even start.
So, to set up your animations in a while loop, you would need to give each animation a delay that is the sum of all previous animation durations. But it is not very simple to do this.
An easier way to set this up is by creating a list of your animations, and then using an AnimatorSet to play them sequentially. Something like this (I didn't test it.):
val animations = exerciseList.map { exercise ->
val exerciseType: String = exercise[0]
ObjectAnimator.ofInt(progressBar, "progress", 0, 100).apply {
duration = exercise[1].toLong() * 1000
doOnStart {
motivation_text.append(exerciseType " \n")
exercise_type.text = exerciseType
}
}
}
AnimatorSet().run {
playSequentially(animations)
start()
}
If you're not familiar with the map
function, it creates a new List out of a source array or iterable by running the code in the lambda and returning the value. So in this case, it creates a list of ObjectAnimators corresponding to each item in the original list.