1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.animation.cts; 18 19 import static com.android.compatibility.common.util.CtsMockitoUtils.within; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertTrue; 23 import static org.mockito.Mockito.atLeast; 24 import static org.mockito.Mockito.mock; 25 import static org.mockito.Mockito.timeout; 26 import static org.mockito.Mockito.times; 27 import static org.mockito.Mockito.verify; 28 29 import android.animation.Animator; 30 import android.animation.AnimatorListenerAdapter; 31 import android.animation.ArgbEvaluator; 32 import android.animation.ObjectAnimator; 33 import android.animation.PropertyValuesHolder; 34 import android.animation.TypeConverter; 35 import android.animation.ValueAnimator; 36 import android.app.Instrumentation; 37 import android.graphics.Color; 38 import android.graphics.Path; 39 import android.graphics.PointF; 40 import android.os.SystemClock; 41 import android.util.Property; 42 import android.view.View; 43 import android.view.animation.AccelerateInterpolator; 44 import android.view.animation.Interpolator; 45 46 import androidx.test.InstrumentationRegistry; 47 import androidx.test.filters.MediumTest; 48 import androidx.test.rule.ActivityTestRule; 49 import androidx.test.runner.AndroidJUnit4; 50 51 import org.junit.Before; 52 import org.junit.Rule; 53 import org.junit.Test; 54 import org.junit.runner.RunWith; 55 56 import java.util.concurrent.CountDownLatch; 57 import java.util.concurrent.TimeUnit; 58 59 @MediumTest 60 @RunWith(AndroidJUnit4.class) 61 public class ObjectAnimatorTest { 62 private static final float LINE1_START = -32f; 63 private static final float LINE1_END = -2f; 64 private static final float LINE1_Y = 0f; 65 private static final float LINE2_START = 2f; 66 private static final float LINE2_END = 12f; 67 private static final float QUADRATIC_CTRL_PT1_X = 0f; 68 private static final float QUADRATIC_CTRL_PT1_Y = 0f; 69 private static final float QUADRATIC_CTRL_PT2_X = 50f; 70 private static final float QUADRATIC_CTRL_PT2_Y = 20f; 71 private static final float QUADRATIC_CTRL_PT3_X = 100f; 72 private static final float QUADRATIC_CTRL_PT3_Y = 0f; 73 private static final float EPSILON = .001f; 74 75 private Instrumentation mInstrumentation; 76 private AnimationActivity mActivity; 77 private ObjectAnimator mObjectAnimator; 78 private long mDuration = 1000; 79 80 @Rule 81 public ActivityTestRule<AnimationActivity> mActivityRule = 82 new ActivityTestRule<>(AnimationActivity.class); 83 84 @Before 85 public void setup() { 86 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 87 mInstrumentation.setInTouchMode(false); 88 mActivity = mActivityRule.getActivity(); 89 mObjectAnimator = (ObjectAnimator) mActivity.createAnimatorWithDuration(mDuration); 90 } 91 92 @Test 93 public void testDuration() throws Throwable { 94 final long duration = 2000; 95 ObjectAnimator objectAnimatorLocal = (ObjectAnimator) mActivity.createAnimatorWithDuration( 96 duration); 97 startAnimation(objectAnimatorLocal); 98 assertEquals(duration, objectAnimatorLocal.getDuration()); 99 } 100 101 @Test 102 public void testOfFloat() throws Throwable { 103 Object object = mActivity.view.newBall; 104 String property = "y"; 105 float startY = mActivity.mStartY; 106 float endY = mActivity.mStartY + mActivity.mDeltaY; 107 ObjectAnimator objAnimator = ObjectAnimator.ofFloat(object, property, startY, endY); 108 assertTrue(objAnimator != null); 109 110 ValueAnimator.AnimatorUpdateListener updateListener = ((animator) -> { 111 float y = (Float) animator.getAnimatedValue(); 112 assertTrue(y >= startY); 113 assertTrue(y <= endY); 114 }); 115 ValueAnimator.AnimatorUpdateListener mockListener = 116 mock(ValueAnimator.AnimatorUpdateListener.class); 117 objAnimator.addUpdateListener(mockListener); 118 objAnimator.addUpdateListener(updateListener); 119 objAnimator.setDuration(200); 120 objAnimator.setRepeatCount(ValueAnimator.INFINITE); 121 objAnimator.setInterpolator(new AccelerateInterpolator()); 122 objAnimator.setRepeatMode(ValueAnimator.REVERSE); 123 mActivityRule.runOnUiThread(objAnimator::start); 124 assertTrue(objAnimator != null); 125 126 verify(mockListener, timeout(2000).atLeast(20)).onAnimationUpdate(objAnimator); 127 mActivityRule.runOnUiThread(objAnimator::cancel); 128 } 129 130 @Test 131 public void testOfFloatBase() throws Throwable { 132 Object object = mActivity.view.newBall; 133 String property = "y"; 134 float startY = mActivity.mStartY; 135 float endY = mActivity.mStartY + mActivity.mDeltaY; 136 ObjectAnimator animator = ObjectAnimator.ofFloat(object, property, startY, endY); 137 ObjectAnimator objAnimator = new ObjectAnimator(); 138 objAnimator.setTarget(object); 139 objAnimator.setPropertyName(property); 140 assertEquals(animator.getTarget(), objAnimator.getTarget()); 141 assertEquals(animator.getPropertyName(), objAnimator.getPropertyName()); 142 } 143 144 @Test 145 public void testOfInt() throws Throwable { 146 Object object = mActivity.view.newBall; 147 String property = "scrollY"; 148 149 final ObjectAnimator intAnimator = ObjectAnimator.ofInt(object, property, 200, 0); 150 ValueAnimator.AnimatorUpdateListener updateListener = ((animator) -> { 151 int value = (Integer) intAnimator.getAnimatedValue(); 152 assertTrue(value <= 200); 153 assertTrue(value >= 0); 154 }); 155 final Animator.AnimatorListener mockListener = mock(Animator.AnimatorListener.class); 156 intAnimator.addListener(mockListener); 157 158 intAnimator.addUpdateListener(updateListener); 159 intAnimator.setDuration(200); 160 intAnimator.setRepeatCount(1); 161 intAnimator.setRepeatMode(ValueAnimator.REVERSE); 162 mActivityRule.runOnUiThread(intAnimator::start); 163 164 verify(mockListener, timeout(400)).onAnimationRepeat(intAnimator); 165 verify(mockListener, timeout(400)).onAnimationEnd(intAnimator, false); 166 } 167 168 @Test 169 public void testOfObject() throws Throwable { 170 Object object = mActivity.view.newBall; 171 String property = "backgroundColor"; 172 int startColor = 0xFFFF8080; 173 int endColor = 0xFF8080FF; 174 175 Object[] values = {new Integer(startColor), new Integer(endColor)}; 176 ArgbEvaluator evaluator = new ArgbEvaluator(); 177 final ObjectAnimator colorAnimator = ObjectAnimator.ofObject(object, property, 178 evaluator, values); 179 ValueAnimator.AnimatorUpdateListener updateListener = ((animator) -> { 180 int color = (Integer) colorAnimator.getAnimatedValue(); 181 // Check that channel is interpolated separately. 182 assertEquals(0xFF, Color.alpha(color)); 183 assertTrue(Color.red(color) <= Color.red(startColor)); 184 assertTrue(Color.red(color) >= Color.red(endColor)); 185 assertEquals(0x80, Color.green(color)); 186 assertTrue(Color.blue(color) >= Color.blue(startColor)); 187 assertTrue(Color.blue(color) <= Color.blue(endColor)); 188 }); 189 final Animator.AnimatorListener mockListener = mock(Animator.AnimatorListener.class); 190 colorAnimator.addListener(mockListener); 191 192 colorAnimator.addUpdateListener(updateListener); 193 colorAnimator.setDuration(200); 194 colorAnimator.setRepeatCount(1); 195 colorAnimator.setRepeatMode(ValueAnimator.REVERSE); 196 mActivityRule.runOnUiThread(colorAnimator::start); 197 198 verify(mockListener, timeout(400)).onAnimationRepeat(colorAnimator); 199 verify(mockListener, timeout(400)).onAnimationEnd(colorAnimator, false); 200 } 201 202 @Test 203 public void testOfPropertyValuesHolder() throws Throwable { 204 Object object = mActivity.view.newBall; 205 String propertyName = "scrollX"; 206 int startValue = 200; 207 int endValue = 0; 208 int[] values = {startValue, endValue}; 209 PropertyValuesHolder propertyValuesHolder = PropertyValuesHolder.ofInt(propertyName, values); 210 final ObjectAnimator intAnimator = ObjectAnimator.ofPropertyValuesHolder(object, 211 propertyValuesHolder); 212 213 ValueAnimator.AnimatorUpdateListener updateListener = ((animator) -> { 214 int value = (Integer) intAnimator.getAnimatedValue(); 215 // Check that each channel is interpolated separately. 216 assertTrue(value <= 200); 217 assertTrue(value >= 0); 218 }); 219 final Animator.AnimatorListener mockListener = mock(Animator.AnimatorListener.class); 220 intAnimator.addListener(mockListener); 221 222 intAnimator.addUpdateListener(updateListener); 223 intAnimator.setDuration(200); 224 mActivityRule.runOnUiThread(intAnimator::start); 225 226 verify(mockListener, timeout(400)).onAnimationEnd(intAnimator, false); 227 } 228 229 @Test 230 public void testOfArgb() throws Throwable { 231 Object object = mActivity.view; 232 String property = "backgroundColor"; 233 int start = 0xffff0000; 234 int end = 0xff0000ff; 235 int[] values = {start, end}; 236 int startRed = Color.red(start); 237 int startBlue = Color.blue(start); 238 int endRed = Color.red(end); 239 int endBlue = Color.blue(end); 240 241 ValueAnimator.AnimatorUpdateListener updateListener = ((anim) -> { 242 Integer animatedValue = (Integer) anim.getAnimatedValue(); 243 int alpha = Color.alpha(animatedValue); 244 int red = Color.red(animatedValue); 245 int green = Color.green(animatedValue); 246 int blue = Color.blue(animatedValue); 247 assertTrue(red <= startRed); 248 assertTrue(red >= endRed); 249 assertTrue(blue >= startBlue); 250 assertTrue(blue <= endBlue); 251 assertEquals(255, alpha); 252 assertEquals(0, green); 253 254 }); 255 256 final Animator.AnimatorListener mockListener = mock(Animator.AnimatorListener.class); 257 final ObjectAnimator animator = ObjectAnimator.ofArgb(object, property, start, end); 258 animator.setDuration(50); 259 animator.addListener(mockListener); 260 animator.addUpdateListener(updateListener); 261 262 mActivityRule.runOnUiThread(animator::start); 263 264 verify(mockListener, timeout(400)).onAnimationEnd(animator, false); 265 } 266 267 @Test 268 public void testNullObject() throws Throwable { 269 final ObjectAnimator anim = ObjectAnimator.ofFloat(null, "dummyValue", 0f, 1f); 270 anim.setDuration(300); 271 final ValueAnimator.AnimatorUpdateListener updateListener = 272 mock(ValueAnimator.AnimatorUpdateListener.class); 273 anim.addUpdateListener(updateListener); 274 final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class); 275 anim.addListener(listener); 276 277 mActivityRule.runOnUiThread(anim::start); 278 verify(listener, within(500)).onAnimationEnd(anim, false); 279 // Verify that null target ObjectAnimator didn't get canceled. 280 verify(listener, times(0)).onAnimationCancel(anim); 281 // Verify that the update listeners gets called a few times. 282 verify(updateListener, atLeast(8)).onAnimationUpdate(anim); 283 } 284 285 @Test 286 public void testGetPropertyName() throws Throwable { 287 Object object = mActivity.view.newBall; 288 String propertyName = "backgroundColor"; 289 int startColor = mActivity.view.RED; 290 int endColor = mActivity.view.BLUE; 291 Object[] values = {new Integer(startColor), new Integer(endColor)}; 292 ArgbEvaluator evaluator = new ArgbEvaluator(); 293 ObjectAnimator colorAnimator = ObjectAnimator.ofObject(object, propertyName, 294 evaluator, values); 295 String actualPropertyName = colorAnimator.getPropertyName(); 296 assertEquals(propertyName, actualPropertyName); 297 } 298 299 @Test 300 public void testSetFloatValues() throws Throwable { 301 Object object = mActivity.view.newBall; 302 String property = "y"; 303 float startY = mActivity.mStartY; 304 float endY = mActivity.mStartY + mActivity.mDeltaY; 305 float[] values = {startY, endY}; 306 ObjectAnimator objAnimator = new ObjectAnimator(); 307 ValueAnimator.AnimatorUpdateListener updateListener = ((animator) -> { 308 float y = (Float) animator.getAnimatedValue(); 309 assertTrue(y >= startY); 310 assertTrue(y <= endY); 311 }); 312 ValueAnimator.AnimatorUpdateListener mockListener = 313 mock(ValueAnimator.AnimatorUpdateListener.class); 314 objAnimator.addUpdateListener(mockListener); 315 objAnimator.addUpdateListener(updateListener); 316 objAnimator.setTarget(object); 317 objAnimator.setPropertyName(property); 318 objAnimator.setFloatValues(values); 319 objAnimator.setDuration(mDuration); 320 objAnimator.setRepeatCount(ValueAnimator.INFINITE); 321 objAnimator.setInterpolator(new AccelerateInterpolator()); 322 objAnimator.setRepeatMode(ValueAnimator.REVERSE); 323 mActivityRule.runOnUiThread(objAnimator::start); 324 325 verify(mockListener, timeout(2000).atLeast(20)).onAnimationUpdate(objAnimator); 326 mActivityRule.runOnUiThread(objAnimator::cancel); 327 } 328 329 @Test 330 public void testGetTarget() throws Throwable { 331 Object object = mActivity.view.newBall; 332 String propertyName = "backgroundColor"; 333 int startColor = mActivity.view.RED; 334 int endColor = mActivity.view.BLUE; 335 Object[] values = {new Integer(startColor), new Integer(endColor)}; 336 ArgbEvaluator evaluator = new ArgbEvaluator(); 337 ObjectAnimator colorAnimator = ObjectAnimator.ofObject(object, propertyName, 338 evaluator, values); 339 Object target = colorAnimator.getTarget(); 340 assertEquals(object, target); 341 } 342 343 @Test 344 public void testClone() throws Throwable { 345 Object object = mActivity.view.newBall; 346 String property = "y"; 347 float startY = mActivity.mStartY; 348 float endY = mActivity.mStartY + mActivity.mDeltaY; 349 Interpolator interpolator = new AccelerateInterpolator(); 350 ObjectAnimator objAnimator = ObjectAnimator.ofFloat(object, property, startY, endY); 351 objAnimator.setDuration(mDuration); 352 objAnimator.setRepeatCount(ValueAnimator.INFINITE); 353 objAnimator.setInterpolator(interpolator); 354 objAnimator.setRepeatMode(ValueAnimator.REVERSE); 355 ObjectAnimator cloneAnimator = objAnimator.clone(); 356 357 assertEquals(mDuration, cloneAnimator.getDuration()); 358 assertEquals(ValueAnimator.INFINITE, cloneAnimator.getRepeatCount()); 359 assertEquals(ValueAnimator.REVERSE, cloneAnimator.getRepeatMode()); 360 assertEquals(object, cloneAnimator.getTarget()); 361 assertEquals(property, cloneAnimator.getPropertyName()); 362 assertEquals(interpolator, cloneAnimator.getInterpolator()); 363 } 364 365 @Test 366 public void testOfFloat_Path() throws Throwable { 367 // Test for ObjectAnimator.ofFloat(Object, String, String, Path) 368 // Create a path that contains two disconnected line segments. Check that the animated 369 // property x and property y always stay on the line segments. 370 Path path = new Path(); 371 path.moveTo(LINE1_START, LINE1_Y); 372 path.lineTo(LINE1_END, LINE1_Y); 373 path.moveTo(LINE2_START, LINE2_START); 374 path.lineTo(LINE2_END, LINE2_END); 375 final double totalLength = (LINE1_END - LINE1_START) + Math.sqrt( 376 (LINE2_END - LINE2_START) * (LINE2_END - LINE2_START) + 377 (LINE2_END - LINE2_START) * (LINE2_END - LINE2_START)); 378 final double firstSegEndFraction = (LINE1_END - LINE1_START) / totalLength; 379 final float delta = 0.01f; 380 381 Object target = new Object() { 382 public void setX(float x) { 383 } 384 385 public void setY(float y) { 386 } 387 }; 388 389 final ObjectAnimator anim = ObjectAnimator.ofFloat(target, "x", "y", path); 390 anim.setDuration(200); 391 // Linear interpolator 392 anim.setInterpolator(null); 393 anim.addUpdateListener((ValueAnimator animation) -> { 394 float fraction = animation.getAnimatedFraction(); 395 float x = (Float) animation.getAnimatedValue("x"); 396 float y = (Float) animation.getAnimatedValue("y"); 397 398 // Check that the point is on the path. 399 if (x <= 0) { 400 // First line segment is a horizontal line. 401 assertTrue(x >= LINE1_START); 402 assertTrue(x <= LINE1_END); 403 assertEquals(LINE1_Y, y, 0.0f); 404 405 // Check that the time animation stays on the first segment is proportional to 406 // the length of the first line segment. 407 assertTrue(fraction < firstSegEndFraction + delta); 408 } else { 409 assertTrue(x >= LINE2_START); 410 assertTrue(x <= LINE2_END); 411 assertEquals(x, y, 0.0f); 412 413 // Check that the time animation stays on the second segment is proportional to 414 // the length of the second line segment. 415 assertTrue(fraction > firstSegEndFraction - delta); 416 } 417 }); 418 final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class); 419 anim.addListener(listener); 420 mActivityRule.runOnUiThread(anim::start); 421 verify(listener, within(400)).onAnimationEnd(anim, false); 422 } 423 424 @Test 425 public void testOfInt_Path() throws Throwable { 426 // Test for ObjectAnimator.ofInt(Object, String, String, Path) 427 // Create a path that contains two disconnected line segments. Check that the animated 428 // property x and property y always stay on the line segments. 429 Path path = new Path(); 430 path.moveTo(LINE1_START, -LINE1_START); 431 path.lineTo(LINE1_END, -LINE1_END); 432 path.moveTo(LINE2_START, LINE2_START); 433 path.lineTo(LINE2_END, LINE2_END); 434 435 Object target = new Object() { 436 public void setX(float x) { 437 } 438 439 public void setY(float y) { 440 } 441 }; 442 final CountDownLatch endLatch = new CountDownLatch(1); 443 final ObjectAnimator anim = ObjectAnimator.ofInt(target, "x", "y", path); 444 anim.setDuration(200); 445 446 // Linear interpolator 447 anim.setInterpolator(null); 448 anim.addUpdateListener((ValueAnimator animation) -> { 449 float fraction = animation.getAnimatedFraction(); 450 int x = (Integer) animation.getAnimatedValue("x"); 451 int y = (Integer) animation.getAnimatedValue("y"); 452 453 // Check that the point is on the path. 454 if (x <= 0) { 455 // Check that the time animation stays on the first segment is proportional to 456 // the length of the first line segment. 457 assertTrue(x >= LINE1_START); 458 assertTrue(x <= LINE1_END); 459 assertEquals(x, -y); 460 461 // First line segment is 3 times as long as the second line segment, so the 462 // 3/4 of the animation duration will be spent on the first line segment. 463 assertTrue(fraction <= 0.75f); 464 } else { 465 // Check that the time animation stays on the second segment is proportional to 466 // the length of the second line segment. 467 assertTrue(x >= LINE2_START); 468 assertTrue(x <= LINE2_END); 469 assertEquals(x, y); 470 471 assertTrue(fraction >= 0.75f); 472 } 473 }); 474 anim.addListener(new AnimatorListenerAdapter() { 475 @Override 476 public void onAnimationEnd(Animator animation) { 477 endLatch.countDown(); 478 } 479 }); 480 mActivityRule.runOnUiThread(anim::start); 481 assertTrue(endLatch.await(400, TimeUnit.MILLISECONDS)); 482 } 483 484 @Test 485 public void testOfMultiFloat_Path() throws Throwable { 486 // Test for ObjectAnimator.ofMultiFloat(Object, String, Path); 487 // Create a quadratic bezier curve that are symmetric about the vertical line (x = 50). 488 // Expect when fraction < 0.5, x < 50, otherwise, x >= 50. 489 Path path = new Path(); 490 path.moveTo(QUADRATIC_CTRL_PT1_X, QUADRATIC_CTRL_PT1_Y); 491 path.quadTo(QUADRATIC_CTRL_PT2_X, QUADRATIC_CTRL_PT2_Y, 492 QUADRATIC_CTRL_PT3_X, QUADRATIC_CTRL_PT3_Y); 493 494 Object target = new Object() { 495 public void setPosition(float x, float y) { 496 } 497 }; 498 499 final ObjectAnimator anim = ObjectAnimator.ofMultiFloat(target, "position", path); 500 // Linear interpolator 501 anim.setInterpolator(null); 502 anim.setDuration(200); 503 504 anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 505 float lastFraction = 0; 506 float lastX = 0; 507 float lastY = 0; 508 @Override 509 public void onAnimationUpdate(ValueAnimator animation) { 510 float[] values = (float[]) animation.getAnimatedValue(); 511 assertEquals(2, values.length); 512 float x = values[0]; 513 float y = values[1]; 514 float fraction = animation.getAnimatedFraction(); 515 // Given that the curve is symmetric about the line (x = 50), x should be less than 516 // 50 for half of the animation duration. 517 if (fraction < 0.5) { 518 assertTrue(x < QUADRATIC_CTRL_PT2_X); 519 } else { 520 assertTrue(x >= QUADRATIC_CTRL_PT2_X); 521 } 522 523 if (lastFraction > 0.5) { 524 // x should be increasing, y should be decreasing 525 assertTrue(x >= lastX); 526 assertTrue(y <= lastY); 527 } else if (fraction <= 0.5) { 528 // when fraction <= 0.5, both x, y should be increasing 529 assertTrue(x >= lastX); 530 assertTrue(y >= lastY); 531 } 532 lastX = x; 533 lastY = y; 534 lastFraction = fraction; 535 } 536 }); 537 final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class); 538 anim.addListener(listener); 539 mActivityRule.runOnUiThread(anim::start); 540 verify(listener, within(400)).onAnimationEnd(anim, false); 541 } 542 543 @Test 544 public void testOfMultiFloat() throws Throwable { 545 // Test for ObjectAnimator.ofMultiFloat(Object, String, float[][]); 546 final float[][] data = new float[10][]; 547 for (int i = 0; i < data.length; i++) { 548 data[i] = new float[3]; 549 data[i][0] = i; 550 data[i][1] = i * 2; 551 data[i][2] = 0f; 552 } 553 554 Object target = new Object() { 555 public void setPosition(float x, float y, float z) { 556 } 557 }; 558 final CountDownLatch endLatch = new CountDownLatch(1); 559 final ObjectAnimator anim = ObjectAnimator.ofMultiFloat(target, "position", data); 560 anim.setInterpolator(null); 561 anim.setDuration(60); 562 anim.addListener(new AnimatorListenerAdapter() { 563 @Override 564 public void onAnimationEnd(Animator animation) { 565 endLatch.countDown(); 566 } 567 }); 568 569 anim.addUpdateListener((ValueAnimator animation) -> { 570 float fraction = animation.getAnimatedFraction(); 571 float[] values = (float[]) animation.getAnimatedValue(); 572 assertEquals(3, values.length); 573 574 float expectedX = fraction * (data.length - 1); 575 576 assertEquals(expectedX, values[0], EPSILON); 577 assertEquals(expectedX * 2, values[1], EPSILON); 578 assertEquals(0f, values[2], 0.0f); 579 }); 580 581 mActivityRule.runOnUiThread(anim::start); 582 assertTrue(endLatch.await(200, TimeUnit.MILLISECONDS)); 583 } 584 585 @Test 586 public void testOfMultiInt_Path() throws Throwable { 587 // Test for ObjectAnimator.ofMultiInt(Object, String, Path); 588 // Create a quadratic bezier curve that are symmetric about the vertical line (x = 50). 589 // Expect when fraction < 0.5, x < 50, otherwise, x >= 50. 590 Path path = new Path(); 591 path.moveTo(QUADRATIC_CTRL_PT1_X, QUADRATIC_CTRL_PT1_Y); 592 path.quadTo(QUADRATIC_CTRL_PT2_X, QUADRATIC_CTRL_PT2_Y, 593 QUADRATIC_CTRL_PT3_X, QUADRATIC_CTRL_PT3_Y); 594 595 Object target = new Object() { 596 public void setPosition(int x, int y) { 597 } 598 }; 599 600 final ObjectAnimator anim = ObjectAnimator.ofMultiInt(target, "position", path); 601 // Linear interpolator 602 anim.setInterpolator(null); 603 anim.setDuration(200); 604 605 anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 606 float lastFraction = 0; 607 int lastX = 0; 608 int lastY = 0; 609 @Override 610 public void onAnimationUpdate(ValueAnimator animation) { 611 int[] values = (int[]) animation.getAnimatedValue(); 612 assertEquals(2, values.length); 613 int x = values[0]; 614 int y = values[1]; 615 float fraction = animation.getAnimatedFraction(); 616 // Given that the curve is symmetric about the line (x = 50), x should be less than 617 // 50 for half of the animation duration. 618 if (fraction < 0.5) { 619 assertTrue(x < QUADRATIC_CTRL_PT2_X); 620 } else { 621 assertTrue(x >= QUADRATIC_CTRL_PT2_X); 622 } 623 624 if (lastFraction > 0.5) { 625 // x should be increasing, y should be decreasing 626 assertTrue(x >= lastX); 627 assertTrue(y <= lastY); 628 } else if (fraction <= 0.5) { 629 // when fraction <= 0.5, both x, y should be increasing 630 assertTrue(x >= lastX); 631 assertTrue(y >= lastY); 632 } 633 lastX = x; 634 lastY = y; 635 lastFraction = fraction; 636 } 637 }); 638 final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class); 639 anim.addListener(listener); 640 mActivityRule.runOnUiThread(anim::start); 641 verify(listener, within(400)).onAnimationEnd(anim, false); 642 } 643 644 @Test 645 public void testOfMultiInt() throws Throwable { 646 // Test for ObjectAnimator.ofMultiFloat(Object, String, int[][]); 647 final int[][] data = new int[10][]; 648 for (int i = 0; i < data.length; i++) { 649 data[i] = new int[3]; 650 data[i][0] = i; 651 data[i][1] = i * 2; 652 data[i][2] = 0; 653 } 654 655 Object target = new Object() { 656 public void setPosition(int x, int y, int z) { 657 } 658 }; 659 final CountDownLatch endLatch = new CountDownLatch(1); 660 final ObjectAnimator anim = ObjectAnimator.ofMultiInt(target, "position", data); 661 anim.setInterpolator(null); 662 anim.setDuration(60); 663 anim.addListener(new AnimatorListenerAdapter() { 664 @Override 665 public void onAnimationEnd(Animator animation) { 666 endLatch.countDown(); 667 } 668 }); 669 670 anim.addUpdateListener((ValueAnimator animation) -> { 671 float fraction = animation.getAnimatedFraction(); 672 int[] values = (int[]) animation.getAnimatedValue(); 673 assertEquals(3, values.length); 674 675 int expectedX = Math.round(fraction * (data.length - 1)); 676 int expectedY = Math.round(fraction * (data.length - 1) * 2); 677 678 // Allow a delta of 1 for rounding errors. 679 assertEquals(expectedX, values[0], 1); 680 assertEquals(expectedY, values[1], 1); 681 assertEquals(0, values[2]); 682 }); 683 684 mActivityRule.runOnUiThread(anim::start); 685 assertTrue(endLatch.await(400, TimeUnit.MILLISECONDS)); 686 } 687 688 @Test 689 public void testOfObject_Converter() throws Throwable { 690 // Test for ObjectAnimator.ofObject(Object, String, TypeConverter<T, V>, Path) 691 // Create a path that contains two disconnected line segments. Check that the animated 692 // property x and property y always stay on the line segments. 693 Path path = new Path(); 694 path.moveTo(LINE1_START, -LINE1_START); 695 path.lineTo(LINE1_END, -LINE1_END); 696 path.moveTo(LINE2_START, LINE2_START); 697 path.lineTo(LINE2_END, LINE2_END); 698 699 Object target1 = new Object() { 700 public void setDistance(float distance) { 701 } 702 }; 703 Object target2 = new Object() { 704 public void setPosition(PointF pos) { 705 } 706 }; 707 TypeConverter<PointF, Float> converter = new TypeConverter<PointF, Float>( 708 PointF.class, Float.class) { 709 @Override 710 public Float convert(PointF value) { 711 return (float) Math.sqrt(value.x * value.x + value.y * value.y); 712 } 713 }; 714 final CountDownLatch endLatch = new CountDownLatch(2); 715 716 // Create two animators. One use a converter that converts the point to distance to origin. 717 // The other one does not have a type converter. 718 final ObjectAnimator anim1 = ObjectAnimator.ofObject(target1, "distance", converter, path); 719 anim1.setDuration(100); 720 anim1.setInterpolator(null); 721 anim1.addListener(new AnimatorListenerAdapter() { 722 @Override 723 public void onAnimationEnd(Animator animation) { 724 endLatch.countDown(); 725 } 726 }); 727 728 final ObjectAnimator anim2 = ObjectAnimator.ofObject(target2, "position", null, path); 729 anim2.setDuration(100); 730 anim2.setInterpolator(null); 731 anim2.addListener(new AnimatorListenerAdapter() { 732 @Override 733 public void onAnimationEnd(Animator animation) { 734 endLatch.countDown(); 735 } 736 }); 737 anim2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 738 // Set the initial value of the distance to the distance between the first point on 739 // the path to the origin. 740 float mLastDistance = (float) (32 * Math.sqrt(2)); 741 float mLastFraction = 0f; 742 @Override 743 public void onAnimationUpdate(ValueAnimator animation) { 744 float fraction = anim1.getAnimatedFraction(); 745 assertEquals(fraction, anim2.getAnimatedFraction(), 0.0f); 746 float distance = (Float) anim1.getAnimatedValue(); 747 PointF position = (PointF) anim2.getAnimatedValue(); 748 749 // Manually calculate the distance for the animator that doesn't have a 750 // TypeConverter, and expect the result to be the same as the animation value from 751 // the type converter. 752 float distanceFromPosition = (float) Math.sqrt( 753 position.x * position.x + position.y * position.y); 754 assertEquals(distance, distanceFromPosition, 0.0001f); 755 756 if (mLastFraction > 0.75) { 757 // In the 2nd line segment of the path, distance to origin should be increasing. 758 assertTrue(distance >= mLastDistance); 759 } else if (fraction < 0.75) { 760 assertTrue(distance <= mLastDistance); 761 } 762 mLastDistance = distance; 763 mLastFraction = fraction; 764 } 765 }); 766 767 mActivityRule.runOnUiThread(() -> { 768 anim1.start(); 769 anim2.start(); 770 }); 771 772 // Wait until both of the animations finish 773 assertTrue(endLatch.await(500, TimeUnit.MILLISECONDS)); 774 } 775 776 @Test 777 public void testIsStarted() throws Throwable { 778 Object object = mActivity.view.newBall; 779 String property = "y"; 780 float startY = mActivity.mStartY; 781 float endY = mActivity.mStartY + mActivity.mDeltaY; 782 Interpolator interpolator = new AccelerateInterpolator(); 783 ObjectAnimator objAnimator = ObjectAnimator.ofFloat(object, property, startY, endY); 784 objAnimator.setDuration(mDuration); 785 objAnimator.setRepeatCount(ValueAnimator.INFINITE); 786 objAnimator.setInterpolator(interpolator); 787 objAnimator.setRepeatMode(ValueAnimator.REVERSE); 788 startAnimation(objAnimator); 789 SystemClock.sleep(100); 790 assertTrue(objAnimator.isStarted()); 791 } 792 793 @Test 794 public void testSetStartEndValues() throws Throwable { 795 final float startValue = 100, endValue = 500; 796 final AnimTarget target = new AnimTarget(); 797 final ObjectAnimator anim1 = ObjectAnimator.ofFloat(target, "testValue", 0); 798 target.setTestValue(startValue); 799 anim1.setupStartValues(); 800 target.setTestValue(endValue); 801 anim1.setupEndValues(); 802 mActivityRule.runOnUiThread(() -> { 803 anim1.start(); 804 assertEquals(startValue, (float) anim1.getAnimatedValue(), 0.0f); 805 anim1.setCurrentFraction(1); 806 assertEquals(endValue, (float) anim1.getAnimatedValue(), 0.0f); 807 anim1.cancel(); 808 }); 809 810 final Property property = AnimTarget.TEST_VALUE; 811 final ObjectAnimator anim2 = ObjectAnimator.ofFloat(target, AnimTarget.TEST_VALUE, 0); 812 target.setTestValue(startValue); 813 final float startValueExpected = (Float) property.get(target); 814 anim2.setupStartValues(); 815 target.setTestValue(endValue); 816 final float endValueExpected = (Float) property.get(target); 817 anim2.setupEndValues(); 818 mActivityRule.runOnUiThread(() -> { 819 anim2.start(); 820 assertEquals(startValueExpected, (float) anim2.getAnimatedValue(), 0.0f); 821 anim2.setCurrentFraction(1); 822 assertEquals(endValueExpected, (float) anim2.getAnimatedValue(), 0.0f); 823 anim2.cancel(); 824 }); 825 826 // This is a test that ensures that the values set on a Property-based animator 827 // are determined by the property, not by the setter/getter of the target object 828 final Property doubler = AnimTarget.TEST_DOUBLING_VALUE; 829 final ObjectAnimator anim3 = ObjectAnimator.ofFloat(target, 830 doubler, 0); 831 target.setTestValue(startValue); 832 final float startValueExpected3 = (Float) doubler.get(target); 833 anim3.setupStartValues(); 834 target.setTestValue(endValue); 835 final float endValueExpected3 = (Float) doubler.get(target); 836 anim3.setupEndValues(); 837 mActivityRule.runOnUiThread(() -> { 838 anim3.start(); 839 assertEquals(startValueExpected3, (float) anim3.getAnimatedValue(), 0.0f); 840 anim3.setCurrentFraction(1); 841 assertEquals(endValueExpected3, (float) anim3.getAnimatedValue(), 0.0f); 842 anim3.cancel(); 843 }); 844 } 845 846 @Test 847 public void testCachedValues() throws Throwable { 848 final AnimTarget target = new AnimTarget(); 849 final ObjectAnimator anim = ObjectAnimator.ofFloat(target, "testValue", 100); 850 anim.setDuration(100); 851 final CountDownLatch twoFramesLatch = new CountDownLatch(2); 852 mActivityRule.runOnUiThread(() -> { 853 anim.start(); 854 final View decor = mActivity.getWindow().getDecorView(); 855 decor.postOnAnimation(new Runnable() { 856 @Override 857 public void run() { 858 if (twoFramesLatch.getCount() > 0) { 859 twoFramesLatch.countDown(); 860 if (twoFramesLatch.getCount() > 0) { 861 decor.postOnAnimation(this); 862 } 863 } 864 } 865 }); 866 }); 867 868 assertTrue("Animation didn't start in a reasonable time", 869 twoFramesLatch.await(800, TimeUnit.MILLISECONDS)); 870 871 mActivityRule.runOnUiThread(() -> { 872 assertTrue("Start value should readjust to current position", 873 target.getTestValue() != 0); 874 anim.cancel(); 875 anim.setupStartValues(); 876 anim.start(); 877 assertTrue("Start value should readjust to current position", 878 target.getTestValue() != 0); 879 anim.cancel(); 880 }); 881 } 882 883 static class AnimTarget { 884 private float mTestValue = 0; 885 886 public void setTestValue(float value) { 887 mTestValue = value; 888 } 889 890 public float getTestValue() { 891 return mTestValue; 892 } 893 894 public static final Property<AnimTarget, Float> TEST_VALUE = 895 new Property<AnimTarget, Float>(Float.class, "testValue") { 896 @Override 897 public void set(AnimTarget object, Float value) { 898 object.setTestValue(value); 899 } 900 901 @Override 902 public Float get(AnimTarget object) { 903 return object.getTestValue(); 904 } 905 }; 906 public static final Property<AnimTarget, Float> TEST_DOUBLING_VALUE = 907 new Property<AnimTarget, Float>(Float.class, "testValue") { 908 @Override 909 public void set(AnimTarget object, Float value) { 910 object.setTestValue(value); 911 } 912 913 @Override 914 public Float get(AnimTarget object) { 915 // purposely different from getTestValue, to verify that properties 916 // are independent of setters/getters 917 return object.getTestValue() * 2; 918 } 919 }; 920 } 921 922 private void startAnimation(final ObjectAnimator mObjectAnimator) throws Throwable { 923 mActivityRule.runOnUiThread(() -> mActivity.startAnimation(mObjectAnimator)); 924 } 925 926 private void startAnimation(final ObjectAnimator mObjectAnimator, final 927 ObjectAnimator colorAnimator) throws Throwable { 928 mActivityRule.runOnUiThread(() -> mActivity.startAnimation(mObjectAnimator, colorAnimator)); 929 } 930 } 931