1/* 2 * Copyright (C) 2016 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 17package android.support.transition; 18 19import static org.hamcrest.CoreMatchers.allOf; 20import static org.hamcrest.CoreMatchers.is; 21import static org.hamcrest.CoreMatchers.notNullValue; 22import static org.hamcrest.CoreMatchers.nullValue; 23import static org.hamcrest.Matchers.greaterThan; 24import static org.hamcrest.Matchers.lessThan; 25import static org.junit.Assert.assertEquals; 26import static org.junit.Assert.assertThat; 27import static org.mockito.Matchers.any; 28import static org.mockito.Mockito.mock; 29import static org.mockito.Mockito.timeout; 30import static org.mockito.Mockito.verify; 31 32import android.animation.Animator; 33import android.animation.ObjectAnimator; 34import android.animation.ValueAnimator; 35import android.os.Build; 36import android.support.annotation.NonNull; 37import android.support.annotation.Nullable; 38import android.support.test.InstrumentationRegistry; 39import android.support.test.annotation.UiThreadTest; 40import android.support.test.filters.MediumTest; 41import android.view.View; 42import android.view.ViewGroup; 43 44import org.junit.Before; 45import org.junit.Test; 46 47@MediumTest 48public class FadeTest extends BaseTest { 49 50 private View mView; 51 private ViewGroup mRoot; 52 53 @UiThreadTest 54 @Before 55 public void setUp() { 56 mRoot = rule.getActivity().getRoot(); 57 mView = new View(rule.getActivity()); 58 mRoot.addView(mView, new ViewGroup.LayoutParams(100, 100)); 59 } 60 61 @Test 62 public void testMode() { 63 assertThat(Fade.IN, is(Visibility.MODE_IN)); 64 assertThat(Fade.OUT, is(Visibility.MODE_OUT)); 65 final Fade fade = new Fade(); 66 assertThat(fade.getMode(), is(Visibility.MODE_IN | Visibility.MODE_OUT)); 67 fade.setMode(Visibility.MODE_IN); 68 assertThat(fade.getMode(), is(Visibility.MODE_IN)); 69 } 70 71 @Test 72 @UiThreadTest 73 public void testDisappear() { 74 final Fade fade = new Fade(); 75 final TransitionValues startValues = new TransitionValues(); 76 startValues.view = mView; 77 fade.captureStartValues(startValues); 78 mView.setVisibility(View.INVISIBLE); 79 final TransitionValues endValues = new TransitionValues(); 80 endValues.view = mView; 81 fade.captureEndValues(endValues); 82 Animator animator = fade.createAnimator(mRoot, startValues, endValues); 83 assertThat(animator, is(notNullValue())); 84 } 85 86 @Test 87 @UiThreadTest 88 public void testAppear() { 89 mView.setVisibility(View.INVISIBLE); 90 final Fade fade = new Fade(); 91 final TransitionValues startValues = new TransitionValues(); 92 startValues.view = mView; 93 fade.captureStartValues(startValues); 94 mView.setVisibility(View.VISIBLE); 95 final TransitionValues endValues = new TransitionValues(); 96 endValues.view = mView; 97 fade.captureEndValues(endValues); 98 Animator animator = fade.createAnimator(mRoot, startValues, endValues); 99 assertThat(animator, is(notNullValue())); 100 } 101 102 @Test 103 @UiThreadTest 104 public void testNoChange() { 105 final Fade fade = new Fade(); 106 final TransitionValues startValues = new TransitionValues(); 107 startValues.view = mView; 108 fade.captureStartValues(startValues); 109 final TransitionValues endValues = new TransitionValues(); 110 endValues.view = mView; 111 fade.captureEndValues(endValues); 112 Animator animator = fade.createAnimator(mRoot, startValues, endValues); 113 // No visibility change; no animation should happen 114 assertThat(animator, is(nullValue())); 115 } 116 117 @Test 118 public void testFadeOutThenIn() throws Throwable { 119 // Fade out 120 final Runnable interrupt = mock(Runnable.class); 121 float[] valuesOut = new float[2]; 122 final InterruptibleFade fadeOut = new InterruptibleFade(Fade.MODE_OUT, interrupt, 123 valuesOut); 124 final Transition.TransitionListener listenerOut = mock(Transition.TransitionListener.class); 125 fadeOut.addListener(listenerOut); 126 changeVisibility(fadeOut, mRoot, mView, View.INVISIBLE); 127 verify(listenerOut, timeout(3000)).onTransitionStart(any(Transition.class)); 128 129 // The view is in the middle of fading out 130 verify(interrupt, timeout(3000)).run(); 131 132 // Fade in 133 float[] valuesIn = new float[2]; 134 final InterruptibleFade fadeIn = new InterruptibleFade(Fade.MODE_IN, null, valuesIn); 135 final Transition.TransitionListener listenerIn = mock(Transition.TransitionListener.class); 136 fadeIn.addListener(listenerIn); 137 changeVisibility(fadeIn, mRoot, mView, View.VISIBLE); 138 verify(listenerOut, timeout(3000)).onTransitionPause(any(Transition.class)); 139 verify(listenerIn, timeout(3000)).onTransitionStart(any(Transition.class)); 140 assertThat(valuesOut[1], allOf(greaterThan(0f), lessThan(1f))); 141 if (Build.VERSION.SDK_INT >= 19) { 142 // These won't match on API levels 18 and below due to lack of Animator pause. 143 assertEquals(valuesOut[1], valuesIn[0], 0.01f); 144 } 145 146 verify(listenerIn, timeout(3000)).onTransitionEnd(any(Transition.class)); 147 assertThat(mView.getVisibility(), is(View.VISIBLE)); 148 assertEquals(valuesIn[1], 1.f, 0.01f); 149 } 150 151 @Test 152 public void testFadeInThenOut() throws Throwable { 153 changeVisibility(null, mRoot, mView, View.INVISIBLE); 154 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 155 156 // Fade in 157 final Runnable interrupt = mock(Runnable.class); 158 float[] valuesIn = new float[2]; 159 final InterruptibleFade fadeIn = new InterruptibleFade(Fade.MODE_IN, interrupt, valuesIn); 160 final Transition.TransitionListener listenerIn = mock(Transition.TransitionListener.class); 161 fadeIn.addListener(listenerIn); 162 changeVisibility(fadeIn, mRoot, mView, View.VISIBLE); 163 verify(listenerIn, timeout(3000)).onTransitionStart(any(Transition.class)); 164 165 // The view is in the middle of fading in 166 verify(interrupt, timeout(3000)).run(); 167 168 // Fade out 169 float[] valuesOut = new float[2]; 170 final InterruptibleFade fadeOut = new InterruptibleFade(Fade.MODE_OUT, null, valuesOut); 171 final Transition.TransitionListener listenerOut = mock(Transition.TransitionListener.class); 172 fadeOut.addListener(listenerOut); 173 changeVisibility(fadeOut, mRoot, mView, View.INVISIBLE); 174 verify(listenerIn, timeout(3000)).onTransitionPause(any(Transition.class)); 175 verify(listenerOut, timeout(3000)).onTransitionStart(any(Transition.class)); 176 assertThat(valuesIn[1], allOf(greaterThan(0f), lessThan(1f))); 177 if (Build.VERSION.SDK_INT >= 19) { 178 // These won't match on API levels 18 and below due to lack of Animator pause. 179 assertEquals(valuesIn[1], valuesOut[0], 0.01f); 180 } 181 182 verify(listenerOut, timeout(3000)).onTransitionEnd(any(Transition.class)); 183 assertThat(mView.getVisibility(), is(View.INVISIBLE)); 184 } 185 186 @Test 187 public void testFadeWithAlpha() throws Throwable { 188 // Set the view alpha to 0.5 189 rule.runOnUiThread(new Runnable() { 190 @Override 191 public void run() { 192 mView.setAlpha(0.5f); 193 } 194 }); 195 // Fade out 196 final Fade fadeOut = new Fade(Fade.OUT); 197 final Transition.TransitionListener listenerOut = mock(Transition.TransitionListener.class); 198 fadeOut.addListener(listenerOut); 199 changeVisibility(fadeOut, mRoot, mView, View.INVISIBLE); 200 verify(listenerOut, timeout(3000)).onTransitionStart(any(Transition.class)); 201 verify(listenerOut, timeout(3000)).onTransitionEnd(any(Transition.class)); 202 // Fade in 203 final Fade fadeIn = new Fade(Fade.IN); 204 final Transition.TransitionListener listenerIn = mock(Transition.TransitionListener.class); 205 fadeIn.addListener(listenerIn); 206 changeVisibility(fadeIn, mRoot, mView, View.VISIBLE); 207 verify(listenerIn, timeout(3000)).onTransitionStart(any(Transition.class)); 208 verify(listenerIn, timeout(3000)).onTransitionEnd(any(Transition.class)); 209 // Confirm that the view still has the original alpha value 210 assertThat(mView.getVisibility(), is(View.VISIBLE)); 211 assertEquals(0.5f, mView.getAlpha(), 0.01f); 212 } 213 214 private void changeVisibility(final Fade fade, final ViewGroup container, final View target, 215 final int visibility) throws Throwable { 216 rule.runOnUiThread(new Runnable() { 217 @Override 218 public void run() { 219 if (fade != null) { 220 TransitionManager.beginDelayedTransition(container, fade); 221 } 222 target.setVisibility(visibility); 223 } 224 }); 225 } 226 227 /** 228 * A special version of {@link Fade} that runs a specified {@link Runnable} soon after the 229 * target starts fading in or out. 230 */ 231 private static class InterruptibleFade extends Fade { 232 233 static final float ALPHA_THRESHOLD = 0.2f; 234 235 float mInitialAlpha = -1; 236 Runnable mMiddle; 237 final float[] mAlphaValues; 238 239 InterruptibleFade(int mode, Runnable middle, float[] alphaValues) { 240 super(mode); 241 mMiddle = middle; 242 mAlphaValues = alphaValues; 243 } 244 245 @Nullable 246 @Override 247 public Animator createAnimator(@NonNull ViewGroup sceneRoot, 248 @Nullable final TransitionValues startValues, 249 @Nullable final TransitionValues endValues) { 250 final Animator animator = super.createAnimator(sceneRoot, startValues, endValues); 251 if (animator instanceof ObjectAnimator) { 252 ((ObjectAnimator) animator).addUpdateListener( 253 new ValueAnimator.AnimatorUpdateListener() { 254 @Override 255 public void onAnimationUpdate(ValueAnimator animation) { 256 final float alpha = (float) animation.getAnimatedValue(); 257 mAlphaValues[1] = alpha; 258 if (mInitialAlpha < 0) { 259 mInitialAlpha = alpha; 260 mAlphaValues[0] = mInitialAlpha; 261 } else if (Math.abs(alpha - mInitialAlpha) > ALPHA_THRESHOLD) { 262 if (mMiddle != null) { 263 mMiddle.run(); 264 mMiddle = null; 265 } 266 } 267 } 268 }); 269 } 270 return animator; 271 } 272 273 } 274 275} 276