/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.support.dynamicanimation.tests; import static junit.framework.Assert.fail; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.AdditionalMatchers.lt; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyFloat; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atMost; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import android.os.SystemClock; import android.support.animation.DynamicAnimation; import android.support.animation.FloatPropertyCompat; import android.support.animation.FloatValueHolder; import android.support.animation.SpringAnimation; import android.support.animation.SpringForce; import android.support.dynamicanimation.test.R; import android.support.test.InstrumentationRegistry; import android.support.test.filters.MediumTest; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; import android.support.v4.view.ViewCompat; import android.util.AndroidRuntimeException; import android.view.View; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; @MediumTest @RunWith(AndroidJUnit4.class) public class SpringTests { @Rule public final ActivityTestRule mActivityTestRule; public View mView1; public View mView2; @Rule public ExpectedException mExpectedException = ExpectedException.none(); public SpringTests() { mActivityTestRule = new ActivityTestRule<>(AnimationActivity.class); } @Before public void setup() throws Exception { mView1 = mActivityTestRule.getActivity().findViewById(R.id.anim_view); mView2 = mActivityTestRule.getActivity().findViewById(R.id.anim_another_view); } /** * Test that custom properties are supported. */ @Test public void testCustomProperties() { final Object animObj = new Object(); FloatPropertyCompat property = new FloatPropertyCompat("") { private float mValue = 0f; @Override public float getValue(Object object) { assertEquals(animObj, object); return mValue; } @Override public void setValue(Object object, float value) { assertEquals(animObj, object); assertTrue(value >= mValue); mValue = value; } }; final SpringAnimation anim = new SpringAnimation(animObj, property, 1f); DynamicAnimation.OnAnimationEndListener listener = mock( DynamicAnimation.OnAnimationEndListener.class); anim.addEndListener(listener); InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { anim.start(); } }); verify(listener, timeout(1000)).onAnimationEnd(anim, false, 1f, 0f); assertEquals(1f, property.getValue(animObj), 0f); } /** * Test that spring animation can work with a single property without an object. */ @Test public void testFloatValueHolder() { final FloatValueHolder floatValueHolder = new FloatValueHolder(0f); DynamicAnimation.OnAnimationUpdateListener updateListener = new DynamicAnimation.OnAnimationUpdateListener() { private float mLastValue = 0f; @Override public void onAnimationUpdate(DynamicAnimation animation, float value, float velocity) { // New value >= value from last frame assertTrue(value >= mLastValue); mLastValue = value; assertEquals(value, floatValueHolder.getValue(), 0f); } }; DynamicAnimation.OnAnimationUpdateListener mockListener = mock(DynamicAnimation.OnAnimationUpdateListener.class); final SpringAnimation anim = new SpringAnimation(floatValueHolder) .addUpdateListener(updateListener).addUpdateListener(mockListener); anim.setSpring(new SpringForce(1000).setDampingRatio(1.2f)); DynamicAnimation.OnAnimationEndListener listener = mock( DynamicAnimation.OnAnimationEndListener.class); anim.addEndListener(listener); InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { anim.setStartValue(0).start(); } }); verify(mockListener, timeout(1000).atLeast(10)).onAnimationUpdate(eq(anim), lt(1000f), any(float.class)); verify(listener, timeout(1000)).onAnimationEnd(anim, false, 1000f, 0f); } /** * Check the final position of the default spring against what's being set through the * constructor. */ @Test public void testGetFinalPosition() { SpringAnimation animation = new SpringAnimation(mView1, DynamicAnimation.TRANSLATION_X, 20); assertEquals(20, animation.getSpring().getFinalPosition(), 0); SpringForce spring = new SpringForce(); spring.setFinalPosition(25.0f); assertEquals(25.0f, spring.getFinalPosition(), 0.0f); } /** * Verify that for over-damped springs, the higher the damping ratio, the slower it is. Also * verify that critically damped springs finish faster than overdamped springs. */ @Test public void testDampingRatioOverAndCriticallyDamped() { // Compare overdamped springs final SpringAnimation anim1 = new SpringAnimation(mView1, DynamicAnimation.X, 0); final SpringAnimation anim2 = new SpringAnimation(mView2, DynamicAnimation.Y, 0); final SpringAnimation anim3 = new SpringAnimation(mView2, DynamicAnimation.Z, 0); final DynamicAnimation.OnAnimationUpdateListener updateListener = new DynamicAnimation.OnAnimationUpdateListener() { public float position1 = 1000; public float position2 = 1000; public float position3 = 1000; @Override public void onAnimationUpdate(DynamicAnimation animation, float value, float velocity) { if (animation == anim1) { position1 = value; if (position1 == 800) { // first frame assertEquals(position1, position2, 0); assertEquals(position1, position3, 0); } else { assertTrue(position2 > position1); assertTrue(position3 > position2); assertTrue(800 > position3); } } else if (animation == anim2) { position2 = value; } else { position3 = value; } } }; final MyEndListener l1 = new MyEndListener(); final MyEndListener l2 = new MyEndListener(); final MyEndListener l3 = new MyEndListener(); final DynamicAnimation.OnAnimationEndListener mockListener = mock(DynamicAnimation.OnAnimationEndListener.class); anim1.getSpring().setStiffness(SpringForce.STIFFNESS_HIGH).setDampingRatio(1f); anim2.getSpring().setStiffness(SpringForce.STIFFNESS_HIGH).setDampingRatio(1.5f); anim3.getSpring().setStiffness(SpringForce.STIFFNESS_HIGH).setDampingRatio(2.0f); InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { anim2.setStartValue(800).addUpdateListener(updateListener).addEndListener(l2) .start(); anim3.setStartValue(800).addUpdateListener(updateListener).addEndListener(l3) .addEndListener(mockListener).start(); anim1.setStartValue(800).addUpdateListener(updateListener).addEndListener(l1) .start(); } }); // The spring animation with critically-damped spring should return to rest position faster. verify(mockListener, timeout(2000)).onAnimationEnd(anim3, false, 0, 0); assertTrue(l1.endTime > 0); assertTrue(l2.endTime > l1.endTime); assertTrue(l3.endTime > l2.endTime); } /** * Verify that more underdamped springs are bouncier, and that critically damped springs finish * faster than underdamped springs. */ @Test public void testDampingRatioUnderDamped() { final SpringAnimation anim1 = new SpringAnimation(mView1, DynamicAnimation.ROTATION, 0); final SpringAnimation anim2 = new SpringAnimation(mView2, DynamicAnimation.ROTATION_X, 0); final SpringAnimation anim3 = new SpringAnimation(mView2, DynamicAnimation.ROTATION_Y, 0); final DynamicAnimation.OnAnimationUpdateListener updateListener = new DynamicAnimation.OnAnimationUpdateListener() { public float bounceCount1 = 0; public float bounceCount2 = 0; public float velocity1 = 0; public float velocity2 = 0; @Override public void onAnimationUpdate(DynamicAnimation animation, float value, float velocity) { if (animation == anim1) { if (velocity > 0 && velocity1 < 0) { bounceCount1++; } velocity1 = velocity; } else if (animation == anim2) { velocity2 = velocity; if (velocity > 0 && velocity2 < 0) { bounceCount2++; assertTrue(bounceCount1 > bounceCount2); } } } }; final MyEndListener l1 = new MyEndListener(); final MyEndListener l2 = new MyEndListener(); final MyEndListener l3 = new MyEndListener(); final DynamicAnimation.OnAnimationEndListener mockListener = mock(DynamicAnimation.OnAnimationEndListener.class); anim1.getSpring().setStiffness(SpringForce.STIFFNESS_MEDIUM).setDampingRatio(0.3f); anim2.getSpring().setStiffness(SpringForce.STIFFNESS_MEDIUM).setDampingRatio(0.5f); anim3.getSpring().setStiffness(SpringForce.STIFFNESS_MEDIUM).setDampingRatio(1f); InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { anim1.setStartValue(360).addUpdateListener(updateListener).addEndListener(l1) .start(); anim2.setStartValue(360).addUpdateListener(updateListener).addEndListener(l2) .addEndListener(mockListener).start(); anim3.setStartValue(360).addEndListener(l3).start(); } }); // The spring animation with critically-damped spring should return to rest position faster. verify(mockListener, timeout(2000)).onAnimationEnd(anim2, false, 0, 0); assertFalse(anim3.isRunning()); assertTrue(l3.endTime > 0); assertTrue(l2.endTime > l3.endTime); InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { if (anim1.isRunning()) { anim1.cancel(); } else { assertTrue(l1.endTime > l2.endTime); } } }); } /** * Verify that stiffer spring animations finish sooner than less stiff spring animations. Run * the same verification on different damping ratios. */ @Test public void testStiffness() { float[] dampingRatios = {0.3f, 0.5f, 1f, 5f}; final float[] stiffness = {50f, 500f, 1500f, 5000f}; DynamicAnimation.ViewProperty[] viewProperties = {DynamicAnimation.SCROLL_X, DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y, DynamicAnimation.TRANSLATION_Z}; assertEquals(viewProperties.length, stiffness.length); final SpringAnimation[] springAnims = new SpringAnimation[stiffness.length]; SpringForce[] springs = new SpringForce[stiffness.length]; MyEndListener[] listeners = new MyEndListener[stiffness.length]; // Sets stiffness for (int i = 0; i < stiffness.length; i++) { springs[i] = new SpringForce(0).setStiffness(stiffness[i]); listeners[i] = new MyEndListener(); springAnims[i] = new SpringAnimation(mView1, viewProperties[i]).setSpring(springs[i]) .addEndListener(listeners[i]); } for (int i = 0; i < dampingRatios.length; i++) { for (int j = 0; j < stiffness.length; j++) { springs[j].setDampingRatio(dampingRatios[i]); springAnims[j].setStartValue(0).setStartVelocity(500); listeners[j].endTime = -1; } DynamicAnimation.OnAnimationEndListener mockListener = mock( DynamicAnimation.OnAnimationEndListener.class); springAnims[1].addEndListener(mockListener); InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { for (int j = 0; j < stiffness.length; j++) { springAnims[j].start(); } } }); verify(mockListener, timeout(2000)).onAnimationEnd(springAnims[1], false, 0f, 0f); if (springAnims[0].isRunning()) { InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { springAnims[0].cancel(); } }); } for (int j = 1; j < stiffness.length; j++) { // The stiffer spring should finish no later than the less stiff spring. assertTrue(listeners[j - 1].endTime > listeners[j].endTime); } } } /** * Test negative stiffness and expect exception. */ @Test public void testInvalidStiffness() { SpringForce spring = new SpringForce(); mExpectedException.expect(IllegalArgumentException.class); spring.setStiffness(-5f); } /** * Test negative dampingRatio and expect exception. */ @Test public void testInvalidDampingRatio() { SpringForce spring = new SpringForce(); mExpectedException.expect(IllegalArgumentException.class); spring.setDampingRatio(-5f); } /** * Remove an update listener and an end listener, and check that there are no interaction after * removal. */ @Test public void testRemoveListeners() { final SpringAnimation anim = new SpringAnimation(mView1, DynamicAnimation.ALPHA, 0.5f); DynamicAnimation.OnAnimationEndListener endListener = mock( DynamicAnimation.OnAnimationEndListener.class); DynamicAnimation.OnAnimationEndListener removedEndListener = mock( DynamicAnimation.OnAnimationEndListener.class); DynamicAnimation.OnAnimationUpdateListener updateListener = mock( DynamicAnimation.OnAnimationUpdateListener.class); DynamicAnimation.OnAnimationUpdateListener removedUpdateListener = mock( DynamicAnimation.OnAnimationUpdateListener.class); anim.addEndListener(removedEndListener); anim.addEndListener(endListener); anim.removeEndListener(removedEndListener); anim.addUpdateListener(removedUpdateListener); anim.addUpdateListener(updateListener); anim.removeUpdateListener(removedUpdateListener); InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { anim.start(); } }); verify(endListener, timeout(1000)).onAnimationEnd(anim, false, 0.5f, 0f); verify(updateListener, atLeast(2)).onAnimationUpdate(eq(anim), any(float.class), any(float.class)); verifyZeroInteractions(removedEndListener); verifyZeroInteractions(removedUpdateListener); } /** * Verifies stiffness getter returns the right value. */ @Test public void testGetStiffness() { SpringForce spring = new SpringForce(); spring.setStiffness(1.0f); assertEquals(1.0f, spring.getStiffness(), 0.0f); spring.setStiffness(2.0f); assertEquals(2.0f, spring.getStiffness(), 0.0f); } /** * Verifies damping ratio getter returns the right value. */ @Test public void testGetDampingRatio() { SpringForce spring = new SpringForce(); spring.setDampingRatio(1.0f); assertEquals(1.0f, spring.getDampingRatio(), 0.0f); spring.setDampingRatio(2.0f); assertEquals(2.0f, spring.getDampingRatio(), 0.0f); } /** * Verifies that once min and max value threshold does apply to the values in animation. */ @Test public void testSetMinMax() { final SpringAnimation anim = new SpringAnimation(mView1, DynamicAnimation.SCALE_X, 0.0f); anim.setMinValue(0.0f); anim.setMaxValue(1.0f); anim.getSpring().setStiffness(SpringForce.STIFFNESS_HIGH).setDampingRatio( SpringForce.DAMPING_RATIO_HIGH_BOUNCY); final DynamicAnimation.OnAnimationUpdateListener mockUpdateListener = mock( DynamicAnimation.OnAnimationUpdateListener.class); final DynamicAnimation.OnAnimationEndListener mockEndListener = mock( DynamicAnimation.OnAnimationEndListener.class); final DynamicAnimation.OnAnimationUpdateListener updateListener = new DynamicAnimation.OnAnimationUpdateListener() { @Override public void onAnimationUpdate(DynamicAnimation animation, float value, float velocity) { assertTrue(value >= 0.0f); assertTrue(value <= 1.0f); } }; InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { anim.setStartValue(1.0f).setStartVelocity(8000f) .addEndListener(mockEndListener).addUpdateListener(mockUpdateListener) .addUpdateListener(updateListener).start(); }}); verify(mockEndListener, timeout(2000)).onAnimationEnd(anim, false, 0f, 0f); verify(mockUpdateListener, atLeast(2)).onAnimationUpdate(eq(anim), any(float.class), any(float.class)); } /** * Verifies animateToFinalPosition works both when the anim hasn't started and when it's * running. */ @Test public void testAnimateToFinalPosition() throws InterruptedException { final SpringAnimation anim = new SpringAnimation(mView1, DynamicAnimation.SCALE_Y, 0.0f); final DynamicAnimation.OnAnimationEndListener mockEndListener = mock( DynamicAnimation.OnAnimationEndListener.class); anim.addEndListener(mockEndListener); InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { anim.animateToFinalPosition(0.0f); } }); assertTrue(anim.isRunning()); Thread.sleep(100); assertTrue(anim.isRunning()); InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { anim.animateToFinalPosition(1.0f); } }); assertTrue(anim.isRunning()); // Verify that it indeed ends at the value from the second animateToFinalPosition() call. verify(mockEndListener, timeout(1500)).onAnimationEnd(anim, false, 1.0f, 0.0f); } /** * Verifies that skip to end will stop the animation, and skips the value to the end value. */ @Test public void testSkipToEnd() { final float finalPosition = 10f; final SpringAnimation anim = new SpringAnimation(mView1, DynamicAnimation.SCROLL_X, finalPosition); final DynamicAnimation.OnAnimationEndListener mockListener = mock(DynamicAnimation.OnAnimationEndListener.class); InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { anim.addEndListener(mockListener).setStartValue(200).start(); } }); assertTrue(anim.isRunning()); InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { int scrollX = mView1.getScrollX(); anim.skipToEnd(); // Expect no change in the animation values until next frame. assertEquals(scrollX, mView1.getScrollX()); assertTrue(anim.isRunning()); } }); verify(mockListener, timeout(100).times(1)).onAnimationEnd(anim, false, finalPosition, 0); // Also make sure the skipToEnd() call doesn't affect next animation run. final DynamicAnimation.OnAnimationEndListener mockListener2 = mock(DynamicAnimation.OnAnimationEndListener.class); InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { anim.addEndListener(mockListener2); anim.animateToFinalPosition(finalPosition + 1000f); } }); // Verify that the animation doesn't finish right away verify(mockListener2, timeout(300).times(0)).onAnimationEnd(any(DynamicAnimation.class), any(boolean.class), any(float.class), any(float.class)); // But the animation should eventually finish. verify(mockListener, timeout(1000).times(1)).onAnimationEnd(anim, false, finalPosition + 1000f, 0); } /** * Check that the min visible change does affect how soon spring animations end. */ public void testScaleMinChange() { FloatValueHolder valueHolder = new FloatValueHolder(0.5f); final SpringAnimation anim = new SpringAnimation(valueHolder); DynamicAnimation.OnAnimationUpdateListener mockListener = mock(DynamicAnimation.OnAnimationUpdateListener.class); anim.addUpdateListener(mockListener); final DynamicAnimation.OnAnimationEndListener endListener = mock(DynamicAnimation.OnAnimationEndListener.class); anim.addEndListener(endListener); InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { anim.animateToFinalPosition(1f); } }); verify(endListener, timeout(500)).onAnimationEnd(anim, false, 0, 0); verify(mockListener, atMost(5)).onAnimationUpdate(eq(anim), anyFloat(), anyFloat()); assertEquals(DynamicAnimation.MIN_VISIBLE_CHANGE_PIXELS, anim.getMinimumVisibleChange()); // Set the right threshold and start again. anim.setMinimumVisibleChange(DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE); anim.setStartValue(0.5f); InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { anim.animateToFinalPosition(1f); } }); verify(endListener, timeout(2000)).onAnimationEnd(anim, false, 0, 0); verify(mockListener, atLeast(10)).onAnimationUpdate(eq(anim), anyFloat(), anyFloat()); } /** * Makes sure all the properties getter works. */ @Test public void testAllProperties() { final DynamicAnimation.ViewProperty[] properties = { DynamicAnimation.ALPHA, DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y, DynamicAnimation.TRANSLATION_Z, DynamicAnimation.SCALE_X, DynamicAnimation.SCALE_Y, DynamicAnimation.ROTATION, DynamicAnimation.ROTATION_X, DynamicAnimation.ROTATION_Y, DynamicAnimation.X, DynamicAnimation.Y, DynamicAnimation.Z, DynamicAnimation.SCROLL_X, DynamicAnimation.SCROLL_Y, }; InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { mView1.setAlpha(0f); mView1.setTranslationX(0f); mView1.setTranslationY(0f); ViewCompat.setTranslationZ(mView1, 0f); mView1.setScaleX(0f); mView1.setScaleY(0f); mView1.setRotation(0f); mView1.setRotationX(0f); mView1.setRotationY(0f); mView1.setX(0f); mView1.setY(0f); ViewCompat.setZ(mView1, 0f); mView1.setScrollX(0); mView1.setScrollY(0); } }); View mockView = mock(View.class); final SpringAnimation[] anims = new SpringAnimation[properties.length]; final DynamicAnimation.OnAnimationUpdateListener[] mockListeners = new DynamicAnimation.OnAnimationUpdateListener[properties.length]; for (int i = 0; i < properties.length; i++) { anims[i] = new SpringAnimation(mView1, properties[i], 1); final int finalI = i; anims[i].addUpdateListener( new DynamicAnimation.OnAnimationUpdateListener() { boolean mIsFirstFrame = true; @Override public void onAnimationUpdate(DynamicAnimation animation, float value, float velocity) { if (mIsFirstFrame) { assertEquals(value, 0f, 0f); } mIsFirstFrame = false; } }); mockListeners[i] = mock(DynamicAnimation.OnAnimationUpdateListener.class); anims[i].addUpdateListener(mockListeners[i]); } InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { for (int i = properties.length - 1; i >= 0; i--) { anims[i].start(); } } }); for (int i = 0; i < properties.length; i++) { int timeout = i == 0 ? 100 : 0; verify(mockListeners[i], timeout(timeout).atLeast(1)).onAnimationUpdate( any(SpringAnimation.class), any(float.class), any(float.class)); } InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { for (int i = 0; i < properties.length; i++) { anims[i].cancel(); } } }); } /** * Test start() on a test thread. */ @Test public void testStartOnNonMainThread() { mExpectedException.expect(AndroidRuntimeException.class); SpringAnimation anim = new SpringAnimation(mView1, DynamicAnimation.ALPHA, 0f); anim.start(); } /** * Test cancel() on a test thread. */ @Test public void testCancelOnNonMainThread() { mExpectedException.expect(AndroidRuntimeException.class); SpringAnimation anim = new SpringAnimation(mView1, DynamicAnimation.ALPHA, 0f); anim.cancel(); } /** * Test skipToEnd() on a test thread. */ @Test public void testSkipToEndOnNonMainThread() { mExpectedException.expect(AndroidRuntimeException.class); SpringAnimation anim = new SpringAnimation(mView1, DynamicAnimation.ALPHA, 0f); anim.skipToEnd(); } /** * Test invalid start condition: no spring position specified, final position > max value, * and final position < min. Expect exception in all these cases. */ @Test public void testInvalidStartingCondition() { final SpringAnimation anim = new SpringAnimation(mView1, DynamicAnimation.X); InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { // Expect exception from not setting spring final position before calling start. try { anim.start(); fail("No exception is thrown when calling start() from non-main thread."); } catch (UnsupportedOperationException e) { } // Expect exception from having a final position < min value try { anim.setMinValue(50); // Final position < min value, expect exception. anim.setStartValue(50).animateToFinalPosition(40); fail("No exception is thrown when spring position is less than min value."); } catch (UnsupportedOperationException e) { } // Expect exception from not setting spring final position before calling start. try { anim.setMaxValue(60); // Final position < min value, expect exception. anim.setStartValue(60).animateToFinalPosition(70); fail("No exception is thrown when spring position is greater than max value."); } catch (UnsupportedOperationException e) { } } }); } /** * Try skipToEnd() on an undamped spring, and expect exception. */ @Test public void testUndampedSpring() { final SpringAnimation anim = new SpringAnimation(mView1, DynamicAnimation.Y); anim.setSpring(new SpringForce(10).setDampingRatio(0)); InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { // Expect exception for ending an undamped spring. try { anim.skipToEnd(); fail("No exception is thrown when calling skipToEnd() on an undamped spring"); } catch (UnsupportedOperationException e) { } } }); } static class MyEndListener implements DynamicAnimation.OnAnimationEndListener { public long endTime = -1; @Override public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, float velocity) { endTime = SystemClock.uptimeMillis(); } } }