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 package android.animation.cts; 17 18 import static com.android.compatibility.common.util.CtsMockitoUtils.within; 19 20 import static org.junit.Assert.assertEquals; 21 import static org.junit.Assert.assertNotNull; 22 import static org.junit.Assert.assertTrue; 23 import static org.mockito.Mockito.mock; 24 import static org.mockito.Mockito.verify; 25 26 import android.animation.Animator; 27 import android.animation.AnimatorListenerAdapter; 28 import android.animation.ArgbEvaluator; 29 import android.animation.Keyframe; 30 import android.animation.ObjectAnimator; 31 import android.animation.PropertyValuesHolder; 32 import android.animation.TypeConverter; 33 import android.animation.ValueAnimator; 34 import android.app.Instrumentation; 35 import android.graphics.Color; 36 import android.graphics.Path; 37 import android.graphics.PointF; 38 import android.graphics.drawable.ShapeDrawable; 39 import android.os.SystemClock; 40 import android.platform.test.annotations.FlakyTest; 41 import android.util.FloatProperty; 42 import android.util.Property; 43 import android.view.View; 44 import android.view.animation.AccelerateInterpolator; 45 46 import androidx.test.InstrumentationRegistry; 47 import androidx.test.filters.LargeTest; 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 @LargeTest 60 @RunWith(AndroidJUnit4.class) 61 public class PropertyValuesHolderTest { 62 private static final float LINE1_START = -32f; 63 private static final float LINE1_END = -2f; 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 long mDuration = 1000; 77 private float mStartY; 78 private float mEndY; 79 private Object mObject; 80 private String mProperty; 81 82 @Rule 83 public ActivityTestRule<AnimationActivity> mActivityRule = 84 new ActivityTestRule<>(AnimationActivity.class); 85 86 @Before setup()87 public void setup() { 88 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 89 mInstrumentation.setInTouchMode(false); 90 mActivity = mActivityRule.getActivity(); 91 mProperty = "y"; 92 mStartY = mActivity.mStartY; 93 mEndY = mActivity.mStartY + mActivity.mDeltaY; 94 mObject = mActivity.view.newBall; 95 } 96 97 @Test testGetPropertyName()98 public void testGetPropertyName() { 99 float[] values = {mStartY, mEndY}; 100 PropertyValuesHolder pVHolder = PropertyValuesHolder.ofFloat(mProperty, values); 101 assertEquals(mProperty, pVHolder.getPropertyName()); 102 } 103 104 @Test testSetPropertyName()105 public void testSetPropertyName() { 106 float[] values = {mStartY, mEndY}; 107 PropertyValuesHolder pVHolder = PropertyValuesHolder.ofFloat("", values); 108 pVHolder.setPropertyName(mProperty); 109 assertEquals(mProperty, pVHolder.getPropertyName()); 110 } 111 112 @Test testClone()113 public void testClone() { 114 float[] values = {mStartY, mEndY}; 115 PropertyValuesHolder pVHolder = PropertyValuesHolder.ofFloat(mProperty, values); 116 PropertyValuesHolder cloneHolder = pVHolder.clone(); 117 assertEquals(pVHolder.getPropertyName(), cloneHolder.getPropertyName()); 118 } 119 120 @FlakyTest 121 @Test testSetValues()122 public void testSetValues() throws Throwable { 123 float[] dummyValues = {100, 150}; 124 float[] values = {mStartY, mEndY}; 125 PropertyValuesHolder pVHolder = PropertyValuesHolder.ofFloat(mProperty, dummyValues); 126 pVHolder.setFloatValues(values); 127 128 ObjectAnimator objAnimator = ObjectAnimator.ofPropertyValuesHolder(mObject,pVHolder); 129 assertTrue(objAnimator != null); 130 setAnimatorProperties(objAnimator); 131 132 startAnimation(objAnimator); 133 assertTrue(objAnimator != null); 134 float[] yArray = getYPosition(); 135 assertResults(yArray, mStartY, mEndY); 136 } 137 createAnimator(Keyframe... keyframes)138 private ObjectAnimator createAnimator(Keyframe... keyframes) { 139 PropertyValuesHolder pVHolder = PropertyValuesHolder.ofKeyframe(mProperty, keyframes); 140 ObjectAnimator objAnimator = ObjectAnimator.ofPropertyValuesHolder(mObject,pVHolder); 141 objAnimator.setDuration(mDuration); 142 objAnimator.setInterpolator(new AccelerateInterpolator()); 143 return objAnimator; 144 } 145 waitUntilFinished(ObjectAnimator objectAnimator, long timeoutMilliseconds)146 private void waitUntilFinished(ObjectAnimator objectAnimator, long timeoutMilliseconds) 147 throws InterruptedException { 148 final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class); 149 objectAnimator.addListener(listener); 150 verify(listener, within(timeoutMilliseconds)).onAnimationEnd(objectAnimator, false); 151 mInstrumentation.waitForIdleSync(); 152 } 153 setTarget(final Animator animator, final Object target)154 private void setTarget(final Animator animator, final Object target) throws Throwable { 155 mActivityRule.runOnUiThread(() -> animator.setTarget(target)); 156 } 157 startSingleAnimation(final Animator animator)158 private void startSingleAnimation(final Animator animator) throws Throwable { 159 mActivityRule.runOnUiThread(() -> mActivity.startSingleAnimation(animator)); 160 } 161 162 @FlakyTest 163 @Test testResetValues()164 public void testResetValues() throws Throwable { 165 final float initialY = mActivity.view.newBall.getY(); 166 Keyframe emptyKeyframe1 = Keyframe.ofFloat(.0f); 167 ObjectAnimator objAnimator1 = createAnimator(emptyKeyframe1, Keyframe.ofFloat(1f, 100f)); 168 startSingleAnimation(objAnimator1); 169 assertTrue("Keyframe should be assigned a value", emptyKeyframe1.hasValue()); 170 assertEquals("Keyframe should get the value from the target", 171 (float) emptyKeyframe1.getValue(), initialY, 0.0f); 172 waitUntilFinished(objAnimator1, mDuration * 2); 173 assertEquals(100f, mActivity.view.newBall.getY(), 0.0f); 174 startSingleAnimation(objAnimator1); 175 waitUntilFinished(objAnimator1, mDuration * 2); 176 177 // run another ObjectAnimator that will move the Y value to something else 178 Keyframe emptyKeyframe2 = Keyframe.ofFloat(.0f); 179 ObjectAnimator objAnimator2 = createAnimator(emptyKeyframe2, Keyframe.ofFloat(1f, 200f)); 180 startSingleAnimation(objAnimator2); 181 assertTrue("Keyframe should be assigned a value", emptyKeyframe2.hasValue()); 182 assertEquals("Keyframe should get the value from the target", 183 (float) emptyKeyframe2.getValue(), 100f, 0.0f); 184 waitUntilFinished(objAnimator2, mDuration * 2); 185 assertEquals(200f, mActivity.view.newBall.getY(), 0.0f); 186 187 // re-run first object animator. since its target did not change, it should have the same 188 // start value for kf1 189 startSingleAnimation(objAnimator1); 190 assertEquals((float) emptyKeyframe1.getValue(), initialY, 0.0f); 191 waitUntilFinished(objAnimator1, mDuration * 2); 192 193 Keyframe fullKeyframe = Keyframe.ofFloat(.0f, 333f); 194 ObjectAnimator objAnimator3 = createAnimator(fullKeyframe, Keyframe.ofFloat(1f, 500f)); 195 startSingleAnimation(objAnimator3); 196 assertEquals("When keyframe has value, should not be assigned from the target object", 197 (float) fullKeyframe.getValue(), 333f, 0.0f); 198 waitUntilFinished(objAnimator3, mDuration * 2); 199 200 // now, null out the target of the first animator 201 float updatedY = mActivity.view.newBall.getY(); 202 setTarget(objAnimator1, null); 203 startSingleAnimation(objAnimator1); 204 assertTrue("Keyframe should get a value", emptyKeyframe1.hasValue()); 205 assertEquals("Keyframe should get the updated Y value", 206 (float) emptyKeyframe1.getValue(), updatedY, 0.0f); 207 waitUntilFinished(objAnimator1, mDuration * 2); 208 assertEquals("Animation should run as expected", 100f, mActivity.view.newBall.getY(), 0.0f); 209 210 // now, reset the target of the fully defined animation. 211 setTarget(objAnimator3, null); 212 startSingleAnimation(objAnimator3); 213 assertEquals("When keyframe is fully defined, its value should not change when target is" 214 + " reset", (float) fullKeyframe.getValue(), 333f, 0.0f); 215 waitUntilFinished(objAnimator3, mDuration * 2); 216 217 // run the other one to change Y value 218 startSingleAnimation(objAnimator2); 219 waitUntilFinished(objAnimator2, mDuration * 2); 220 // now, set another target w/ the same View type. it should still reset 221 ShapeHolder view = new ShapeHolder(new ShapeDrawable()); 222 updatedY = mActivity.view.newBall.getY(); 223 setTarget(objAnimator1, view); 224 startSingleAnimation(objAnimator1); 225 assertTrue("Keyframe should get a value when target is set to another view of the same" 226 + " class", emptyKeyframe1.hasValue()); 227 assertEquals("Keyframe should get the updated Y value when target is set to another view" 228 + " of the same class", (float) emptyKeyframe1.getValue(), updatedY, 0.0f); 229 waitUntilFinished(objAnimator1, mDuration * 2); 230 assertEquals("Animation should run as expected", 100f, mActivity.view.newBall.getY(), 0.0f); 231 } 232 233 @FlakyTest 234 @Test testOfFloat()235 public void testOfFloat() throws Throwable { 236 float[] values = {mStartY, mEndY}; 237 PropertyValuesHolder pVHolder = PropertyValuesHolder.ofFloat(mProperty, values); 238 assertNotNull(pVHolder); 239 ObjectAnimator objAnimator = ObjectAnimator.ofPropertyValuesHolder(mObject,pVHolder); 240 assertTrue(objAnimator != null); 241 242 setAnimatorProperties(objAnimator); 243 startAnimation(objAnimator); 244 assertTrue(objAnimator != null); 245 float[] yArray = getYPosition(); 246 assertResults(yArray, mStartY, mEndY); 247 } 248 249 @FlakyTest 250 @Test testOfFloat_Property()251 public void testOfFloat_Property() throws Throwable { 252 float[] values = {mStartY, mEndY}; 253 ShapeHolderYProperty property=new ShapeHolderYProperty(ShapeHolder.class,"y"); 254 property.setObject(mObject); 255 PropertyValuesHolder pVHolder = PropertyValuesHolder.ofFloat(property, values); 256 assertNotNull(pVHolder); 257 ObjectAnimator objAnimator = ObjectAnimator.ofPropertyValuesHolder(mObject,pVHolder); 258 assertTrue(objAnimator != null); 259 260 setAnimatorProperties(objAnimator); 261 startAnimation(objAnimator); 262 assertTrue(objAnimator != null); 263 float[] yArray = getYPosition(); 264 assertResults(yArray, mStartY, mEndY); 265 } 266 267 @FlakyTest 268 @Test testOfInt()269 public void testOfInt() throws Throwable { 270 int start = 0; 271 int end = 10; 272 int[] values = {start, end}; 273 PropertyValuesHolder pVHolder = PropertyValuesHolder.ofInt(mProperty, values); 274 assertNotNull(pVHolder); 275 final ObjectAnimator objAnimator = ObjectAnimator.ofPropertyValuesHolder(mObject,pVHolder); 276 assertTrue(objAnimator != null); 277 setAnimatorProperties(objAnimator); 278 mActivityRule.runOnUiThread(objAnimator::start); 279 SystemClock.sleep(2000); 280 assertTrue(objAnimator.isRunning()); 281 Integer animatedValue = (Integer) objAnimator.getAnimatedValue(); 282 assertTrue(animatedValue >= start); 283 assertTrue(animatedValue <= end); 284 } 285 286 @Test testOfInt_Property()287 public void testOfInt_Property() throws Throwable{ 288 Object object = mActivity.view; 289 String property = "backgroundColor"; 290 int startColor = mActivity.view.RED; 291 int endColor = mActivity.view.BLUE; 292 int values[] = {startColor, endColor}; 293 294 ViewColorProperty colorProperty=new ViewColorProperty(Integer.class,property); 295 colorProperty.setObject(object); 296 PropertyValuesHolder pVHolder = PropertyValuesHolder.ofInt(colorProperty, values); 297 assertNotNull(pVHolder); 298 299 ObjectAnimator colorAnimator = ObjectAnimator.ofPropertyValuesHolder(object,pVHolder); 300 colorAnimator.setDuration(1000); 301 colorAnimator.setEvaluator(new ArgbEvaluator()); 302 colorAnimator.setRepeatCount(ValueAnimator.INFINITE); 303 colorAnimator.setRepeatMode(ValueAnimator.REVERSE); 304 305 ObjectAnimator objectAnimator = (ObjectAnimator) mActivity.createAnimatorWithDuration( 306 mDuration); 307 startAnimation(objectAnimator, colorAnimator); 308 SystemClock.sleep(1000); 309 Integer animatedValue = (Integer) colorAnimator.getAnimatedValue(); 310 int redMin = Math.min(Color.red(startColor), Color.red(endColor)); 311 int redMax = Math.max(Color.red(startColor), Color.red(endColor)); 312 int blueMin = Math.min(Color.blue(startColor), Color.blue(endColor)); 313 int blueMax = Math.max(Color.blue(startColor), Color.blue(endColor)); 314 assertTrue(Color.red(animatedValue) >= redMin); 315 assertTrue(Color.red(animatedValue) <= redMax); 316 assertTrue(Color.blue(animatedValue) >= blueMin); 317 assertTrue(Color.blue(animatedValue) <= blueMax); 318 } 319 320 @Test testOfMultiFloat_Path()321 public void testOfMultiFloat_Path() throws Throwable { 322 // Test for PropertyValuesHolder.ofMultiFloat(String, Path); 323 // Create a quadratic bezier curve that are symmetric about the vertical line (x = 50). 324 // Expect when fraction < 0.5, x < 50, otherwise, x >= 50. 325 Path path = new Path(); 326 path.moveTo(QUADRATIC_CTRL_PT1_X, QUADRATIC_CTRL_PT1_Y); 327 path.quadTo(QUADRATIC_CTRL_PT2_X, QUADRATIC_CTRL_PT2_Y, 328 QUADRATIC_CTRL_PT3_X, QUADRATIC_CTRL_PT3_Y); 329 330 PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiFloat("position", path); 331 final ValueAnimator anim = ValueAnimator.ofPropertyValuesHolder(pvh); 332 333 // Linear interpolator 334 anim.setInterpolator(null); 335 anim.setDuration(200); 336 337 anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 338 float lastFraction = 0; 339 float lastX = 0; 340 float lastY = 0; 341 @Override 342 public void onAnimationUpdate(ValueAnimator animation) { 343 float[] values = (float[]) animation.getAnimatedValue(); 344 assertEquals(2, values.length); 345 float x = values[0]; 346 float y = values[1]; 347 float fraction = animation.getAnimatedFraction(); 348 // Given that the curve is symmetric about the line (x = 50), x should be less than 349 // 50 for half of the animation duration. 350 if (fraction < 0.5) { 351 assertTrue(x < QUADRATIC_CTRL_PT2_X); 352 } else { 353 assertTrue(x >= QUADRATIC_CTRL_PT2_X); 354 } 355 356 if (lastFraction > 0.5) { 357 // x should be increasing, y should be decreasing 358 assertTrue(x >= lastX); 359 assertTrue(y <= lastY); 360 } else if (fraction <= 0.5) { 361 // when fraction <= 0.5, both x, y should be increasing 362 assertTrue(x >= lastX); 363 assertTrue(y >= lastY); 364 } 365 lastX = x; 366 lastY = y; 367 lastFraction = fraction; 368 } 369 }); 370 final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class); 371 anim.addListener(listener); 372 mActivityRule.runOnUiThread(anim::start); 373 verify(listener, within(400)).onAnimationEnd(anim, false); 374 } 375 376 @Test testOfMultiFloat_Array()377 public void testOfMultiFloat_Array() throws Throwable { 378 // Test for PropertyValuesHolder.ofMultiFloat(String, float[][]); 379 final float[][] data = new float[10][]; 380 for (int i = 0; i < data.length; i++) { 381 data[i] = new float[3]; 382 data[i][0] = i; 383 data[i][1] = i * 2; 384 data[i][2] = 0f; 385 } 386 final CountDownLatch endLatch = new CountDownLatch(1); 387 final PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiFloat("position", data); 388 389 final ValueAnimator anim = ValueAnimator.ofPropertyValuesHolder(pvh); 390 anim.setInterpolator(null); 391 anim.setDuration(60); 392 anim.addListener(new AnimatorListenerAdapter() { 393 @Override 394 public void onAnimationEnd(Animator animation) { 395 endLatch.countDown(); 396 } 397 }); 398 399 anim.addUpdateListener((ValueAnimator animation) -> { 400 float fraction = animation.getAnimatedFraction(); 401 float[] values = (float[]) animation.getAnimatedValue(); 402 assertEquals(3, values.length); 403 404 float expectedX = fraction * (data.length - 1); 405 406 assertEquals(expectedX, values[0], EPSILON); 407 assertEquals(expectedX * 2, values[1], EPSILON); 408 assertEquals(0.0f, values[2], 0.0f); 409 }); 410 411 mActivityRule.runOnUiThread(anim::start); 412 assertTrue(endLatch.await(200, TimeUnit.MILLISECONDS)); 413 } 414 415 @Test testOfMultiInt_Path()416 public void testOfMultiInt_Path() throws Throwable { 417 // Test for PropertyValuesHolder.ofMultiInt(String, Path); 418 // Create a quadratic bezier curve that are symmetric about the vertical line (x = 50). 419 // Expect when fraction < 0.5, x < 50, otherwise, x >= 50. 420 Path path = new Path(); 421 path.moveTo(QUADRATIC_CTRL_PT1_X, QUADRATIC_CTRL_PT1_Y); 422 path.quadTo(QUADRATIC_CTRL_PT2_X, QUADRATIC_CTRL_PT2_Y, 423 QUADRATIC_CTRL_PT3_X, QUADRATIC_CTRL_PT3_Y); 424 425 final PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiInt("position", path); 426 final ValueAnimator anim = ValueAnimator.ofPropertyValuesHolder(pvh); 427 // Linear interpolator 428 anim.setInterpolator(null); 429 anim.setDuration(200); 430 431 anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 432 float lastFraction = 0; 433 int lastX = 0; 434 int lastY = 0; 435 @Override 436 public void onAnimationUpdate(ValueAnimator animation) { 437 int[] values = (int[]) animation.getAnimatedValue(); 438 assertEquals(2, values.length); 439 int x = values[0]; 440 int y = values[1]; 441 float fraction = animation.getAnimatedFraction(); 442 // Given that the curve is symmetric about the line (x = 50), x should be less than 443 // 50 for half of the animation duration. 444 if (fraction < 0.5) { 445 assertTrue(x < QUADRATIC_CTRL_PT2_X); 446 } else { 447 assertTrue(x >= QUADRATIC_CTRL_PT2_X); 448 } 449 450 if (lastFraction > 0.5) { 451 // x should be increasing, y should be decreasing 452 assertTrue(x >= lastX); 453 assertTrue(y <= lastY); 454 } else if (fraction <= 0.5) { 455 // when fraction <= 0.5, both x, y should be increasing 456 assertTrue(x >= lastX); 457 assertTrue(y >= lastY); 458 } 459 lastX = x; 460 lastY = y; 461 lastFraction = fraction; 462 } 463 }); 464 final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class); 465 anim.addListener(listener); 466 mActivityRule.runOnUiThread(anim::start); 467 verify(listener, within(400)).onAnimationEnd(anim, false); 468 } 469 470 @Test testOfMultiInt_Array()471 public void testOfMultiInt_Array() throws Throwable { 472 // Test for PropertyValuesHolder.ofMultiFloat(String, int[][]); 473 final int[][] data = new int[10][]; 474 for (int i = 0; i < data.length; i++) { 475 data[i] = new int[3]; 476 data[i][0] = i; 477 data[i][1] = i * 2; 478 data[i][2] = 0; 479 } 480 481 final CountDownLatch endLatch = new CountDownLatch(1); 482 final PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiInt("position", data); 483 final ValueAnimator anim = ValueAnimator.ofPropertyValuesHolder(pvh); 484 anim.setInterpolator(null); 485 anim.setDuration(60); 486 anim.addListener(new AnimatorListenerAdapter() { 487 @Override 488 public void onAnimationEnd(Animator animation) { 489 endLatch.countDown(); 490 } 491 }); 492 493 anim.addUpdateListener((ValueAnimator animation) -> { 494 float fraction = animation.getAnimatedFraction(); 495 int[] values = (int[]) animation.getAnimatedValue(); 496 assertEquals(3, values.length); 497 498 int expectedX = Math.round(fraction * (data.length - 1)); 499 int expectedY = Math.round(fraction * (data.length - 1) * 2); 500 501 // Allow a delta of 1 for rounding errors. 502 assertEquals(expectedX, values[0], 1); 503 assertEquals(expectedY, values[1], 1); 504 assertEquals(0, values[2]); 505 }); 506 507 mActivityRule.runOnUiThread(anim::start); 508 assertTrue(endLatch.await(200, TimeUnit.MILLISECONDS)); 509 } 510 511 @Test testOfObject_Converter()512 public void testOfObject_Converter() throws Throwable { 513 // Test for PropertyValuesHolder.ofObject(String, TypeConverter<T, V>, Path) 514 // and for PropertyValuesHolder.ofObject(Property, TypeConverter<T, V>, Path) 515 // Create a path that contains two disconnected line segments. Check that the animated 516 // property x and property y always stay on the line segments. 517 Path path = new Path(); 518 path.moveTo(LINE1_START, -LINE1_START); 519 path.lineTo(LINE1_END, -LINE1_END); 520 path.moveTo(LINE2_START, LINE2_START); 521 path.lineTo(LINE2_END, LINE2_END); 522 TypeConverter<PointF, Float> converter = new TypeConverter<PointF, Float>( 523 PointF.class, Float.class) { 524 @Override 525 public Float convert(PointF value) { 526 return (float) Math.sqrt(value.x * value.x + value.y * value.y); 527 } 528 }; 529 final CountDownLatch endLatch = new CountDownLatch(3); 530 531 // Create three animators. The first one use a converter that converts the point to distance 532 // to origin. The second one does not have a type converter. The third animator uses a 533 // converter to changes sign of the x, y value of the input pointF. 534 FloatProperty property = new FloatProperty("distance") { 535 @Override 536 public void setValue(Object object, float value) { 537 } 538 539 @Override 540 public Object get(Object object) { 541 return null; 542 } 543 }; 544 final PropertyValuesHolder pvh1 = 545 PropertyValuesHolder.ofObject(property, converter, path); 546 final ValueAnimator anim1 = ValueAnimator.ofPropertyValuesHolder(pvh1); 547 anim1.setDuration(100); 548 anim1.setInterpolator(null); 549 anim1.addListener(new AnimatorListenerAdapter() { 550 @Override 551 public void onAnimationEnd(Animator animation) { 552 endLatch.countDown(); 553 } 554 }); 555 556 final PropertyValuesHolder pvh2 = 557 PropertyValuesHolder.ofObject("position", null, path); 558 final ValueAnimator anim2 = ValueAnimator.ofPropertyValuesHolder(pvh2); 559 anim2.setDuration(100); 560 anim2.setInterpolator(null); 561 anim2.addListener(new AnimatorListenerAdapter() { 562 @Override 563 public void onAnimationEnd(Animator animation) { 564 endLatch.countDown(); 565 } 566 }); 567 568 TypeConverter<PointF, PointF> converter3 = new TypeConverter<PointF, PointF>( 569 PointF.class, PointF.class) { 570 PointF mValue = new PointF(); 571 @Override 572 public PointF convert(PointF value) { 573 mValue.x = -value.x; 574 mValue.y = -value.y; 575 return mValue; 576 } 577 }; 578 final PropertyValuesHolder pvh3 = 579 PropertyValuesHolder.ofObject("position", converter3, path); 580 final ValueAnimator anim3 = ValueAnimator.ofPropertyValuesHolder(pvh3); 581 anim3.setDuration(100); 582 anim3.setInterpolator(null); 583 anim3.addListener(new AnimatorListenerAdapter() { 584 @Override 585 public void onAnimationEnd(Animator animation) { 586 endLatch.countDown(); 587 } 588 }); 589 590 anim3.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 591 // Set the initial value of the distance to the distance between the first point on 592 // the path to the origin. 593 float mLastDistance = (float) (32 * Math.sqrt(2)); 594 float mLastFraction = 0f; 595 @Override 596 public void onAnimationUpdate(ValueAnimator animation) { 597 float fraction = anim1.getAnimatedFraction(); 598 assertEquals(fraction, anim2.getAnimatedFraction(), 0.0f); 599 assertEquals(fraction, anim3.getAnimatedFraction(), 0.0f); 600 float distance = (Float) anim1.getAnimatedValue(); 601 PointF position = (PointF) anim2.getAnimatedValue(); 602 PointF positionReverseSign = (PointF) anim3.getAnimatedValue(); 603 assertEquals(position.x, -positionReverseSign.x, 0.0f); 604 assertEquals(position.y, -positionReverseSign.y, 0.0f); 605 606 // Manually calculate the distance for the animator that doesn't have a 607 // TypeConverter, and expect the result to be the same as the animation value from 608 // the type converter. 609 float distanceFromPosition = (float) Math.sqrt( 610 position.x * position.x + position.y * position.y); 611 assertEquals(distance, distanceFromPosition, 0.0001f); 612 613 if (mLastFraction > 0.75) { 614 // In the 2nd line segment of the path, distance to origin should be increasing. 615 assertTrue(distance >= mLastDistance); 616 } else if (fraction < 0.75) { 617 assertTrue(distance <= mLastDistance); 618 } 619 mLastDistance = distance; 620 mLastFraction = fraction; 621 } 622 }); 623 624 mActivityRule.runOnUiThread(() -> { 625 anim1.start(); 626 anim2.start(); 627 anim3.start(); 628 }); 629 630 // Wait until both of the animations finish 631 assertTrue(endLatch.await(200, TimeUnit.MILLISECONDS)); 632 } 633 634 @Test testSetConverter()635 public void testSetConverter() throws Throwable { 636 // Test for PropertyValuesHolder.setConverter() 637 PropertyValuesHolder pvh = PropertyValuesHolder.ofObject("", null, 0f, 1f); 638 // Reverse the sign of the float in the converter, and use that value as the new type 639 // PointF's x value. 640 pvh.setConverter(new TypeConverter<Float, PointF>(Float.class, PointF.class) { 641 PointF mValue = new PointF(); 642 @Override 643 public PointF convert(Float value) { 644 mValue.x = value * (-1f); 645 mValue.y = 0f; 646 return mValue; 647 } 648 }); 649 final CountDownLatch endLatch = new CountDownLatch(2); 650 651 final ValueAnimator anim1 = ValueAnimator.ofPropertyValuesHolder(pvh); 652 anim1.setInterpolator(null); 653 anim1.setDuration(100); 654 anim1.addListener(new AnimatorListenerAdapter() { 655 @Override 656 public void onAnimationEnd(Animator animation) { 657 endLatch.countDown(); 658 } 659 }); 660 661 final ValueAnimator anim2 = ValueAnimator.ofFloat(0f, 1f); 662 anim2.setInterpolator(null); 663 anim2.addUpdateListener((ValueAnimator animation) -> { 664 assertEquals(anim1.getAnimatedFraction(), anim2.getAnimatedFraction(), 0.0f); 665 // Check that the pvh with type converter did reverse the sign of float, and set 666 // the x value of the PointF with it. 667 PointF value1 = (PointF) anim1.getAnimatedValue(); 668 float value2 = (Float) anim2.getAnimatedValue(); 669 assertEquals(value2, -value1.x, 0.0f); 670 assertEquals(0f, value1.y, 0.0f); 671 }); 672 anim2.setDuration(100); 673 anim2.addListener(new AnimatorListenerAdapter() { 674 @Override 675 public void onAnimationEnd(Animator animation) { 676 endLatch.countDown(); 677 } 678 }); 679 680 mActivityRule.runOnUiThread(() -> { 681 anim1.start(); 682 anim2.start(); 683 }); 684 685 // Wait until both of the animations finish 686 assertTrue(endLatch.await(200, TimeUnit.MILLISECONDS)); 687 } 688 689 @FlakyTest 690 @Test testSetProperty()691 public void testSetProperty() throws Throwable { 692 float[] values = {mStartY, mEndY}; 693 ShapeHolderYProperty property=new ShapeHolderYProperty(ShapeHolder.class,"y"); 694 property.setObject(mObject); 695 PropertyValuesHolder pVHolder = PropertyValuesHolder.ofFloat("", values); 696 pVHolder.setProperty(property); 697 ObjectAnimator objAnimator = ObjectAnimator.ofPropertyValuesHolder(mObject,pVHolder); 698 setAnimatorProperties(objAnimator); 699 startAnimation(objAnimator); 700 assertTrue(objAnimator != null); 701 float[] yArray = getYPosition(); 702 assertResults(yArray, mStartY, mEndY); 703 } 704 705 class ShapeHolderYProperty extends Property { 706 private ShapeHolder shapeHolder ; 707 private Class type = Float.class; 708 private String name = "y"; 709 @SuppressWarnings("unchecked") ShapeHolderYProperty(Class type, String name)710 public ShapeHolderYProperty(Class type, String name) throws Exception { 711 super(Float.class, name ); 712 if(!( type.equals(this.type) || ( name.equals(this.name))) ){ 713 throw new Exception("Type or name provided does not match with " + 714 this.type.getName() + " or " + this.name); 715 } 716 } 717 setObject(Object object)718 public void setObject(Object object){ 719 shapeHolder = (ShapeHolder) object; 720 } 721 722 @Override get(Object object)723 public Object get(Object object) { 724 return shapeHolder; 725 } 726 727 @Override getName()728 public String getName() { 729 return "y"; 730 } 731 732 @Override getType()733 public Class getType() { 734 return super.getType(); 735 } 736 737 @Override isReadOnly()738 public boolean isReadOnly() { 739 return false; 740 } 741 742 @Override set(Object object, Object value)743 public void set(Object object, Object value) { 744 shapeHolder.setY((Float)value); 745 } 746 747 } 748 749 class ViewColorProperty extends Property { 750 private View view ; 751 private Class type = Integer.class; 752 private String name = "backgroundColor"; 753 @SuppressWarnings("unchecked") ViewColorProperty(Class type, String name)754 public ViewColorProperty(Class type, String name) throws Exception { 755 super(Integer.class, name ); 756 if(!( type.equals(this.type) || ( name.equals(this.name))) ){ 757 throw new Exception("Type or name provided does not match with " + 758 this.type.getName() + " or " + this.name); 759 } 760 } 761 setObject(Object object)762 public void setObject(Object object){ 763 view = (View) object; 764 } 765 766 @Override get(Object object)767 public Object get(Object object) { 768 return view; 769 } 770 771 @Override getName()772 public String getName() { 773 return name; 774 } 775 776 @Override getType()777 public Class getType() { 778 return super.getType(); 779 } 780 781 @Override isReadOnly()782 public boolean isReadOnly() { 783 return false; 784 } 785 786 @Override set(Object object, Object value)787 public void set(Object object, Object value) { 788 view.setBackgroundColor((Integer)value); 789 } 790 } 791 setAnimatorProperties(ObjectAnimator objAnimator)792 private void setAnimatorProperties(ObjectAnimator objAnimator) { 793 objAnimator.setDuration(5000); 794 objAnimator.setRepeatCount(ValueAnimator.INFINITE); 795 objAnimator.setInterpolator(new AccelerateInterpolator()); 796 objAnimator.setRepeatMode(ValueAnimator.REVERSE); 797 } 798 getYPosition()799 public float[] getYPosition() throws Throwable{ 800 float[] yArray = new float[3]; 801 for(int i = 0; i < 3; i++) { 802 float y = mActivity.view.newBall.getY(); 803 yArray[i] = y; 804 SystemClock.sleep(1300); 805 } 806 return yArray; 807 } 808 assertResults(float[] yArray,float startY, float endY)809 public void assertResults(float[] yArray,float startY, float endY) { 810 for(int i = 0; i < 3; i++){ 811 float y = yArray[i]; 812 assertTrue(y >= startY); 813 assertTrue(y <= endY); 814 if(i < 2) { 815 float yNext = yArray[i+1]; 816 assertTrue(y != yNext); 817 } 818 } 819 } 820 startAnimation(final Animator animator)821 private void startAnimation(final Animator animator) throws Throwable { 822 mActivityRule.runOnUiThread(() -> mActivity.startAnimation(animator)); 823 } 824 startAnimation(final ObjectAnimator mObjectAnimator, final ObjectAnimator colorAnimator)825 private void startAnimation(final ObjectAnimator mObjectAnimator, 826 final ObjectAnimator colorAnimator) throws Throwable { 827 mActivityRule.runOnUiThread(() -> mActivity.startAnimation(mObjectAnimator, colorAnimator)); 828 } 829 } 830 831