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 final ArrayList<View> sharedElementTargets) { 101 if (enterTransitionObject != null || sharedElementTransitionObject != null) { 102 final Transition enterTransition = (Transition) enterTransitionObject; 103 if (enterTransition != null) { 104 enterTransition.addTarget(nonExistentView); 105 } 106 if (sharedElementTransitionObject != null) { 107 Transition sharedElementTransition = (Transition) sharedElementTransitionObject; 108 addTargets(sharedElementTransition, sharedElementTargets); 109 } 110 111 if (inFragment != null) { 112 container.getViewTreeObserver().addOnPreDrawListener( 113 new ViewTreeObserver.OnPreDrawListener() { 114 public boolean onPreDraw() { 115 container.getViewTreeObserver().removeOnPreDrawListener(this); 116 View fragmentView = inFragment.getView(); 117 if (fragmentView != null) { 118 if (!nameOverrides.isEmpty()) { 119 findNamedViews(renamedViews, fragmentView); 120 renamedViews.keySet().retainAll(nameOverrides.values()); 121 for (Map.Entry<String, String> entry : nameOverrides.entrySet()) { 122 String to = entry.getValue(); 123 View view = renamedViews.get(to); 124 if (view != null) { 125 String from = entry.getKey(); 126 view.setTransitionName(from); 127 } 128 } 129 } 130 if (enterTransition != null) { 131 captureTransitioningViews(enteringViews, fragmentView); 132 enteringViews.removeAll(renamedViews.values()); 133 addTargets(enterTransition, enteringViews); 134 } 135 } 136 return true; 137 } 138 }); 139 } 140 setSharedElementEpicenter(enterTransition, epicenterView); 141 } 142 } 143 144 public static Object mergeTransitions(Object enterTransitionObject, 145 Object exitTransitionObject, Object sharedElementTransitionObject, 146 boolean allowOverlap) { 147 boolean overlap = true; 148 Transition enterTransition = (Transition) enterTransitionObject; 149 Transition exitTransition = (Transition) exitTransitionObject; 150 Transition sharedElementTransition = (Transition) sharedElementTransitionObject; 151 152 if (enterTransition != null && exitTransition != null) { 153 overlap = allowOverlap; 154 } 155 156 // Wrap the transitions. Explicit targets like in enter and exit will cause the 157 // views to be targeted regardless of excluded views. If that happens, then the 158 // excluded fragments views (hidden fragments) will still be in the transition. 159 160 Transition transition; 161 if (overlap) { 162 // Regular transition -- do it all together 163 TransitionSet transitionSet = new TransitionSet(); 164 if (enterTransition != null) { 165 transitionSet.addTransition(enterTransition); 166 } 167 if (exitTransition != null) { 168 transitionSet.addTransition(exitTransition); 169 } 170 if (sharedElementTransition != null) { 171 transitionSet.addTransition(sharedElementTransition); 172 } 173 transition = transitionSet; 174 } else { 175 // First do exit, then enter, but allow shared element transition to happen 176 // during both. 177 Transition staggered = null; 178 if (exitTransition != null && enterTransition != null) { 179 staggered = new TransitionSet() 180 .addTransition(exitTransition) 181 .addTransition(enterTransition) 182 .setOrdering(TransitionSet.ORDERING_SEQUENTIAL); 183 } else if (exitTransition != null) { 184 staggered = exitTransition; 185 } else if (enterTransition != null) { 186 staggered = enterTransition; 187 } 188 if (sharedElementTransition != null) { 189 TransitionSet together = new TransitionSet(); 190 if (staggered != null) { 191 together.addTransition(staggered); 192 } 193 together.addTransition(sharedElementTransition); 194 transition = together; 195 } else { 196 transition = staggered; 197 } 198 } 199 return transition; 200 } 201 202 203 204 private static void setSharedElementEpicenter(Transition transition, 205 final EpicenterView epicenterView) { 206 if (transition != null) { 207 transition.setEpicenterCallback(new Transition.EpicenterCallback() { 208 private Rect mEpicenter; 209 210 @Override 211 public Rect onGetEpicenter(Transition transition) { 212 if (mEpicenter == null && epicenterView.epicenter != null) { 213 mEpicenter = getBoundsOnScreen(epicenterView.epicenter); 214 } 215 return mEpicenter; 216 } 217 }); 218 } 219 } 220 221 private static Rect getBoundsOnScreen(View view) { 222 Rect epicenter = new Rect(); 223 int[] loc = new int[2]; 224 view.getLocationOnScreen(loc); 225 // not as good as View.getBoundsOnScreen, but that's not public 226 epicenter.set(loc[0], loc[1], loc[0] + view.getWidth(), loc[1] + view.getHeight()); 227 return epicenter; 228 } 229 230 private static void captureTransitioningViews(ArrayList<View> transitioningViews, View view) { 231 if (view.getVisibility() == View.VISIBLE) { 232 if (view instanceof ViewGroup) { 233 ViewGroup viewGroup = (ViewGroup) view; 234 if (viewGroup.isTransitionGroup()) { 235 transitioningViews.add(viewGroup); 236 } else { 237 int count = viewGroup.getChildCount(); 238 for (int i = 0; i < count; i++) { 239 View child = viewGroup.getChildAt(i); 240 captureTransitioningViews(transitioningViews, child); 241 } 242 } 243 } else { 244 transitioningViews.add(view); 245 } 246 } 247 } 248 249 public static void findNamedViews(Map<String, View> namedViews, View view) { 250 if (view.getVisibility() == View.VISIBLE) { 251 String transitionName = view.getTransitionName(); 252 if (transitionName != null) { 253 namedViews.put(transitionName, view); 254 } 255 if (view instanceof ViewGroup) { 256 ViewGroup viewGroup = (ViewGroup) view; 257 int count = viewGroup.getChildCount(); 258 for (int i = 0; i < count; i++) { 259 View child = viewGroup.getChildAt(i); 260 findNamedViews(namedViews, child); 261 } 262 } 263 } 264 } 265 266 public static void cleanupTransitions(final View sceneRoot, final View nonExistentView, 267 Object enterTransitionObject, final ArrayList<View> enteringViews, 268 Object exitTransitionObject, final ArrayList<View> exitingViews, 269 Object sharedElementTransitionObject, final ArrayList<View> sharedElementTargets, 270 Object overallTransitionObject, final ArrayList<View> hiddenViews, 271 final Map<String, View> renamedViews) { 272 final Transition enterTransition = (Transition) enterTransitionObject; 273 final Transition exitTransition = (Transition) exitTransitionObject; 274 final Transition sharedElementTransition = (Transition) sharedElementTransitionObject; 275 final Transition overallTransition = (Transition) overallTransitionObject; 276 if (overallTransition != null) { 277 sceneRoot.getViewTreeObserver().addOnPreDrawListener( 278 new ViewTreeObserver.OnPreDrawListener() { 279 public boolean onPreDraw() { 280 sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this); 281 if (enterTransition != null) { 282 enterTransition.removeTarget(nonExistentView); 283 removeTargets(enterTransition, enteringViews); 284 } 285 if (exitTransition != null) { 286 removeTargets(exitTransition, exitingViews); 287 } 288 if (sharedElementTransition != null) { 289 removeTargets(sharedElementTransition, sharedElementTargets); 290 } 291 for (Map.Entry<String, View> entry : renamedViews.entrySet()) { 292 View view = entry.getValue(); 293 String name = entry.getKey(); 294 view.setTransitionName(name); 295 } 296 int numViews = hiddenViews.size(); 297 for (int i = 0; i < numViews; i++) { 298 overallTransition.excludeTarget(hiddenViews.get(i), false); 299 } 300 overallTransition.excludeTarget(nonExistentView, false); 301 return true; 302 } 303 }); 304 } 305 } 306 307 public static void removeTargets(Object transitionObject, ArrayList<View> views) { 308 Transition transition = (Transition) transitionObject; 309 int numViews = views.size(); 310 for (int i = 0; i < numViews; i++) { 311 transition.removeTarget(views.get(i)); 312 } 313 } 314 315 public static void addTargets(Object transitionObject, ArrayList<View> views) { 316 Transition transition = (Transition) transitionObject; 317 int numViews = views.size(); 318 for (int i = 0; i < numViews; i++) { 319 transition.addTarget(views.get(i)); 320 } 321 } 322 323 public interface ViewRetriever { 324 View getView(); 325 } 326 327 public static class EpicenterView { 328 public View epicenter; 329 } 330} 331