I'm trying to get two views to move to the middle of the screen and bounce back again x number of times.
This code does that but it runs only once.
` val view = findViewById(R.id.imageView2)
val animation = SpringAnimation(view, DynamicAnimation.TRANSLATION_Y, 0f)
val view2 = findViewById<View>(R.id.imageView3)
val animation2 = SpringAnimation(view2, DynamicAnimation.TRANSLATION_Y, 0f)
findViewById<View>(R.id.imageView2).also { img ->
SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply {
animation.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_HIGH_BOUNCY)
animation.spring.stiffness = SpringForce.STIFFNESS_VERY_LOW
animation.animateToFinalPosition(50f)
}
}
findViewById<View>(R.id.imageView3).also { img ->
SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply {
animation2.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_HIGH_BOUNCY)
animation2.spring.stiffness = SpringForce.STIFFNESS_VERY_LOW
animation2.animateToFinalPosition(-100f)
}
}`
So how do I get it to run x number of times?
This is obviously Spring Animation, but I'm not married to it. If there is another animation that would accomplish this I'd be totally open to changing.
You can run multiple SpringAnimations on the same View by repeatedly calling animateToFinalPosition(translation) with a sequence of translation values.
For example:
startSpringAnimations(findViewById<View>(R.id.imageView1), 300f, 6)
startSpringAnimations(findViewById<View>(R.id.imageView2), -600f, 6)
with a function
/**
* [view] will be moved using [times] SpringAnimations over a distance of abs([totalTranslation])
* If [totalTranslation] is negative, direction will be up, else down
*/
private fun startSpringAnimations(view: View, totalTranslation: Float, times: Int ) {
if(times <= 0){
return
}
val translation = totalTranslation/ times.toFloat()
SpringAnimation(view, DynamicAnimation.TRANSLATION_Y, 0f).apply{
spring.dampingRatio = SpringForce.DAMPING_RATIO_HIGH_BOUNCY
spring.stiffness = SpringForce.STIFFNESS_VERY_LOW
addEndListener(object: DynamicAnimation.OnAnimationEndListener{
private var count = 1
override fun onAnimationEnd(animation1: DynamicAnimation<*>?, canceled: Boolean, value: Float, velocity: Float) {
Log.d("SpringAnimation", "onAnimationEnd: animation $animation1 canceled $canceled value $value velocity $velocity count $count")
if (canceled) return
count++
if(count <= times){
animateToFinalPosition(translation * count)
}
}
})
animateToFinalPosition(translation)
}
}
Set android:repeatCount="infinite" in anim folder
Related
This is something weird and unexpected that is happening. While creating a custom view, I am animating a padding value which is later displaying on Canvas. But other objects like RectF are changing its values too. Using kotlin, I have this helper function:
operator fun Pair<Float, Float>.invoke(callback: (Float) -> Unit) {
with(ValueAnimator.ofFloat(first, second)) {
duration = 500
addUpdateListener {
callback(it.animatedValue as Float)
}
start()
}
}
In my Custom View, I have this code:
private val bgCardBounds = RectF(0f, 0f, 0f, 0f)
private var expandedCard = 0
set(value) {
field = value
when (value) {
1 -> {
(0f to 32f) {
bitmapPadding = it
invalidate()
}
}
}
}
On an touch event, when the value of expandedCard changes from 0 to 1, then even the values left, top, right, bottom values of bgCardBounds also changes. Can someone help me point out why this is happening and how to stop it?
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 a custom flip transformation in a ViewPager2, I've disabled user input so it can only change pages programmatically, however, the speed of the transition is way too fast.
The behavior I need is with 2 fragments, the first one loads and after a few milliseconds I trigger the transition programmatically, I see the animation and the 2nd fragment, that's it.
Here is the pager related code:
viewPager.apply {
adapter = ViewPagerAdapter(this#HostFragment)
setPageTransformer(VerticalFlipTransformation())
isUserInputEnabled = false
}
At some point I trigger the transition like this:
viewPager.currentItem = 1
Here is my adapter:
private class ViewPagerAdapter(fragment: Fragment) :
FragmentStateAdapter(fragment) {
override fun getItemCount() = 2
override fun createFragment(position: Int) = if (position == 0) {
Fragment1()
} else {
Fragment2()
}
}
Finally here is the transformation I'm using:
class VerticalFlipTransformation : ViewPager2.PageTransformer {
override fun transformPage(page: View, position: Float) {
page.translationX = -position * page.width
page.cameraDistance = 20000f
if (position < 0.5 && position > -0.5) {
page.visibility = VISIBLE
} else {
page.visibility = GONE
}
when {
position < -1 -> {
page.alpha = 0f
}
position <= 0 -> {
page.alpha = 1f
page.rotationX = 180 * (1 - abs(position) + 1)
}
position <= 1 -> {
page.alpha = 1f
page.rotationX = -180 * (1 - abs(position) + 1)
}
else -> {
page.alpha = 0f
}
}
}
}
I need to slow down the transition, any ideas? Thanks in advance!
I need to build my own animation library/helper, that could move a view from a point A to point B just by updating the animation's progression percentage. This has to be written in Kotlin, but if needed I can translate it in java.
What I've done
class Animator(
private val animations: List<Animation>
) {
constructor(view: View,
startPoint: Point2D = Point2D(view.x, view.y),
endPoint: Point2D) : this(listOf(Animation(view, startPoint, endPoint)))
private val transitionState: TransitionState = TransitionState(0)
private val animationQueue: Handler = Handler()
/**
* Apply a progression in the animation from [startPoint] to [endPoint] or conversely depending
* on the transition state.
* When the [TransitionState.progress] reach 0 the position of the [view] to animate is at [startPoint]. When it
* reach 100 the view will be at the [endPoint]
*
* #param percent an Integer that must be between 0 and 100 because it's percentage. If the
* given percent is below 0 it
* will override it to 0. Same process when it's over 100.
*/
fun progressTo(percent: Int) {
for (anim in animations) {
val finalPercent = if (percent > 100) 100 else if (percent < 0) 0 else percent
if (Math.abs(transitionState.progress - finalPercent) < 10) {
animationQueue.post {
anim.view.x = (Vector2D(anim.startPoint, anim.endPoint) % finalPercent).endPoint.x
anim.view.y = (Vector2D(anim.startPoint, anim.endPoint) % finalPercent).endPoint.y
}
} else {
anim.view.animate().x((Vector2D(anim.startPoint, anim.endPoint) % finalPercent).endPoint.x)
anim.view.animate().y((Vector2D(anim.startPoint, anim.endPoint) % finalPercent).endPoint.y)
}
transitionState.progress = finalPercent
}
}
/**
* Finish the animation to the endPoint or startPoint depending on the [TransitionState.progress]
*/
fun finishAnimation() {
if (transitionState.progress < 50) {
progressTo(0)
} else if (transitionState.progress >= 50) {
progressTo(100)
}
}
}
data class TransitionState(
var progress: Int,
var isOnTransaction: Boolean = progress != 0 && progress != 100
)
data class Vector2D(
val startPoint: Point2D,
val endPoint: Point2D
) {
operator fun rem(percent: Int): Vector2D {
val finalPercent = if (percent > 100) 100 else if (percent < 0) 0 else percent
return Vector2D(startPoint, Point2D(
startPoint.x + ((endPoint.x - startPoint.x) * finalPercent / 100),
startPoint.y + ((endPoint.y - startPoint.y) * finalPercent / 100)
))
}
}
data class Animation(
val view: View,
val startPoint: Point2D = Point2D(view.x, view.y),
val endPoint: Point2D
)
data class Point2D(
val x: Float,
val y: Float
)
As you can see i'm using the property View.setX and View.setY to move the view. It works great it move the view properly with the correct animation and movement.
The problem
When I use the X and Y values, the constraints (LinearLayout, RelativeLayout or ConstraintLayout) of the view's layout parent are not updated or respected. For instance there is a view (the red one in the first capture) in a constraint layout which is constrained to parent left, right and bottom as shown in this layout editor capture.
When I change the y value of the view the bottom constraint is not updated (it does not fit anymore the parent bottom). Here is the captures of the real application before animation and after animation.
Using the View.layout() or View.invalidate() does not change anything, and if there is a method that could redraw and restore the whole layout, I'm not sure that the application will be performant anymore ....
EDIT
I corrected a piece of code (in the for loop) that enables the anmiation to be more fluent.
The question
Is it possible to change the position of a view without breaking its constraints ?
I'm pretty sure I'm misunderstanding something in the android ui positioning/drawing, so feel free to post your understanding of this even you can't answer to my question. Thank you by advance.
I tried to use this tutorial youtube tutorial. I have a function as follows:
fun fact(x:Int):Int{
tailrec fun factTail(y:Int, z:Int):Int{
return if(y == 0) {
z
} else {
factTail(y - 1, y * z)
}
}
return factTail(x,1)
}
and this function is called in oncreate as:
var abc = fact(5)
Log.i(TAG, "5! = $abc")
When the app outputs log it shows like this:
I/MainActivity: 5! = 0
Can anyone point out what is wrong here.
You code is right and you definitely get 0 for multiple result more than MAX_SIZE of Int value. You can get Int max size with:
Int.MAX_VALUE
So if this y * x cross Int.MAX_VALUE = 2147483647, fun will return 0 to you.
For number bigger than 16 func will return minus number and for greater than 33 it will return 0. you can check this by:
for(x in 5..50){
log.i("$x! : ${fact(x)}")
}
So you can handle this by changing variable from Int to Long
fun fact(x : Long) : Long {
fun factTail(y : Long , z :Long):Long {
return if (y == 0L) z
else return factTail(y-1 ,y*z)
}
return factTail(x ,1)
}
But Long also have its limitation. Hope you get the point.