FragmentTransitionCompat21.java revision 5a8f14de0bb4304f9a3aebdcb35ee0dd607f27f7
1/* 2 * Copyright (C) 2014 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.v4.app; 18 19import android.content.Context; 20import android.content.res.TypedArray; 21import android.graphics.Rect; 22import android.transition.Transition; 23import android.transition.TransitionInflater; 24import android.transition.TransitionManager; 25import android.transition.TransitionSet; 26import android.util.ArrayMap; 27import android.view.View; 28import android.view.ViewGroup; 29import android.view.ViewTreeObserver; 30 31import java.util.ArrayList; 32import java.util.Map; 33 34class FragmentTransitionCompat21 { 35 public static String getTransitionName(View view) { 36 return view.getTransitionName(); 37 } 38 39 public static Object cloneTransition(Object transition) { 40 if (transition != null) { 41 transition = ((Transition)transition).clone(); 42 } 43 return transition; 44 } 45 46 public static Object captureExitingViews(Object exitTransition, View root, 47 ArrayList<View> viewList, Map<String, View> namedViews) { 48 if (exitTransition != null) { 49 captureTransitioningViews(viewList, root); 50 if (namedViews != null) { 51 viewList.removeAll(namedViews.values()); 52 } 53 if (viewList.isEmpty()) { 54 exitTransition = null; 55 } else { 56 addTargets((Transition) exitTransition, viewList); 57 } 58 } 59 return exitTransition; 60 } 61 62 public static void excludeTarget(Object transitionObject, View view, boolean exclude) { 63 Transition transition = (Transition) transitionObject; 64 transition.excludeTarget(view, exclude); 65 } 66 67 public static void beginDelayedTransition(ViewGroup sceneRoot, Object transitionObject) { 68 Transition transition = (Transition) transitionObject; 69 TransitionManager.beginDelayedTransition(sceneRoot, transition); 70 } 71 72 public static void setEpicenter(Object transitionObject, View view) { 73 Transition transition = (Transition) transitionObject; 74 final Rect epicenter = getBoundsOnScreen(view); 75 76 transition.setEpicenterCallback(new Transition.EpicenterCallback() { 77 @Override 78 public Rect onGetEpicenter(Transition transition) { 79 return epicenter; 80 } 81 }); 82 } 83 84 /** 85 * Prepares the enter transition by adding a non-existent view to the transition's target list 86 * and setting it epicenter callback. By adding a non-existent view to the target list, 87 * we can prevent any view from being targeted at the beginning of the transition. 88 * We will add to the views before the end state of the transition is captured so that the 89 * views will appear. At the start of the transition, we clear the list of targets so that 90 * we can restore the state of the transition and use it again. 91 * 92 * <p>The shared element transition maps its shared elements immediately prior to 93 * capturing the final state of the Transition.</p> 94 */ 95 public static void addTransitionTargets(Object enterTransitionObject, 96 Object sharedElementTransitionObject, final View container, 97 final ViewRetriever inFragment, final View nonExistentView, 98 EpicenterView epicenterView, final Map<String, String> nameOverrides, 99 final ArrayList<View> enteringViews, final Map<String, View> renamedViews) { 100 if (enterTransitionObject != null || sharedElementTransitionObject != null) { 101 final Transition enterTransition = (Transition) enterTransitionObject; 102 if (enterTransition != null) { 103 enterTransition.addTarget(nonExistentView); 104 } 105 if (inFragment != null) { 106 container.getViewTreeObserver().addOnPreDrawListener( 107 new ViewTreeObserver.OnPreDrawListener() { 108 public boolean onPreDraw() { 109 container.getViewTreeObserver().removeOnPreDrawListener(this); 110 View fragmentView = inFragment.getView(); 111 if (fragmentView != null) { 112 if (!nameOverrides.isEmpty()) { 113 findNamedViews(renamedViews, fragmentView); 114 renamedViews.keySet().retainAll(nameOverrides.values()); 115 for (Map.Entry<String, String> entry : nameOverrides.entrySet()) { 116 String to = entry.getValue(); 117 View view = renamedViews.get(to); 118 if (view != null) { 119 String from = entry.getKey(); 120 view.setTransitionName(from); 121 } 122 } 123 } 124 if (enterTransition != null) { 125 captureTransitioningViews(enteringViews, fragmentView); 126 enteringViews.removeAll(renamedViews.values()); 127 addTargets(enterTransition, enteringViews); 128 } 129 } 130 return true; 131 } 132 }); 133 } 134 setSharedElementEpicenter(enterTransition, epicenterView); 135 } 136 } 137 138 public static Object mergeTransitions(Object enterTransitionObject, 139 Object exitTransitionObject, Object sharedElementTransitionObject, 140 boolean allowOverlap) { 141 boolean overlap = true; 142 Transition enterTransition = (Transition) enterTransitionObject; 143 Transition exitTransition = (Transition) exitTransitionObject; 144 Transition sharedElementTransition = (Transition) sharedElementTransitionObject; 145 146 if (enterTransition != null && exitTransition != null) { 147 overlap = allowOverlap; 148 } 149 150 // Wrap the transitions. Explicit targets like in enter and exit will cause the 151 // views to be targeted regardless of excluded views. If that happens, then the 152 // excluded fragments views (hidden fragments) will still be in the transition. 153 154 Transition transition; 155 if (overlap) { 156 // Regular transition -- do it all together 157 transition = mergeTransitions(enterTransition, exitTransition, 158 sharedElementTransition); 159 if (!(transition instanceof TransitionSet)) { 160 transition = new TransitionSet().addTransition(transition); 161 } 162 } else { 163 // First do exit, then enter, but allow shared element transition to happen 164 // during both. 165 Transition staggered = null; 166 if (exitTransition != null && enterTransition != null) { 167 staggered = new TransitionSet() 168 .addTransition(exitTransition) 169 .addTransition(enterTransition) 170 .setOrdering(TransitionSet.ORDERING_SEQUENTIAL); 171 } else if (exitTransition != null) { 172 staggered = exitTransition; 173 } else if (enterTransition != null) { 174 staggered = enterTransition; 175 } 176 if (sharedElementTransition != null) { 177 TransitionSet together = new TransitionSet(); 178 if (staggered != null) { 179 together.addTransition(staggered); 180 } 181 together.addTransition(sharedElementTransition); 182 transition = together; 183 } else { 184 transition = staggered; 185 } 186 } 187 return transition; 188 } 189 190 191 192 private static void setSharedElementEpicenter(Transition transition, 193 final EpicenterView epicenterView) { 194 if (transition != null) { 195 transition.setEpicenterCallback(new Transition.EpicenterCallback() { 196 private Rect mEpicenter; 197 198 @Override 199 public Rect onGetEpicenter(Transition transition) { 200 if (mEpicenter == null && epicenterView.epicenter != null) { 201 mEpicenter = getBoundsOnScreen(epicenterView.epicenter); 202 } 203 return mEpicenter; 204 } 205 }); 206 } 207 } 208 209 private static Rect getBoundsOnScreen(View view) { 210 Rect epicenter = new Rect(); 211 int[] loc = new int[2]; 212 view.getLocationOnScreen(loc); 213 // not as good as View.getBoundsOnScreen, but that's not public 214 epicenter.set(loc[0], loc[1], loc[0] + view.getWidth(), loc[1] + view.getHeight()); 215 return epicenter; 216 } 217 218 private static void captureTransitioningViews(ArrayList<View> transitioningViews, View view) { 219 if (view.getVisibility() == View.VISIBLE) { 220 if (view instanceof ViewGroup) { 221 ViewGroup viewGroup = (ViewGroup) view; 222 if (viewGroup.isTransitionGroup()) { 223 transitioningViews.add(viewGroup); 224 } else { 225 int count = viewGroup.getChildCount(); 226 for (int i = 0; i < count; i++) { 227 View child = viewGroup.getChildAt(i); 228 captureTransitioningViews(transitioningViews, child); 229 } 230 } 231 } else { 232 transitioningViews.add(view); 233 } 234 } 235 } 236 237 public static void findNamedViews(Map<String, View> namedViews, View view) { 238 if (view.getVisibility() == View.VISIBLE) { 239 String transitionName = view.getTransitionName(); 240 if (transitionName != null) { 241 namedViews.put(transitionName, view); 242 } 243 if (view instanceof ViewGroup) { 244 ViewGroup viewGroup = (ViewGroup) view; 245 int count = viewGroup.getChildCount(); 246 for (int i = 0; i < count; i++) { 247 View child = viewGroup.getChildAt(i); 248 findNamedViews(namedViews, child); 249 } 250 } 251 } 252 } 253 254 public static void cleanupTransitions(final View sceneRoot, final View nonExistentView, 255 Object enterTransitionObject, final ArrayList<View> enteringViews, 256 Object exitTransitionObject, final ArrayList<View> exitingViews, 257 Object overallTransitionObject, final ArrayList<View> hiddenViews, 258 final Map<String, View> renamedViews) { 259 final Transition enterTransition = (Transition) enterTransitionObject; 260 final Transition exitTransition = (Transition) exitTransitionObject; 261 final Transition overallTransition = (Transition) overallTransitionObject; 262 if (overallTransition != null) { 263 sceneRoot.getViewTreeObserver().addOnPreDrawListener( 264 new ViewTreeObserver.OnPreDrawListener() { 265 public boolean onPreDraw() { 266 sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this); 267 if (enterTransition != null) { 268 enterTransition.removeTarget(nonExistentView); 269 removeTargets(enterTransition, enteringViews); 270 } 271 if (exitTransition != null) { 272 removeTargets(exitTransition, exitingViews); 273 } 274 for (Map.Entry<String, View> entry : renamedViews.entrySet()) { 275 View view = entry.getValue(); 276 String name = entry.getKey(); 277 view.setTransitionName(name); 278 } 279 int numViews = hiddenViews.size(); 280 for (int i = 0; i < numViews; i++) { 281 overallTransition.excludeTarget(hiddenViews.get(i), false); 282 } 283 overallTransition.excludeTarget(nonExistentView, false); 284 return true; 285 } 286 }); 287 } 288 } 289 290 private static void removeTargets(Transition transition, ArrayList<View> views) { 291 int numViews = views.size(); 292 for (int i = 0; i < numViews; i++) { 293 transition.removeTarget(views.get(i)); 294 } 295 } 296 297 private static void addTargets(Transition transition, ArrayList<View> views) { 298 int numViews = views.size(); 299 for (int i = 0; i < numViews; i++) { 300 transition.addTarget(views.get(i)); 301 } 302 } 303 304 public interface ViewRetriever { 305 View getView(); 306 } 307 308 public static class EpicenterView { 309 public View epicenter; 310 } 311 312 public static Transition mergeTransitions(Transition... transitions) { 313 int count = 0; 314 int nonNullIndex = -1; 315 for (int i = 0; i < transitions.length; i++) { 316 if (transitions[i] != null) { 317 count++; 318 nonNullIndex = i; 319 } 320 } 321 322 if (count == 0) { 323 return null; 324 } 325 326 if (count == 1) { 327 return transitions[nonNullIndex]; 328 } 329 330 TransitionSet transitionSet = new TransitionSet(); 331 for (int i = 0; i < transitions.length; i++) { 332 if (transitions[i] != null) { 333 transitionSet.addTransition(transitions[i]); 334 } 335 } 336 return transitionSet; 337 } 338} 339