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