Animation set, shake effect - android

I try to shake animation my view, i write this code, but It seems to me that it is possible to make it easier
var animatorSet = AnimatorSet()
var objectRotateAnimator = ObjectAnimator.ofFloat(shake, "rotation", -5f, 5f)
objectRotateAnimator.apply {
repeatMode = ValueAnimator.REVERSE
repeatCount = ValueAnimator.INFINITE
duration = 70
interpolator = LinearInterpolator()
}
var objectTranslateAnimator = ObjectAnimator.ofFloat(shake, "translate", -5f, 5f)
objectTranslateAnimator.apply {
repeatMode = ValueAnimator.REVERSE
repeatCount = ValueAnimator.INFINITE
duration = 70
interpolator = LinearInterpolator()
}
start_shake.setOnClickListener {
animatorSet.play(objectRotateAnimator).with(objectTranslateAnimator)
animatorSet.start()
}
How can i do it more simple?

You can extract some functions and fields to reduce the duplication:
private val linearInterpolator = LinearInterpolator()
private fun shakeAnimator(propertyName: String) =
ObjectAnimator.ofFloat(shake, propertyName, -5f, 5f).apply {
repeatMode = ValueAnimator.REVERSE
repeatCount = ValueAnimator.INFINITE
duration = 70
interpolator = linearInterpolator
}
Then it will just look like:
start_shake.setOnClickListener {
AnimatorSet().apply {
play(shakeAnimator("rotation")).with(shakeAnimator("translate"))
start()
}
}
The good method names have removed the need for intermediate variables which has shortened the code further.

Related

How to make dynamic animation with ObjectAnimator?

I'm currently working in a custom view, basically its a draggable view that has a pulse effect. In fact I achieved the effect that I want but I it has performance issues.
I don't know if it can be seen well in the gif but at first the effect runs without problems and then it shows performance issues.
Here is my class
class BalanceAndFadeView #JvmOverloads constructor(...) : FrameLayout(...) {
// The thumb view is the draggable view
private var thumb: ImageView
// These are for the pulsing effect
private var pulseView: ImageView
private var pulseView2: ImageView
private var selectedPoint: Point = Point()
private val animators = arrayListOf<Animator>()
private var animatorsSet = AnimatorSet()
private var centerX = 0f
private var centerY = 0f
init {
// Here I create and add the views to the root view
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
centerX = w * 0.5f
centerY = h * 0.5f
animationStart()
}
override fun onTouchEvent(event: MotionEvent): Boolean {
return when (event.actionMasked) {
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_MOVE,
MotionEvent.ACTION_UP -> {
val snapPoint = Point(event.x.toInt(), event.y.toInt())
selectedPoint = snapPoint
setCoordinate(snapPoint.x, snapPoint.y)
true
}
else -> {
false
}
}
}
fun setCoordinate(x: Int, y: Int) {
// Sets the thumb view coordinates to create the draggable view effect
thumb.x = x - thumb.measuredWidth * 0.5f
thumb.y = y - thumb.measuredHeight * 0.5f
// These animators are for the pulsing effect to translate to the center of the view
// I want the start point of the translation to update with the position
// of the thumb view, so I need it to be a dynamic animation
val translationXAnimator =
ObjectAnimator.ofFloat(pulseView, "TranslationX", x.toFloat() - centerX, 0f)
.apply {
repeatCount = ObjectAnimator.INFINITE
repeatMode = ObjectAnimator.RESTART
startDelay = 0
}
val translationYAnimator =
ObjectAnimator.ofFloat(pulseView, "TranslationY", y.toFloat() - centerY, 0f)
.apply {
repeatCount = ObjectAnimator.INFINITE
repeatMode = ObjectAnimator.RESTART
startDelay = 0
}
val translationXAnimator2 = ObjectAnimator.ofFloat(...).apply { ... }
val translationYAnimator2 = ObjectAnimator.ofFloat(...).apply { ... }
animatorsSet.playTogether(
... // adds new animators
)
// Since I run this method every time the touch event it's troggered,
// the performance gets worse every time I touch the screen
animatorsSet.start()
// But if I call animatorsSet.end() and then animatorsSet.start()
// the pulsing effect does not work as expected since the animation stops
// but in terms of performance it's just normal
}
// These animators are for the pulsing effect
private fun animationStart() {
val scaleXAnimator = ObjectAnimator.ofFloat(pulseView, "ScaleX", 1f, 6f).apply {
repeatCount = ObjectAnimator.INFINITE
repeatMode = ObjectAnimator.RESTART
startDelay = 0
}
animators.add(scaleXAnimator)
val scaleYAnimator = ObjectAnimator.ofFloat(pulseView, "ScaleY", 1f, 6f).apply {
repeatCount = ObjectAnimator.INFINITE
repeatMode = ObjectAnimator.RESTART
startDelay = 0
}
animators.add(scaleYAnimator)
val alphaAnimator = ObjectAnimator.ofFloat(pulseView, "Alpha", 1f, 0f).apply {
repeatCount = ObjectAnimator.INFINITE
repeatMode = ObjectAnimator.RESTART
startDelay = 0
}
animators.add(alphaAnimator)
val scaleXAnimator2 = ObjectAnimator.ofFloat( ... ).apply { ... }
animators.add(scaleXAnimator2)
val scaleYAnimator2 = ObjectAnimator.ofFloat( ... ).apply { ... }
animators.add(scaleYAnimator2)
val alphaAnimator2 = ObjectAnimator.ofFloat( ... ).apply { ... }
animators.add(alphaAnimator2)
animatorsSet.playTogether(animators)
animatorsSet.interpolator = LinearInterpolator()
animatorsSet.duration = 1000
animatorsSet.start()
}
}
I hope you can help me or recommend me a better approach

Is it possible to create AnimatonSet from ValueAnimator?

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.

How do I add Listener to ObjectAnimator in Kotlin?

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()
}

How to delay an animation in Kotlin?

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()

Looping an AnimationSet causes StackOverflowError

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()

Categories

Resources