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