/*
* Copyright (C) 2014 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.v4.app;
import android.graphics.Rect;
import android.transition.Transition;
import android.transition.TransitionManager;
import android.transition.TransitionSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
class FragmentTransitionCompat21 {
public static String getTransitionName(View view) {
return view.getTransitionName();
}
public static Object cloneTransition(Object transition) {
if (transition != null) {
transition = ((Transition)transition).clone();
}
return transition;
}
public static Object captureExitingViews(Object exitTransition, View root,
ArrayList<View> viewList, Map<String, View> namedViews, View nonExistentView) {
if (exitTransition != null) {
captureTransitioningViews(viewList, root);
if (namedViews != null) {
viewList.removeAll(namedViews.values());
}
if (viewList.isEmpty()) {
exitTransition = null;
} else {
viewList.add(nonExistentView);
addTargets((Transition) exitTransition, viewList);
}
}
return exitTransition;
}
public static void excludeTarget(Object transitionObject, View view, boolean exclude) {
Transition transition = (Transition) transitionObject;
transition.excludeTarget(view, exclude);
}
public static void beginDelayedTransition(ViewGroup sceneRoot, Object transitionObject) {
Transition transition = (Transition) transitionObject;
TransitionManager.beginDelayedTransition(sceneRoot, transition);
}
public static void setEpicenter(Object transitionObject, View view) {
Transition transition = (Transition) transitionObject;
final Rect epicenter = getBoundsOnScreen(view);
transition.setEpicenterCallback(new Transition.EpicenterCallback() {
@Override
public Rect onGetEpicenter(Transition transition) {
return epicenter;
}
});
}
public static Object wrapSharedElementTransition(Object transitionObj) {
if (transitionObj == null) {
return null;
}
Transition transition = (Transition) transitionObj;
if (transition == null) {
return null;
}
TransitionSet transitionSet = new TransitionSet();
transitionSet.addTransition(transition);
return transitionSet;
}
/**
* Prepares the enter transition by adding a non-existent view to the transition's target list
* and setting it epicenter callback. By adding a non-existent view to the target list,
* we can prevent any view from being targeted at the beginning of the transition.
* We will add to the views before the end state of the transition is captured so that the
* views will appear. At the start of the transition, we clear the list of targets so that
* we can restore the state of the transition and use it again.
*
* <p>The shared element transition maps its shared elements immediately prior to
* capturing the final state of the Transition.</p>
*/
public static void addTransitionTargets(Object enterTransitionObject,
Object sharedElementTransitionObject, final View container,
final ViewRetriever inFragment, final View nonExistentView,
EpicenterView epicenterView, final Map<String, String> nameOverrides,
final ArrayList<View> enteringViews, final Map<String, View> namedViews,
final Map<String, View> renamedViews, final ArrayList<View> sharedElementTargets) {
if (enterTransitionObject != null || sharedElementTransitionObject != null) {
final Transition enterTransition = (Transition) enterTransitionObject;
if (enterTransition != null) {
enterTransition.addTarget(nonExistentView);
}
if (sharedElementTransitionObject != null) {
setSharedElementTargets(sharedElementTransitionObject, nonExistentView,
namedViews, sharedElementTargets);
}
if (inFragment != null) {
container.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
public boolean onPreDraw() {
container.getViewTreeObserver().removeOnPreDrawListener(this);
if (enterTransition != null) {
enterTransition.removeTarget(nonExistentView);
}
View fragmentView = inFragment.getView();
if (fragmentView != null) {
if (!nameOverrides.isEmpty()) {
findNamedViews(renamedViews, fragmentView);
renamedViews.keySet().retainAll(nameOverrides.values());
for (Map.Entry<String, String> entry : nameOverrides.entrySet()) {
String to = entry.getValue();
View view = renamedViews.get(to);
if (view != null) {
String from = entry.getKey();
view.setTransitionName(from);
}
}
}
if (enterTransition != null) {
captureTransitioningViews(enteringViews, fragmentView);
enteringViews.removeAll(renamedViews.values());
enteringViews.add(nonExistentView);
addTargets(enterTransition, enteringViews);
}
}
return true;
}
});
}
setSharedElementEpicenter(enterTransition, epicenterView);
}
}
public static Object mergeTransitions(Object enterTransitionObject,
Object exitTransitionObject, Object sharedElementTransitionObject,
boolean allowOverlap) {
boolean overlap = true;
Transition enterTransition = (Transition) enterTransitionObject;
Transition exitTransition = (Transition) exitTransitionObject;
Transition sharedElementTransition = (Transition) sharedElementTransitionObject;
if (enterTransition != null && exitTransition != null) {
overlap = allowOverlap;
}
// Wrap the transitions. Explicit targets like in enter and exit will cause the
// views to be targeted regardless of excluded views. If that happens, then the
// excluded fragments views (hidden fragments) will still be in the transition.
Transition transition;
if (overlap) {
// Regular transition -- do it all together
TransitionSet transitionSet = new TransitionSet();
if (enterTransition != null) {
transitionSet.addTransition(enterTransition);
}
if (exitTransition != null) {
transitionSet.addTransition(exitTransition);
}
if (sharedElementTransition != null) {
transitionSet.addTransition(sharedElementTransition);
}
transition = transitionSet;
} else {
// First do exit, then enter, but allow shared element transition to happen
// during both.
Transition staggered = null;
if (exitTransition != null && enterTransition != null) {
staggered = new TransitionSet()
.addTransition(exitTransition)
.addTransition(enterTransition)
.setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
} else if (exitTransition != null) {
staggered = exitTransition;
} else if (enterTransition != null) {
staggered = enterTransition;
}
if (sharedElementTransition != null) {
TransitionSet together = new TransitionSet();
if (staggered != null) {
together.addTransition(staggered);
}
together.addTransition(sharedElementTransition);
transition = together;
} else {
transition = staggered;
}
}
return transition;
}
/**
* Finds all children of the shared elements and sets the wrapping TransitionSet
* targets to point to those. It also limits transitions that have no targets to the
* specific shared elements. This allows developers to target child views of the
* shared elements specifically, but this doesn't happen by default.
*/
public static void setSharedElementTargets(Object transitionObj,
View nonExistentView, Map<String, View> namedViews,
ArrayList<View> sharedElementTargets) {
TransitionSet transition = (TransitionSet) transitionObj;
sharedElementTargets.clear();
sharedElementTargets.addAll(namedViews.values());
final List<View> views = transition.getTargets();
views.clear();
final int count = sharedElementTargets.size();
for (int i = 0; i < count; i++) {
final View view = sharedElementTargets.get(i);
bfsAddViewChildren(views, view);
}
sharedElementTargets.add(nonExistentView);
addTargets(transition, sharedElementTargets);
}
/**
* Uses a breadth-first scheme to add startView and all of its children to views.
* It won't add a child if it is already in views.
*/
private static void bfsAddViewChildren(final List<View> views, final View startView) {
final int startIndex = views.size();
if (containedBeforeIndex(views, startView, startIndex)) {
return; // This child is already in the list, so all its children are also.
}
views.add(startView);
for (int index = startIndex; index < views.size(); index++) {
final View view = views.get(index);
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
final int childCount = viewGroup.getChildCount();
for (int childIndex = 0; childIndex < childCount; childIndex++) {
final View child = viewGroup.getChildAt(childIndex);
if (!containedBeforeIndex(views, child, startIndex)) {
views.add(child);
}
}
}
}
}
/**
* Does a linear search through views for view, limited to maxIndex.
*/
private static boolean containedBeforeIndex(final List<View> views, final View view,
final int maxIndex) {
for (int i = 0; i < maxIndex; i++) {
if (views.get(i) == view) {
return true;
}
}
return false;
}
private static void setSharedElementEpicenter(Transition transition,
final EpicenterView epicenterView) {
if (transition != null) {
transition.setEpicenterCallback(new Transition.EpicenterCallback() {
private Rect mEpicenter;
@Override
public Rect onGetEpicenter(Transition transition) {
if (mEpicenter == null && epicenterView.epicenter != null) {
mEpicenter = getBoundsOnScreen(epicenterView.epicenter);
}
return mEpicenter;
}
});
}
}
private static Rect getBoundsOnScreen(View view) {
Rect epicenter = new Rect();
int[] loc = new int[2];
view.getLocationOnScreen(loc);
// not as good as View.getBoundsOnScreen, but that's not public
epicenter.set(loc[0], loc[1], loc[0] + view.getWidth(), loc[1] + view.getHeight());
return epicenter;
}
private static void captureTransitioningViews(ArrayList<View> transitioningViews, View view) {
if (view.getVisibility() == View.VISIBLE) {
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
if (viewGroup.isTransitionGroup()) {
transitioningViews.add(viewGroup);
} else {
int count = viewGroup.getChildCount();
for (int i = 0; i < count; i++) {
View child = viewGroup.getChildAt(i);
captureTransitioningViews(transitioningViews, child);
}
}
} else {
transitioningViews.add(view);
}
}
}
public static void findNamedViews(Map<String, View> namedViews, View view) {
if (view.getVisibility() == View.VISIBLE) {
String transitionName = view.getTransitionName();
if (transitionName != null) {
namedViews.put(transitionName, view);
}
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
int count = viewGroup.getChildCount();
for (int i = 0; i < count; i++) {
View child = viewGroup.getChildAt(i);
findNamedViews(namedViews, child);
}
}
}
}
public static void cleanupTransitions(final View sceneRoot, final View nonExistentView,
Object enterTransitionObject, final ArrayList<View> enteringViews,
Object exitTransitionObject, final ArrayList<View> exitingViews,
Object sharedElementTransitionObject, final ArrayList<View> sharedElementTargets,
Object overallTransitionObject, final ArrayList<View> hiddenViews,
final Map<String, View> renamedViews) {
final Transition enterTransition = (Transition) enterTransitionObject;
final Transition exitTransition = (Transition) exitTransitionObject;
final Transition sharedElementTransition = (Transition) sharedElementTransitionObject;
final Transition overallTransition = (Transition) overallTransitionObject;
if (overallTransition != null) {
sceneRoot.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
public boolean onPreDraw() {
sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
if (enterTransition != null) {
removeTargets(enterTransition, enteringViews);
}
if (exitTransition != null) {
removeTargets(exitTransition, exitingViews);
}
if (sharedElementTransition != null) {
removeTargets(sharedElementTransition, sharedElementTargets);
}
for (Map.Entry<String, View> entry : renamedViews.entrySet()) {
View view = entry.getValue();
String name = entry.getKey();
view.setTransitionName(name);
}
int numViews = hiddenViews.size();
for (int i = 0; i < numViews; i++) {
overallTransition.excludeTarget(hiddenViews.get(i), false);
}
overallTransition.excludeTarget(nonExistentView, false);
return true;
}
});
}
}
/**
* This method removes the views from transitions that target ONLY those views.
* The views list should match those added in addTargets and should contain
* one view that is not in the view hierarchy (state.nonExistentView).
*/
public static void removeTargets(Object transitionObject, ArrayList<View> views) {
Transition transition = (Transition) transitionObject;
if (transition instanceof TransitionSet) {
TransitionSet set = (TransitionSet) transition;
int numTransitions = set.getTransitionCount();
for (int i = 0; i < numTransitions; i++) {
Transition child = set.getTransitionAt(i);
removeTargets(child, views);
}
} else if (!hasSimpleTarget(transition)) {
List<View> targets = transition.getTargets();
if (targets != null && targets.size() == views.size() &&
targets.containsAll(views)) {
// We have an exact match. We must have added these earlier in addTargets
for (int i = views.size() - 1; i >= 0; i--) {
transition.removeTarget(views.get(i));
}
}
}
}
/**
* This method adds views as targets to the transition, but only if the transition
* doesn't already have a target. It is best for views to contain one View object
* that does not exist in the view hierarchy (state.nonExistentView) so that
* when they are removed later, a list match will suffice to remove the targets.
* Otherwise, if you happened to have targeted the exact views for the transition,
* the removeTargets call will remove them unexpectedly.
*/
public static void addTargets(Object transitionObject, ArrayList<View> views) {
Transition transition = (Transition) transitionObject;
if (transition instanceof TransitionSet) {
TransitionSet set = (TransitionSet) transition;
int numTransitions = set.getTransitionCount();
for (int i = 0; i < numTransitions; i++) {
Transition child = set.getTransitionAt(i);
addTargets(child, views);
}
} else if (!hasSimpleTarget(transition)) {
List<View> targets = transition.getTargets();
if (isNullOrEmpty(targets)) {
// We can just add the target views
int numViews = views.size();
for (int i = 0; i < numViews; i++) {
transition.addTarget(views.get(i));
}
}
}
}
private static boolean hasSimpleTarget(Transition transition) {
return !isNullOrEmpty(transition.getTargetIds()) ||
!isNullOrEmpty(transition.getTargetNames()) ||
!isNullOrEmpty(transition.getTargetTypes());
}
private static boolean isNullOrEmpty(List list) {
return list == null || list.isEmpty();
}
public interface ViewRetriever {
View getView();
}
public static class EpicenterView {
public View epicenter;
}
}