ObjectAnimators are two times faster then the given duration - Android - android

I have a translate animation like this
fun createTranslateAnimation(fromX: Float, toX: Float, fromY: Float, toY: Float):
ObjectAnimator {
val propertyValuesX = PropertyValuesHolder.ofFloat(View.TRANSLATION_X, fromX, toX)
val propertyValuesY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, fromY, toY)
return ObjectAnimator.ofPropertyValuesHolder(imageView,
propertyValuesX,
propertyValuesY)
.apply {
duration = 3000
}
}
I specified the duration 3000ms. But when I run the app, the animation is executing 2 times faster. So I've put some logs like this.
val objectAnim = createTranslateAnimation(xValueFrom, xValueTo, yValueFrom, yValueTo)
objectAnim.apply {
doOnStart {
time = System.currentTimeMillis()
}
doOnEnd {
val elapsedTime = System.currentTimeMillis() - time
Log.d("$elapsedTime seconds")
}
start()
}
At the end the result is - 1518 seconds
What am I doing wrong? If I specify the duration of 3000ms, why is it executing two times faster?

It sounds like Animation duration scale setting in device's Developer options section is changed.
As far as I know once the Developer options and some setting or value is changed there is nothing you can do so the system doesn't take it in consideration.

Related

How to repeat Android Animation

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

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.

Code inside Kotlin's GlobalScope (Coroutine) is not working properly

