I am delevolping an android app in which I have an infinitely repeating animation that is causing a StackOverflowError. It does this, when another animation on the same object is started.
private fun pulse() {
val randomGenerator = Random()
val durationx = randomGenerator.nextInt(4000) + 1500
val inflateX = ObjectAnimator.ofFloat(mContentView, "scaleX", 1.3f).apply {
duration = durationx.toLong()
}
val inflateY = ObjectAnimator.ofFloat(mContentView, "scaleY", 1.3f).apply {
duration = durationx.toLong()
}
val deflateX = ObjectAnimator.ofFloat(mContentView, "scaleX", 1.0f).apply {
duration = durationx.toLong()
}
val deflateY = ObjectAnimator.ofFloat(mContentView, "scaleY", 1.0f).apply {
duration = durationx.toLong()
}
val rotate = ObjectAnimator.ofFloat(mContentView, "rotation", 1.0f).apply {
duration = durationx.toLong()
}
val soulToButton = AnimatorSet().apply {
play(inflateX).with(inflateY)
play(rotate).with(inflateX)
play(deflateX).after(inflateX)
play(deflateY).after(inflateY)
start()
}
soulToButton.addListener(object: AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
soulToButton.start() // stacktrace points to this line as cause for the error.
}
})
soulToButton.start()
AnimatorSet().apply {
play(soulToButton)
start()
}
}
I tried changing the function to fun pulse(stop: boolean), calling pulse(false) for starting the pulse and pulse(true) before the other animation starts, and adding if (stop) {soulToButton.cancel()} in several places. I also tried wrapping the line causing the error like this: while(stop == false){soultoButton.start()}
All of this didnĀ“t help.
Try out this version: its short and concise and basically doing the same thing.
val randomGenerator = Random()
val durationx = randomGenerator.nextInt(4000) + 1500
val animator = animator#{ property : String, value : Float ->
return#animator ObjectAnimator.ofFloat(mContextView, property, value).apply {
duration = durationx.toLong()
repeatMode = REVERSE
repeatCount = INFINITE
}
}
AnimatorSet().apply {
playTogether(animator("scaleX", 1.3f),animator("scaleY", 1.3f),animator("rotation", 45.0f))
start()
}
It is Kotlin but it is still Java and all the JVM limitations are relevant here. Thus you cannot call one function indefinitely without causing StackOverflowError since the JVM stack is limited.
There are special functions that will help you to run the animation indefinitely. For example
objectAnimator.setRepeatCount(ObjectAnimator.INFINITE);
Or in your case
ObjectAnimator.ofFloat(mContentView, "scaleX", 1.3f).apply {
duration = durationx.toLong()
repeatCount = ObjectAnimator.INFINITE
}
After you modify all your animations the same way and chain them in AnimatorSet its start function will play this animation indefinite time without StackOverflowError.
Hope it helps.
You are trying to start animation again after it has completed in here ,
soulToButton.addListener(object: AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
soulToButton.start() // This will start the animation again without an end to the animation hence the StackOverflow error.
}
})
In order to run an animation for infinite time use the following piece of code,
val inflateX = ObjectAnimator.ofFloat(mContentView, "scaleX", 1.3f).apply {
duration = durationx.toLong()
repeatCount = ObjectAnimator.INFINITE
}
val soulToButton = AnimatorSet().apply {
play(inflateX).with(inflateY)
play(rotate).with(inflateX)
play(deflateX).after(inflateX)
play(deflateY).after(inflateY)
}
soulToButton.start()
Related
I have a custom view. In this view, I am drawing 4 circles. This view has drawCirclesWithAnim() function in which I would like to draw circles with animation one by one with sequence.
For example, first circle is drawn at first, then second, then third ... so on. How to achieve this with custom views?
Since it is custom view, I think my only option is ValueAnimator. Currently, to achieve what I need, I am creating 4 ValueAnimators. Check out the code below:
CustomView.kt
private var mAnimValCircle1 = 0f //These values are used in "onDraw()".
private var mAnimValCircle2 = 0f
private var mAnimValCircle3 = 0f
private var mAnimValCircle4 = 0f
...
fun drawCirclesWithAnim(): {
ValueAnimator().apply {
duration = 200
addUpdateListener {
mAnimValCircle1 = it.animatedValue as Float
invalidate()
}
setFloatValues(0f, 1f)
start()
}
ValueAnimator().apply {
startDelay = 200
duration = 150
addUpdateListener {
mAnimValCircle2 = it.animatedValue as Float
invalidate()
}
setFloatValues(0f, 1f)
start()
}
ValueAnimator().apply {
startDelay = 350
duration = 150
addUpdateListener {
mAnimValCircle3 = it.animatedValue as Float
invalidate()
}
setFloatValues(0f, 1f)
start()
}
ValueAnimator().apply {
startDelay = 500
duration = 150
addUpdateListener {
mAnimValCircle4 = it.animatedValue as Float
invalidate()
}
setFloatValues(0f, 1f)
start()
}
}
As you can see, I am manualy calculating the delays and making sure that each animation starts after the previous one finishes. Not only that but also I am creating ValueAnimator for each of the circles. It is working fine. But I reckon this is not perfect solution.
Is there a way to create AnimationSet from ValueAnimator? Or, are there any other solutions available to tackle such cases?
I don't know of a way to use AnimationSet and ValueAnimator and don't believe it is possible out-of-the-box, but what you could do is use parameters in your function and write one method to handle it all, and with that get rid of lots of unneccessary code, something like :
fun drawCircleWithAnim(mAnimValCircle: float, duration: Int, startDelay: Int) {
ValueAnimator().apply {
startDelay = startDelay
duration = duration
addUpdateListener {
mAnimValCircle = it.animatedValue as Float
invalidate()
}
setFloatValues(0f, 1f)
start()
}
}
And then call 4 times like this :
drawCircleWithAnim(mAnimValCircle1, 200, 0)
drawCircleWithAnim(mAnimValCircle2, 150, 200)
drawCircleWithAnim(mAnimValCircle3, 350, 150)
drawCircleWithAnim(mAnimValCircle4, 500, 150)
Creating 4 ValueAnimators should not be a problem unless you are looking for ultra-micro optimization.
I have set-up this objectAnimator that I need to add a listener to:
val animator = ObjectAnimator.ofFloat(star, View.TRANSLATION_X, 200f)
.apply {
repeatCount = 1
repeatMode = ObjectAnimator.REVERSE
start()
}
I know how to use the anonymous listner like below:
animator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
super.onAnimationStart(animation)
//callback 1 - do something
}
override fun onAnimationEnd(animation: Animator?) {
super.onAnimationEnd(animation)
//callback 2 - do something
}
What I now want is to experiment with the lambda version but I am stuck on how to call the listener's callbacks: My code looks like this
animator.addListener {
onStart(it) {
//get errors with this code
}
Please help.
I think you can do it like that. You can pick the animator object by it.
animator.addListener(
onEnd = { animator: Animator -> animator.start() },
onStart = {},
onCancel = {},
onRepeat = {})
Sample in Kotlin :
val sizeAnimation = ValueAnimator.ofFloat(lineWidth * 2)
sizeAnimation
.apply {
addUpdateListener {
annimation.strokeWidth = it.animatedValue as Float
myView.postInvalidateOnAnimation()
}
repeatCount = 5
repeatMode = ValueAnimator.REVERSE
interpolator = LinearInterpolator()
duration = 1000L
start()
}.doOnEnd {
// What to do in the end? // sizeAnimation.start()
}
I did something like this
val animator = ObjectAnimator.ofFloat(view, "translationY", 350f,0f)
animator.duration = 500
animator.startDelay=200
animator.interpolator =AccelerateDecelerateInterpolator()
animator.start()
Now I am trying to add listener to this adapter.
I have tried this,
animator.addListener(onStart = {view.visibility=View.VISIBLE})
but is not working.
Although your question is not clear, as you are not mentioning what is not working. My guess is that your listener is useless.
You are starting the animator and then adding it which of course will never be invoked.
Change as following:
val animator = ObjectAnimator.ofFloat(view, "translationY", 350f, 0f)
animator.apply {
duration = 500
startDelay = 200
addListener(onStart = {
view.visibility = View.VISIBLE
})
AccelerateDecelerateInterpolator()
start()
}
Can someone help me with delaying an animation before every repetition with a duration x?
Here is my animation:
val animations = arrayOf(-140f).map { translation ->
ObjectAnimator.ofFloat(button, "translationX", translation).apply {
startDelay = 1000L //Update
duration = 800
repeatCount = ObjectAnimator.INFINITE
repeatMode = ObjectAnimator.RESTART
}
}
val set = AnimatorSet()
set.playTogether(animations)
set.start()
Thanks!
You can use objectanimator.setStartDelay(your_delay_in_millis); method
It's too easy! just a little change to your code:
1- Change repeatCount from INFINITE to 1
2- Add doOnEnd and start
3- Add your delay time!
Finally, your code must be like this:
val animations = arrayOf(-140f).map { translation ->
ObjectAnimator.ofFloat(binding.viewYatch, "translationX", translation).apply {
//Update
duration = 800
repeatCount = 1
repeatMode = ObjectAnimator.RESTART
doOnEnd {
startDelay = 1000L
start()
}
}
}
val set = AnimatorSet()
set.playTogether(animations)
set.start()
I'm using kotlin destructuring declarations. I used it before with SpringAnimation and it worked perfectly. Now I want use it with ObjectAnimator and I get this error:
Destructuring declaration initializer of type ObjectAnimator! must have a 'component1()' function
Destructuring declaration initializer of type ObjectAnimator! must have a 'component2()' function
Here is my code:
val (xanimator, alphaanim) = findViewById<View>(R.id.imageView).let { img ->
ObjectAnimator.ofFloat(img, "translationX", 100f).apply {
duration = 2000
}
to
ObjectAnimator.ofFloat(img, "alpha", 1.0f).apply {
duration = 2000
}
}
What's wrong?
The issue here is that you can't start an infix call function call on a new line - the compiler essentially infers a semicolon/line ending after your first apply call. This is the same way with operators, see this issue for example.
So you need to reformat your code a bit for the to to connect, most simply like this:
val (xanimator: ObjectAnimator, alphaanim: ObjectAnimator) = findViewById<View>(R.id.imageView).let { img ->
ObjectAnimator.ofFloat(img, "translationX", 100f).apply {
duration = 2000
} to
ObjectAnimator.ofFloat(img, "alpha", 1.0f).apply {
duration = 2000
}
}
But for readability, maybe you could go with something like this:
val (xanimator: ObjectAnimator, alphaanim: ObjectAnimator) = findViewById<View>(R.id.imageView).let { img ->
Pair(
ObjectAnimator.ofFloat(img, "translationX", 100f).apply {
duration = 2000
},
ObjectAnimator.ofFloat(img, "alpha", 1.0f).apply {
duration = 2000
}
)
}
Or anything inbetween.