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