I'm trying to make visualizer of quicksort algorithm, the functionality of alogrithm is fine but when i put the code in GlobalScope so that i can use delay to visulize things, the algorithm behaves differently.
Here are my functions....
//Quick Sort Part
private suspend fun partition(arr: MutableList<Button>, low: Int, high: Int): Int {
//If I comment this job, and run the code the algorithm works
val job2 = GlobalScope.launch(Dispatchers.Main) {
var low = low
var high = high
var pivot = arr[(low + high) / 2].layoutParams.height
while (low <= high) {
Log.d(TAG, "partition: $low $high")
while (arr[low].layoutParams.height < pivot) {
low++
}
while (arr[high].layoutParams.height > pivot) {
high--
}
if (low <= high) {
val temp = arr[low].layoutParams.height
arr[low].layoutParams.height = arr[high].layoutParams.height
arr[high].layoutParams.height = temp
root_layout.requestLayout()
low++
high--
}
}
}
job2.join()
return low
}
private fun quickSort(arr: MutableList<Button>, low: Int, high: Int) {
val job1 = GlobalScope.launch(Dispatchers.Main) {
val pi = partition(arr, low, high)
if (low < pi - 1) {
quickSort(arr, low, pi - 1)
}
if (pi < high) {
quickSort(arr, pi, high)
}
}
}
This is the result of that function, and if i don't use coroutine, this works fine, all the bars gets sorted in increasing order..
Because you have defined the low and high variables inside the launch scope, and updating it there but still returning the variable which was received as parameter.
Define it in the outerscope:
var low = low // define the variable in the outer scope
val job2 = GlobalScope.launch(Dispatchers.Main) { // ...
But shadowing isn't the best way (it is error prone as it tricked you), just name it something else for simplicity.
Tip: Use of Main thread is usually for less CPU intensive tasks and should be used mainly for displaying stuffs and updating UI. For computational tasks feel free to use the Dispatchers.Default!
Also using Jobs for doing tasks and joining them isn't that optimized than withContext which is intended for returning the result from another Dispatching thread easily, use it instead!
private suspend fun partition(arr: MutableList<Button>, low: Int, high: Int): Int {
return withContext(Dispatchers.Default) {
var low = low
var high = high
var pivot = arr[(low + high) / 2].layoutParams.height
while (low <= high) {
Log.d(TAG, "partition: $low $high")
while (arr[low].layoutParams.height < pivot) {
low++
}
while (arr[high].layoutParams.height > pivot) {
high--
}
if (low <= high) {
val temp = arr[low].layoutParams.height
arr[low].layoutParams.height = arr[high].layoutParams.height
arr[high].layoutParams.height = temp
root_layout.requestLayout()
low++
high--
}
}
low //^withContext
}
}

Implement animation without breaking layout constraints

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.

Espresso with Custom KeyboardView button press

I am implementing a custom KeyboardView in my app and it's all working at the moment, however, when I attempt to press a key on the keyboard using Espresso ViewAction, I am getting an exception saying:
android.support.test.espresso.PerformException:
Error performing 'single click - At Coordinates: 1070, 2809 and
precision: 16, 16' on view 'with id:
com.example.app.mvpdemo:id/keyboardLayout'.
The code throwing the exception is:
#Test
fun enter100AsPriceShouldDisplay120ForA20PercentTip(){
onView(withId(R.id.editTextCheckAmount))
.perform(typeText("100"), closeSoftKeyboard())
val appContext = InstrumentationRegistry.getTargetContext()
val displayMetrics = appContext.resources.displayMetrics
onView(withId(R.id.keyboardLayout)).perform(clickXY(displayMetrics.widthPixels - 10, displayMetrics.heightPixels - 10))
onView(withText("$120.00")).check(matches(isDisplayed()))
}
and the click XY function which came from this post
private fun clickXY(x: Int, y: Int): ViewAction {
return GeneralClickAction(
Tap.SINGLE,
CoordinatesProvider { view ->
val screenPos = IntArray(2)
view.getLocationOnScreen(screenPos)
val screenX = (screenPos[0] + x).toFloat()
val screenY = (screenPos[1] + y).toFloat()
floatArrayOf(screenX, screenY)
},
Press.FINGER, 0, 0)
}
Here is my keyboard layout (pinned to the bottom of the screen inside a ConstraintLayout):
Does anyone know why? Any help is appreciated.
Answering my own question after determining a flexible solution:
First attempt - get DisplayMetrics of the root View and subtract an arbitrary number to attempt to hit the Keyboard.Key
this didn't work because clickXY function uses the position of the view
this ended up being the reason for the exception since the view is smaller than the DisplayMetrics values and adding to the Views on screen position would give a very high number for the x and y.
So I tried again,
Second attempt - use check method on the ViewMatcher to check the KeyBoardView.
by doing so I was able to get access to the KeyboardView's position x
then I was able to get the KeyboardView's width and height
by performing some math, I was able to figure out target index for x & y
the math:
take the widthPercent for the Keyboard.Key (in my case 33.3%)
take the rowCount of the keyboard.xml (in my case 3)
use (viewWidth * widthPercent) / 4 to get relativeButtonX
use (viewHeight / rowCount) / 2 to get relativeButtonY
then for targetY, I took viewHeight - relativeButtonY
finally, for targetX, I took (viewPosX + viewWidth) - relativeButtonX
So enough explanation, here is the code:
#Test
fun enter100AsPriceShouldDisplay120ForA20PercentTip() {
onView(withId(R.id.editTextCheckAmount))
.perform(typeText("100"), closeSoftKeyboard())
// call the function to get the targets
val (viewTargetY, viewTargetX) = getTargetXAndY()
// perform the action
onView(withId(R.id.keyboardLayout)).perform(clickXY(viewTargetX.toInt(), viewTargetY))
onView(withText("Tip: $20.00")).check(matches(isDisplayed()))
onView(withText("Total: $120.00")).check(matches(isDisplayed()))
}
and the helper method with all the math:
private fun getTargetXAndY(): Pair<Int, Double> {
var viewHeight = 0
var viewWidth = 0
var viewPosX = 0F
val viewMatcher = onView(withId(R.id.keyboardLayout))
viewMatcher.check { view, _ ->
viewWidth = view.width
viewHeight = view.height
viewPosX = view.x
}
val keyboardKeyWidthPercent = 0.333
val keyboardRowsCount = 3
val keyboardButtonWidthQuarter = (viewWidth * keyboardKeyWidthPercent) / 4
val keyboardButtonHeightHalf = (viewHeight / keyboardRowsCount) / 2
val viewTargetY = viewHeight - keyboardButtonHeightHalf
val viewTargetX = (viewPosX + viewWidth) - keyboardButtonWidthQuarter
return Pair(viewTargetY, viewTargetX)
}
Now, the click is not perfectly centered but it clicks the button pretty close to the center.

Categories

Resources