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.support.test.InstrumentationRegistry; 42 import android.support.test.filters.MediumTest; 43 import android.support.test.rule.ActivityTestRule; 44 import android.support.test.runner.AndroidJUnit4; 45 import android.util.Property; 46 import android.view.View; 47 import android.view.animation.AccelerateInterpolator; 48 import android.view.animation.Interpolator; 49 50 import org.junit.Before; 51 import org.junit.Rule; 52 import org.junit.Test; 53 import org.junit.runner.RunWith; 54 55 import java.util.concurrent.CountDownLatch; 56 import java.util.concurrent.TimeUnit; 57 58 @MediumTest 59 @RunWith(AndroidJUnit4.class) 60 public class ObjectAnimatorTest { 61 private static final float LINE1_START = -32f; 62 private static final float LINE1_END = -2f; 63 private static final float LINE1_Y = 0f; 64 private static final float LINE2_START = 2f; 65 private static final float LINE2_END = 12f; 66 private static final float QUADRATIC_CTRL_PT1_X = 0f; 67 private static final float QUADRATIC_CTRL_PT1_Y = 0f; 68 private static final float QUADRATIC_CTRL_PT2_X = 50f; 69 private static final float QUADRATIC_CTRL_PT2_Y = 20f; 70 private static final float QUADRATIC_CTRL_PT3_X = 100f; 71 private static final float QUADRATIC_CTRL_PT3_Y = 0f; 72 private static final float EPSILON = .001f; 73 74 private Instrumentation mInstrumentation; 75 private AnimationActivity mActivity; 76 private ObjectAnimator mObjectAnimator; 77 private long mDuration = 1000; 78 79 @Rule 80 public ActivityTestRule<AnimationActivity> mActivityRule = 81 new ActivityTestRule<>(AnimationActivity.class); 82 83 @Before 84 public void setup() { 85 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 86 mInstrumentation.setInTouchMode(false); 87 mActivity = mActivityRule.getActivity(); 88 mObjectAnimator = (ObjectAnimator) mActivity.createAnimatorWithDuration(mDuration); 89 } 90 91 @Test 92 public void testDuration() throws Throwable { 93 final long duration = 2000; 94 ObjectAnimator objectAnimatorLocal = (ObjectAnimator) mActivity.createAnimatorWithDuration( 95 duration); 96 startAnimation(objectAnimatorLocal); 97 assertEquals(duration, objectAnimatorLocal.getDuration()); 98 } 99 100 @Test 101 public void testOfFloat() throws Throwable { 102 Object object = mActivity.view.newBall; 103 String property = "y"; 104 float startY = mActivity.mStartY; 105 float endY = mActivity.mStartY + mActivity.mDeltaY; 106 ObjectAnimator objAnimator = ObjectAnimator.ofFloat(object, property, startY, endY); 107 assertTrue(objAnimator != null); 108 109 ValueAnimator.AnimatorUpdateListener updateListener = ((animator) -> { 110 float y = (Float) animator.getAnimatedValue(); 111 assertTrue(y >= startY); 112 assertTrue(y <= endY); 113 }); 114 ValueAnimator.AnimatorUpdateListener mockListener = 115 mock(ValueAnimator.AnimatorUpdateListener.class); 116 objAnimator.addUpdateListener(mockListener); 117 objAnimator.addUpdateListener(updateListener); 118 objAnimator.setDuration(200); 119 objAnimator.setRepeatCount(ValueAnimator.INFINITE); 120 objAnimator.setInterpolator(new AccelerateInterpolator()); 121 objAnimator.setRepeatMode(ValueAnimator.REVERSE); 122 mActivityRule.runOnUiThread(objAnimator::start); 123 assertTrue(objAnimator != null); 124 125 verify(mockListener, timeout(2000).atLeast(20)).onAnimationUpdate(objAnimator); 126 mActivityRule.runOnUiThread(objAnimator::cancel); 127 } 128 129 @Test 130 public void testOfFloatBase() throws Throwable { 131 Object object = mActivity.view.newBall; 132 String property = "y"; 133 float startY = mActivity.mStartY; 134 float endY = mActivity.mStartY + mActivity.mDeltaY; 135 ObjectAnimator animator = ObjectAnimator.ofFloat(object, property, startY, endY); 136 ObjectAnimator objAnimator = new ObjectAnimator(); 137 objAnimator.setTarget(object); 138 objAnimator.setPropertyName(property); 139 assertEquals(animator.getTarget(), objAnimator.getTarget()); 140 assertEquals(animator.getPropertyName(), objAnimator.getPropertyName()); 141 } 142 143 @Test 144 public void testOfInt() throws Throwable { 145 Object object = mActivity.view.newBall; 146 String property = "scrollY"; 147 148 final ObjectAnimator intAnimator = ObjectAnimator.ofInt(object, property, 200, 0); 149 ValueAnimator.AnimatorUpdateListener updateListener = ((animator) -> { 150 int value = (Integer) intAnimator.getAnimatedValue(); 151 assertTrue(value <= 200); 152 assertTrue(value >= 0); 153 }); 154 final Animator.AnimatorListener mockListener = mock(Animator.AnimatorListener.class); 155 intAnimator.addListener(mockListener); 156 157 intAnimator.addUpdateListener(updateListener); 158 intAnimator.setDuration(200); 159 intAnimator.setRepeatCount(1); 160 intAnimator.setRepeatMode(ValueAnimator.REVERSE); 161 mActivityRule.runOnUiThread(intAnimator::start); 162 163 verify(mockListener, timeout(400)).onAnimationRepeat(intAnimator); 164 verify(mockListener, timeout(400)).onAnimationEnd(intAnimator, false); 165 } 166 167 @Test 168 public void testOfObject() throws Throwable { 169 Object object = mActivity.view.newBall; 170 String property = "backgroundColor"; 171 int startColor = 0xFFFF8080; 172 int endColor = 0xFF8080FF; 173 174 Object[] values = {new Integer(startColor), new Integer(endColor)}; 175 ArgbEvaluator evaluator = new ArgbEvaluator(); 176 final ObjectAnimator colorAnimator = ObjectAnimator.ofObject(object, property, 177 evaluator, values); 178 ValueAnimator.AnimatorUpdateListener updateListener = ((animator) -> { 179 int color = (Integer) colorAnimator.getAnimatedValue(); 180 // Check that channel is interpolated separately. 181 assertEquals(0xFF, Color.alpha(color)); 182 assertTrue(Color.red(color) <= Color.red(startColor)); 183 assertTrue(Color.red(color) >= Color.red(endColor)); 184 assertEquals(0x80, Color.green(color)); 185 assertTrue(Color.blue(color) >= Color.blue(startColor)); 186 assertTrue(Color.blue(color) <= Color.blue(endColor)); 187 }); 188 final Animator.AnimatorListener mockListener = mock(Animator.AnimatorListener.class); 189 colorAnimator.addListener(mockListener); 190 191 colorAnimator.addUpdateListener(updateListener); 192 colorAnimator.setDuration(200); 193 colorAnimator.setRepeatCount(1); 194 colorAnimator.setRepeatMode(ValueAnimator.REVERSE); 195 mActivityRule.runOnUiThread(colorAnimator::start); 196 197 verify(mockListener, timeout(400)).onAnimationRepeat(colorAnimator); 198 verify(mockListener, timeout(400)).onAnimationEnd(colorAnimator, false); 199 } 200 201 @Test 202 public void testOfPropertyValuesHolder() throws Throwable { 203 Object object = mActivity.view.newBall; 204 String propertyName = "scrollX"; 205 int startValue = 200; 206 int endValue = 0; 207 int[] values = {startValue, endValue}; 208 PropertyValuesHolder propertyValuesHolder = PropertyValuesHolder.ofInt(propertyName, values); 209 final ObjectAnimator intAnimator = ObjectAnimator.ofPropertyValuesHolder(object, 210 propertyValuesHolder); 211 212 ValueAnimator.AnimatorUpdateListener updateListener = ((animator) -> { 213 int value = (Integer) intAnimator.getAnimatedValue(); 214 // Check that each channel is interpolated separately. 215 assertTrue(value <= 200); 216 assertTrue(value >= 0); 217 }); 218 final Animator.AnimatorListener mockListener = mock(Animator.AnimatorListener.class); 219 intAnimator.addListener(mockListener); 220 221 intAnimator.addUpdateListener(updateListener); 222 intAnimator.setDuration(200); 223 mActivityRule.runOnUiThread(intAnimator::start); 224 225 verify(mockListener, timeout(400)).onAnimationEnd(intAnimator, false); 226 } 227 228 @Test 229 public void testOfArgb() throws Throwable { 230 Object object = mActivity.view; 231 String property = "backgroundColor"; 232 int start = 0xffff0000; 233 int end = 0xff0000ff; 234 int[] values = {start, end}; 235 int startRed = Color.red(start); 236 int startBlue = Color.blue(start); 237 int endRed = Color.red(end); 238 int endBlue = Color.blue(end); 239 240 ValueAnimator.AnimatorUpdateListener updateListener = ((anim) -> { 241 Integer animatedValue = (Integer) anim.getAnimatedValue(); 242 int alpha = Color.alpha(animatedValue); 243 int red = Color.red(animatedValue); 244 int green = Color.green(animatedValue); 245 int blue = Color.blue(animatedValue); 246 assertTrue(red <= startRed); 247 assertTrue(red >= endRed); 248 assertTrue(blue >= startBlue); 249 assertTrue(blue <= endBlue); 250 assertEquals(255, alpha); 251 assertEquals(0, green); 252 253 }); 254 255 final Animator.AnimatorListener mockListener = mock(Animator.AnimatorListener.class); 256 final ObjectAnimator animator = ObjectAnimator.ofArgb(object, property, start, end); 257 animator.setDuration(200); 258 animator.addListener(mockListener); 259 animator.addUpdateListener(updateListener); 260 261 mActivityRule.runOnUiThread(animator::start); 262 assertTrue(animator.isRunning()); 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(200, 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 SystemClock.sleep(100); 792 } 793 794 @Test 795 public void testSetStartEndValues() throws Throwable { 796 final float startValue = 100, endValue = 500; 797 final AnimTarget target = new AnimTarget(); 798 final ObjectAnimator anim1 = ObjectAnimator.ofFloat(target, "testValue", 0); 799 target.setTestValue(startValue); 800 anim1.setupStartValues(); 801 target.setTestValue(endValue); 802 anim1.setupEndValues(); 803 mActivityRule.runOnUiThread(() -> { 804 anim1.start(); 805 assertEquals(startValue, (float) anim1.getAnimatedValue(), 0.0f); 806 anim1.setCurrentFraction(1); 807 assertEquals(endValue, (float) anim1.getAnimatedValue(), 0.0f); 808 anim1.cancel(); 809 }); 810 811 final Property property = AnimTarget.TEST_VALUE; 812 final ObjectAnimator anim2 = ObjectAnimator.ofFloat(target, AnimTarget.TEST_VALUE, 0); 813 target.setTestValue(startValue); 814 final float startValueExpected = (Float) property.get(target); 815 anim2.setupStartValues(); 816 target.setTestValue(endValue); 817 final float endValueExpected = (Float) property.get(target); 818 anim2.setupEndValues(); 819 mActivityRule.runOnUiThread(() -> { 820 anim2.start(); 821 assertEquals(startValueExpected, (float) anim2.getAnimatedValue(), 0.0f); 822 anim2.setCurrentFraction(1); 823 assertEquals(endValueExpected, (float) anim2.getAnimatedValue(), 0.0f); 824 anim2.cancel(); 825 }); 826 827 // This is a test that ensures that the values set on a Property-based animator 828 // are determined by the property, not by the setter/getter of the target object 829 final Property doubler = AnimTarget.TEST_DOUBLING_VALUE; 830 final ObjectAnimator anim3 = ObjectAnimator.ofFloat(target, 831 doubler, 0); 832 target.setTestValue(startValue); 833 final float startValueExpected3 = (Float) doubler.get(target); 834 anim3.setupStartValues(); 835 target.setTestValue(endValue); 836 final float endValueExpected3 = (Float) doubler.get(target); 837 anim3.setupEndValues(); 838 mActivityRule.runOnUiThread(() -> { 839 anim3.start(); 840 assertEquals(startValueExpected3, (float) anim3.getAnimatedValue(), 0.0f); 841 anim3.setCurrentFraction(1); 842 assertEquals(endValueExpected3, (float) anim3.getAnimatedValue(), 0.0f); 843 anim3.cancel(); 844 }); 845 } 846 847 @Test 848 public void testCachedValues() throws Throwable { 849 final AnimTarget target = new AnimTarget(); 850 final ObjectAnimator anim = ObjectAnimator.ofFloat(target, "testValue", 100); 851 anim.setDuration(200); 852 final CountDownLatch twoFramesLatch = new CountDownLatch(2); 853 mActivityRule.runOnUiThread(() -> { 854 anim.start(); 855 final View decor = mActivity.getWindow().getDecorView(); 856 decor.postOnAnimation(new Runnable() { 857 @Override 858 public void run() { 859 if (twoFramesLatch.getCount() > 0) { 860 twoFramesLatch.countDown(); 861 decor.postOnAnimation(this); 862 } 863 } 864 }); 865 }); 866 867 assertTrue("Animation didn't start in a reasonable time", 868 twoFramesLatch.await(100, TimeUnit.MILLISECONDS)); 869 870 mActivityRule.runOnUiThread(() -> { 871 assertTrue("Start value should readjust to current position", 872 target.getTestValue() != 0); 873 anim.cancel(); 874 anim.setupStartValues(); 875 anim.start(); 876 assertTrue("Start value should readjust to current position", 877 target.getTestValue() != 0); 878 anim.cancel(); 879 }); 880 } 881 882 static class AnimTarget { 883 private float mTestValue = 0; 884 885 public void setTestValue(float value) { 886 mTestValue = value; 887 } 888 889 public float getTestValue() { 890 return mTestValue; 891 } 892 893 public static final Property<AnimTarget, Float> TEST_VALUE = 894 new Property<AnimTarget, Float>(Float.class, "testValue") { 895 @Override 896 public void set(AnimTarget object, Float value) { 897 object.setTestValue(value); 898 } 899 900 @Override 901 public Float get(AnimTarget object) { 902 return object.getTestValue(); 903 } 904 }; 905 public static final Property<AnimTarget, Float> TEST_DOUBLING_VALUE = 906 new Property<AnimTarget, Float>(Float.class, "testValue") { 907 @Override 908 public void set(AnimTarget object, Float value) { 909 object.setTestValue(value); 910 } 911 912 @Override 913 public Float get(AnimTarget object) { 914 // purposely different from getTestValue, to verify that properties 915 // are independent of setters/getters 916 return object.getTestValue() * 2; 917 } 918 }; 919 } 920 921 private void startAnimation(final ObjectAnimator mObjectAnimator) throws Throwable { 922 mActivityRule.runOnUiThread(() -> mActivity.startAnimation(mObjectAnimator)); 923 } 924 925 private void startAnimation(final ObjectAnimator mObjectAnimator, final 926 ObjectAnimator colorAnimator) throws Throwable { 927 mActivityRule.runOnUiThread(() -> mActivity.startAnimation(mObjectAnimator, colorAnimator)); 928 } 929 } 930