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 17 package androidx.transition; 18 19 20 import static org.hamcrest.CoreMatchers.equalTo; 21 import static org.hamcrest.CoreMatchers.is; 22 import static org.hamcrest.CoreMatchers.not; 23 import static org.hamcrest.CoreMatchers.nullValue; 24 import static org.hamcrest.MatcherAssert.assertThat; 25 import static org.hamcrest.Matchers.hasItem; 26 import static org.hamcrest.Matchers.sameInstance; 27 import static org.junit.Assert.assertNotNull; 28 import static org.junit.Assert.fail; 29 import static org.mockito.Matchers.any; 30 import static org.mockito.Mockito.mock; 31 import static org.mockito.Mockito.never; 32 import static org.mockito.Mockito.verify; 33 34 import android.animation.Animator; 35 import android.animation.ObjectAnimator; 36 import android.animation.TimeInterpolator; 37 import android.graphics.Rect; 38 import android.support.test.annotation.UiThreadTest; 39 import android.support.test.filters.MediumTest; 40 import android.view.LayoutInflater; 41 import android.view.View; 42 import android.view.ViewGroup; 43 import android.view.animation.LinearInterpolator; 44 import android.widget.Button; 45 import android.widget.FrameLayout; 46 import android.widget.ImageView; 47 48 import androidx.annotation.NonNull; 49 import androidx.annotation.Nullable; 50 import androidx.core.view.ViewCompat; 51 import androidx.transition.test.R; 52 53 import org.junit.Before; 54 import org.junit.Test; 55 56 import java.util.List; 57 58 @MediumTest 59 public class TransitionTest extends BaseTest { 60 61 private Scene[] mScenes = new Scene[2]; 62 private View[] mViews = new View[3]; 63 64 @Before 65 public void prepareScenes() { 66 TransitionActivity activity = rule.getActivity(); 67 ViewGroup root = activity.getRoot(); 68 mScenes[0] = Scene.getSceneForLayout(root, R.layout.support_scene0, activity); 69 mScenes[1] = Scene.getSceneForLayout(root, R.layout.support_scene1, activity); 70 } 71 72 @Test 73 public void testName() { 74 Transition transition = new EmptyTransition(); 75 assertThat(transition.getName(), 76 is(equalTo("androidx.transition.TransitionTest$EmptyTransition"))); 77 } 78 79 @Test 80 public void testDuration() { 81 Transition transition = new EmptyTransition(); 82 long duration = 12345; 83 assertThat(transition.setDuration(duration), is(sameInstance(transition))); 84 assertThat(transition.getDuration(), is(duration)); 85 } 86 87 @Test 88 public void testInterpolator() { 89 Transition transition = new EmptyTransition(); 90 TimeInterpolator interpolator = new LinearInterpolator(); 91 assertThat(transition.setInterpolator(interpolator), is(sameInstance(transition))); 92 assertThat(transition.getInterpolator(), is(interpolator)); 93 } 94 95 @Test 96 public void testStartDelay() { 97 Transition transition = new EmptyTransition(); 98 long startDelay = 12345; 99 assertThat(transition.setStartDelay(startDelay), is(sameInstance(transition))); 100 assertThat(transition.getStartDelay(), is(startDelay)); 101 } 102 103 @Test 104 public void testTargetIds() { 105 Transition transition = new EmptyTransition(); 106 assertThat(transition.addTarget(R.id.view0), is(sameInstance(transition))); 107 assertThat(transition.addTarget(R.id.view1), is(sameInstance(transition))); 108 List<Integer> targetIds = transition.getTargetIds(); 109 assertThat(targetIds.size(), is(2)); 110 assertThat(targetIds, hasItem(R.id.view0)); 111 assertThat(targetIds, hasItem(R.id.view1)); 112 assertThat(transition.removeTarget(R.id.view0), is(sameInstance(transition))); 113 targetIds = transition.getTargetIds(); 114 assertThat(targetIds.size(), is(1)); 115 assertThat(targetIds, not(hasItem(R.id.view0))); 116 assertThat(targetIds, hasItem(R.id.view1)); 117 } 118 119 @Test 120 @UiThreadTest 121 public void testTargetView() { 122 // Set up views 123 TransitionActivity activity = rule.getActivity(); 124 ViewGroup root = activity.getRoot(); 125 View container = LayoutInflater.from(activity) 126 .inflate(R.layout.support_scene0, root, false); 127 root.addView(container); 128 View view0 = container.findViewById(R.id.view0); 129 View view1 = container.findViewById(R.id.view1); 130 // Test transition targets 131 Transition transition = new EmptyTransition(); 132 assertThat(transition.addTarget(view0), is(sameInstance(transition))); 133 assertThat(transition.addTarget(view1), is(sameInstance(transition))); 134 List<View> targets = transition.getTargets(); 135 assertThat(targets.size(), is(2)); 136 assertThat(targets, hasItem(sameInstance(view0))); 137 assertThat(targets, hasItem(sameInstance(view1))); 138 assertThat(transition.removeTarget(view0), is(sameInstance(transition))); 139 targets = transition.getTargets(); 140 assertThat(targets.size(), is(1)); 141 assertThat(targets, not(hasItem(sameInstance(view0)))); 142 assertThat(targets, hasItem(sameInstance(view1))); 143 } 144 145 @Test 146 public void testTargetName() { 147 Transition transition = new EmptyTransition(); 148 assertThat(transition.addTarget("a"), is(sameInstance(transition))); 149 assertThat(transition.addTarget("b"), is(sameInstance(transition))); 150 List<String> targetNames = transition.getTargetNames(); 151 assertNotNull(targetNames); 152 assertThat(targetNames.size(), is(2)); 153 assertThat(targetNames, hasItem("a")); 154 assertThat(targetNames, hasItem("b")); 155 transition.removeTarget("a"); 156 assertThat(targetNames.size(), is(1)); 157 assertThat(targetNames, not(hasItem("a"))); 158 assertThat(targetNames, hasItem("b")); 159 } 160 161 @Test 162 public void testTargetType() { 163 Transition transition = new EmptyTransition(); 164 assertThat(transition.addTarget(Button.class), is(sameInstance(transition))); 165 assertThat(transition.addTarget(ImageView.class), is(sameInstance(transition))); 166 List<Class> targetTypes = transition.getTargetTypes(); 167 assertNotNull(targetTypes); 168 assertThat(targetTypes.size(), is(2)); 169 assertThat(targetTypes, hasItem(Button.class)); 170 assertThat(targetTypes, hasItem(ImageView.class)); 171 transition.removeTarget(Button.class); 172 assertThat(targetTypes.size(), is(1)); 173 assertThat(targetTypes, not(hasItem(Button.class))); 174 assertThat(targetTypes, hasItem(ImageView.class)); 175 } 176 177 @Test 178 public void testExcludeTargetId() throws Throwable { 179 showInitialScene(); 180 Transition transition = new EmptyTransition(); 181 transition.addTarget(R.id.view0); 182 transition.addTarget(R.id.view1); 183 View view0 = rule.getActivity().findViewById(R.id.view0); 184 View view1 = rule.getActivity().findViewById(R.id.view1); 185 assertThat(transition.isValidTarget(view0), is(true)); 186 assertThat(transition.isValidTarget(view1), is(true)); 187 transition.excludeTarget(R.id.view0, true); 188 assertThat(transition.isValidTarget(view0), is(false)); 189 assertThat(transition.isValidTarget(view1), is(true)); 190 } 191 192 @Test 193 public void testExcludeTargetView() throws Throwable { 194 showInitialScene(); 195 Transition transition = new EmptyTransition(); 196 View view0 = rule.getActivity().findViewById(R.id.view0); 197 View view1 = rule.getActivity().findViewById(R.id.view1); 198 transition.addTarget(view0); 199 transition.addTarget(view1); 200 assertThat(transition.isValidTarget(view0), is(true)); 201 assertThat(transition.isValidTarget(view1), is(true)); 202 transition.excludeTarget(view0, true); 203 assertThat(transition.isValidTarget(view0), is(false)); 204 assertThat(transition.isValidTarget(view1), is(true)); 205 } 206 207 @Test 208 public void testExcludeTargetName() throws Throwable { 209 showInitialScene(); 210 Transition transition = new EmptyTransition(); 211 View view0 = rule.getActivity().findViewById(R.id.view0); 212 View view1 = rule.getActivity().findViewById(R.id.view1); 213 ViewCompat.setTransitionName(view0, "zero"); 214 ViewCompat.setTransitionName(view1, "one"); 215 transition.addTarget("zero"); 216 transition.addTarget("one"); 217 assertThat(transition.isValidTarget(view0), is(true)); 218 assertThat(transition.isValidTarget(view1), is(true)); 219 transition.excludeTarget("zero", true); 220 assertThat(transition.isValidTarget(view0), is(false)); 221 assertThat(transition.isValidTarget(view1), is(true)); 222 } 223 224 @Test 225 public void testExcludeTargetType() throws Throwable { 226 showInitialScene(); 227 Transition transition = new EmptyTransition(); 228 FrameLayout container = (FrameLayout) rule.getActivity().findViewById(R.id.container); 229 View view0 = rule.getActivity().findViewById(R.id.view0); 230 transition.addTarget(View.class); 231 assertThat(transition.isValidTarget(container), is(true)); 232 assertThat(transition.isValidTarget(view0), is(true)); 233 transition.excludeTarget(FrameLayout.class, true); 234 assertThat(transition.isValidTarget(container), is(false)); 235 assertThat(transition.isValidTarget(view0), is(true)); 236 } 237 238 @Test 239 public void testListener() { 240 Transition transition = new EmptyTransition(); 241 Transition.TransitionListener listener = new EmptyTransitionListener(); 242 assertThat(transition.addListener(listener), is(sameInstance(transition))); 243 assertThat(transition.removeListener(listener), is(sameInstance(transition))); 244 } 245 246 @Test 247 public void testMatchOrder() throws Throwable { 248 showInitialScene(); 249 final Transition transition = new ChangeBounds() { 250 @Nullable 251 @Override 252 public Animator createAnimator(@NonNull ViewGroup sceneRoot, 253 @Nullable TransitionValues startValues, @Nullable TransitionValues endValues) { 254 if (startValues != null && endValues != null) { 255 fail("Match by View ID should be prevented"); 256 } 257 return super.createAnimator(sceneRoot, startValues, endValues); 258 } 259 }; 260 transition.setDuration(0); 261 // This prevents matches between start and end scenes because they have different set of 262 // View instances. They will be regarded as independent views even though they share the 263 // same View IDs. 264 transition.setMatchOrder(Transition.MATCH_INSTANCE); 265 SyncRunnable enter1 = new SyncRunnable(); 266 mScenes[1].setEnterAction(enter1); 267 goToScene(mScenes[1], transition); 268 if (!enter1.await()) { 269 fail("Timed out while waiting for scene change"); 270 } 271 } 272 273 @Test 274 public void testExcludedTransitionAnimator() throws Throwable { 275 showInitialScene(); 276 final Animator.AnimatorListener animatorListener = mock(Animator.AnimatorListener.class); 277 final DummyTransition transition = new DummyTransition(animatorListener); 278 final SyncTransitionListener transitionListener = new SyncTransitionListener( 279 SyncTransitionListener.EVENT_END); 280 transition.addListener(transitionListener); 281 transition.addTarget(mViews[0]); 282 transition.excludeTarget(mViews[0], true); 283 rule.runOnUiThread(new Runnable() { 284 @Override 285 public void run() { 286 TransitionManager.beginDelayedTransition(rule.getActivity().getRoot(), transition); 287 mViews[0].setTranslationX(3.f); 288 } 289 }); 290 if (!transitionListener.await()) { 291 fail("Timed out waiting for the TransitionListener"); 292 } 293 verify(animatorListener, never()).onAnimationStart(any(Animator.class)); 294 } 295 296 @Test 297 public void testEpicenter() throws Throwable { 298 final Transition transition = new EmptyTransition(); 299 final Transition.EpicenterCallback epicenterCallback = new Transition.EpicenterCallback() { 300 private Rect mRect = new Rect(); 301 302 @Override 303 public Rect onGetEpicenter(@NonNull Transition t) { 304 assertThat(t, is(sameInstance(transition))); 305 mRect.set(1, 2, 3, 4); 306 return mRect; 307 } 308 }; 309 transition.setEpicenterCallback(epicenterCallback); 310 assertThat(transition.getEpicenterCallback(), 311 is(sameInstance(transition.getEpicenterCallback()))); 312 Rect rect = transition.getEpicenter(); 313 assertNotNull(rect); 314 assertThat(rect.left, is(1)); 315 assertThat(rect.top, is(2)); 316 assertThat(rect.right, is(3)); 317 assertThat(rect.bottom, is(4)); 318 } 319 320 @Test 321 public void testSetPropagation() throws Throwable { 322 final Transition transition = new EmptyTransition(); 323 assertThat(transition.getPropagation(), is(nullValue())); 324 final TransitionPropagation propagation = new CircularPropagation(); 325 transition.setPropagation(propagation); 326 assertThat(propagation, is(sameInstance(propagation))); 327 } 328 329 @Test 330 public void testIsTransitionRequired() throws Throwable { 331 final EmptyTransition transition = new EmptyTransition(); 332 assertThat(transition.isTransitionRequired(null, null), is(false)); 333 final TransitionValues start = new TransitionValues(); 334 final String propname = "android:transition:dummy"; 335 start.values.put(propname, 1); 336 final TransitionValues end = new TransitionValues(); 337 end.values.put(propname, 1); 338 assertThat(transition.isTransitionRequired(start, end), is(false)); 339 end.values.put(propname, 2); 340 assertThat(transition.isTransitionRequired(start, end), is(true)); 341 } 342 343 private void showInitialScene() throws Throwable { 344 SyncRunnable enter0 = new SyncRunnable(); 345 mScenes[0].setEnterAction(enter0); 346 AutoTransition transition1 = new AutoTransition(); 347 transition1.setDuration(0); 348 goToScene(mScenes[0], transition1); 349 if (!enter0.await()) { 350 fail("Timed out while waiting for scene change"); 351 } 352 mViews[0] = rule.getActivity().findViewById(R.id.view0); 353 mViews[1] = rule.getActivity().findViewById(R.id.view1); 354 mViews[2] = rule.getActivity().findViewById(R.id.view2); 355 } 356 357 private void goToScene(final Scene scene, final Transition transition) throws Throwable { 358 rule.runOnUiThread(new Runnable() { 359 @Override 360 public void run() { 361 TransitionManager.go(scene, transition); 362 } 363 }); 364 } 365 366 public static class EmptyTransition extends Transition { 367 368 @Override 369 public void captureEndValues(@NonNull TransitionValues transitionValues) { 370 } 371 372 @Override 373 public void captureStartValues(@NonNull TransitionValues transitionValues) { 374 } 375 376 @Override 377 public Animator createAnimator(@NonNull ViewGroup sceneRoot, 378 @Nullable TransitionValues startValues, 379 @Nullable TransitionValues endValues) { 380 return null; 381 } 382 383 } 384 385 public static class EmptyTransitionListener implements Transition.TransitionListener { 386 387 @Override 388 public void onTransitionStart(@NonNull Transition transition) { 389 } 390 391 @Override 392 public void onTransitionEnd(@NonNull Transition transition) { 393 } 394 395 @Override 396 public void onTransitionCancel(@NonNull Transition transition) { 397 } 398 399 @Override 400 public void onTransitionPause(@NonNull Transition transition) { 401 } 402 403 @Override 404 public void onTransitionResume(@NonNull Transition transition) { 405 } 406 407 } 408 409 /** 410 * A dummy transition for monitoring use of its animator by the Transition framework. 411 */ 412 private static class DummyTransition extends Transition { 413 414 private final Animator.AnimatorListener mListener; 415 416 DummyTransition(Animator.AnimatorListener listener) { 417 mListener = listener; 418 } 419 420 @Override 421 public void captureStartValues(@NonNull TransitionValues transitionValues) { 422 transitionValues.values.put("state", 1); 423 } 424 425 @Override 426 public void captureEndValues(@NonNull TransitionValues transitionValues) { 427 transitionValues.values.put("state", 2); 428 } 429 430 @Override 431 public Animator createAnimator(@NonNull ViewGroup sceneRoot, TransitionValues startValues, 432 TransitionValues endValues) { 433 if (startValues == null || endValues == null) { 434 return null; 435 } 436 final ObjectAnimator animator = ObjectAnimator 437 .ofFloat(startValues.view, "translationX", 1.f, 2.f); 438 animator.addListener(mListener); 439 return animator; 440 } 441 442 } 443 } 444