blob: 3c1455b0a4b8b45628b1741973ff3b9c267cef95 [file] [log] [blame]
/*
* 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.app;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import android.transition.Transition;
import android.transition.TransitionManager;
import android.transition.TransitionSet;
import android.util.ArrayMap;
import android.util.Pair;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroupOverlay;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.widget.ImageView;
import java.util.ArrayList;
import java.util.Collection;
/**
* Base class for ExitTransitionCoordinator and EnterTransitionCoordinator, classes
* that manage activity transitions and the communications coordinating them between
* Activities. The ExitTransitionCoordinator is created in the
* ActivityOptions#makeSceneTransitionAnimation. The EnterTransitionCoordinator
* is created by ActivityOptions#createEnterActivityTransition by Activity when the window is
* attached.
*
* Typical startActivity goes like this:
* 1) ExitTransitionCoordinator created with ActivityOptions#makeSceneTransitionAnimation
* 2) Activity#startActivity called and that calls startExit() through
* ActivityOptions#dispatchStartExit
* - Exit transition starts by setting transitioning Views to INVISIBLE
* 3) Launched Activity starts, creating an EnterTransitionCoordinator.
* - The Window is made translucent
* - The Window background alpha is set to 0
* - The transitioning views are made INVISIBLE
* - MSG_SET_LISTENER is sent back to the ExitTransitionCoordinator.
* 4) The shared element transition completes.
* - MSG_TAKE_SHARED_ELEMENTS is sent to the EnterTransitionCoordinator
* 5) The MSG_TAKE_SHARED_ELEMENTS is received by the EnterTransitionCoordinator.
* - Shared elements are made VISIBLE
* - Shared elements positions and size are set to match the end state of the calling
* Activity.
* - The shared element transition is started
* - If the window allows overlapping transitions, the views transition is started by setting
* the entering Views to VISIBLE and the background alpha is animated to opaque.
* - MSG_HIDE_SHARED_ELEMENTS is sent to the ExitTransitionCoordinator
* 6) MSG_HIDE_SHARED_ELEMENTS is received by the ExitTransitionCoordinator
* - The shared elements are made INVISIBLE
* 7) The exit transition completes in the calling Activity.
* - MSG_EXIT_TRANSITION_COMPLETE is sent to the EnterTransitionCoordinator.
* 8) The MSG_EXIT_TRANSITION_COMPLETE is received by the EnterTransitionCoordinator.
* - If the window doesn't allow overlapping enter transitions, the enter transition is started
* by setting entering views to VISIBLE and the background is animated to opaque.
* 9) The background opacity animation completes.
* - The window is made opaque
* 10) The calling Activity gets an onStop() call
* - onActivityStopped() is called and all exited Views are made VISIBLE.
*
* Typical finishWithTransition goes like this:
* 1) finishWithTransition() calls startExit()
* - The Window start transitioning to Translucent
* - If no background exists, a black background is substituted
* - MSG_PREPARE_RESTORE is sent to the ExitTransitionCoordinator
* - The shared elements in the scene are matched against those shared elements
* that were sent by comparing the names.
* - The exit transition is started by setting Views to INVISIBLE.
* 2) MSG_PREPARE_RESTORE is received by the EnterTransitionCoordinator
* - All transitioning views are made VISIBLE to reverse what was done when onActivityStopped()
* was called
* 3) The Window is made translucent and a callback is received
* - The background alpha is animated to 0
* 4) The background alpha animation completes
* 5) The shared element transition completes
* - After both 4 & 5 complete, MSG_TAKE_SHARED_ELEMENTS is sent to the
* ExitTransitionCoordinator
* 6) MSG_TAKE_SHARED_ELEMENTS is received by ExitTransitionCoordinator
* - Shared elements are made VISIBLE
* - Shared elements positions and size are set to match the end state of the calling
* Activity.
* - The shared element transition is started
* - If the window allows overlapping transitions, the views transition is started by setting
* the entering Views to VISIBLE.
* - MSG_HIDE_SHARED_ELEMENTS is sent to the EnterTransitionCoordinator
* 7) MSG_HIDE_SHARED_ELEMENTS is received by the EnterTransitionCoordinator
* - The shared elements are made INVISIBLE
* 8) The exit transition completes in the finishing Activity.
* - MSG_EXIT_TRANSITION_COMPLETE is sent to the ExitTransitionCoordinator.
* - finish() is called on the exiting Activity
* 9) The MSG_EXIT_TRANSITION_COMPLETE is received by the ExitTransitionCoordinator.
* - If the window doesn't allow overlapping enter transitions, the enter transition is started
* by setting entering views to VISIBLE.
*/
abstract class ActivityTransitionCoordinator extends ResultReceiver {
private static final String TAG = "ActivityTransitionCoordinator";
/**
* The names of shared elements that are transitioned to the started Activity.
* This is also the name of shared elements that the started Activity accepted.
*/
public static final String KEY_SHARED_ELEMENT_NAMES = "android:shared_element_names";
public static final String KEY_SHARED_ELEMENT_STATE = "android:shared_element_state";
/**
* For Activity transitions, the called Activity's listener to receive calls
* when transitions complete.
*/
static final String KEY_TRANSITION_RESULTS_RECEIVER = "android:transitionTargetListener";
private static final String KEY_SCREEN_X = "shared_element:screenX";
private static final String KEY_SCREEN_Y = "shared_element:screenY";
private static final String KEY_TRANSLATION_Z = "shared_element:translationZ";
private static final String KEY_WIDTH = "shared_element:width";
private static final String KEY_HEIGHT = "shared_element:height";
private static final String KEY_NAME = "shared_element:name";
private static final String KEY_BITMAP = "shared_element:bitmap";
/**
* Sent by the exiting coordinator (either EnterTransitionCoordinator
* or ExitTransitionCoordinator) after the shared elements have
* become stationary (shared element transition completes). This tells
* the remote coordinator to take control of the shared elements and
* that animations may begin. The remote Activity won't start entering
* until this message is received, but may wait for
* MSG_EXIT_TRANSITION_COMPLETE if allowOverlappingTransitions() is true.
*/
public static final int MSG_SET_LISTENER = 100;
/**
* Sent by the entering coordinator to tell the exiting coordinator
* to hide its shared elements after it has started its shared
* element transition. This is temporary until the
* interlock of shared elements is figured out.
*/
public static final int MSG_HIDE_SHARED_ELEMENTS = 101;
/**
* Sent by the EnterTransitionCoordinator to tell the
* ExitTransitionCoordinator to hide all of its exited views after
* MSG_ACTIVITY_STOPPED has caused them all to show.
*/
public static final int MSG_PREPARE_RESTORE = 102;
/**
* Sent by the exiting Activity in ActivityOptions#dispatchActivityStopped
* to leave the Activity in a good state after it has been hidden.
*/
public static final int MSG_ACTIVITY_STOPPED = 103;
/**
* Sent by the exiting coordinator (either EnterTransitionCoordinator
* or ExitTransitionCoordinator) after the shared elements have
* become stationary (shared element transition completes). This tells
* the remote coordinator to take control of the shared elements and
* that animations may begin. The remote Activity won't start entering
* until this message is received, but may wait for
* MSG_EXIT_TRANSITION_COMPLETE if allowOverlappingTransitions() is true.
*/
public static final int MSG_TAKE_SHARED_ELEMENTS = 104;
/**
* Sent by the exiting coordinator (either
* EnterTransitionCoordinator or ExitTransitionCoordinator) after
* the exiting Views have finished leaving the scene. This will
* be ignored if allowOverlappingTransitions() is true on the
* remote coordinator. If it is false, it will trigger the enter
* transition to start.
*/
public static final int MSG_EXIT_TRANSITION_COMPLETE = 105;
/**
* Sent by Activity#startActivity to begin the exit transition.
*/
public static final int MSG_START_EXIT_TRANSITION = 106;
private Window mWindow;
private ArrayList<View> mSharedElements = new ArrayList<View>();
private ArrayList<String> mTargetSharedNames = new ArrayList<String>();
private ActivityOptions.ActivityTransitionListener mListener =
new ActivityOptions.ActivityTransitionListener();
private ArrayList<View> mEnteringViews;
private ResultReceiver mRemoteResultReceiver;
private boolean mNotifiedSharedElementTransitionComplete;
private boolean mNotifiedExitTransitionComplete;
private boolean mSharedElementTransitionStarted;
private FixedEpicenterCallback mEpicenterCallback = new FixedEpicenterCallback();
private Transition.TransitionListener mSharedElementListener =
new Transition.TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
transition.removeListener(this);
onSharedElementTransitionEnd();
}
};
private Transition.TransitionListener mExitListener =
new Transition.TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
transition.removeListener(this);
onExitTransitionEnd();
}
};
public ActivityTransitionCoordinator(Window window)
{
super(new Handler());
mWindow = window;
}
// -------------------- ResultsReceiver Overrides ----------------------
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
switch (resultCode) {
case MSG_SET_LISTENER:
ResultReceiver resultReceiver
= resultData.getParcelable(KEY_TRANSITION_RESULTS_RECEIVER);
setRemoteResultReceiver(resultReceiver);
onSetResultReceiver();
break;
case MSG_HIDE_SHARED_ELEMENTS:
onHideSharedElements();
break;
case MSG_PREPARE_RESTORE:
onPrepareRestore();
break;
case MSG_EXIT_TRANSITION_COMPLETE:
if (!mSharedElementTransitionStarted) {
send(resultCode, resultData);
} else {
onRemoteSceneExitComplete();
}
break;
case MSG_TAKE_SHARED_ELEMENTS:
ArrayList<String> sharedElementNames
= resultData.getStringArrayList(KEY_SHARED_ELEMENT_NAMES);
Bundle sharedElementState = resultData.getBundle(KEY_SHARED_ELEMENT_STATE);
onTakeSharedElements(sharedElementNames, sharedElementState);
break;
case MSG_ACTIVITY_STOPPED:
onActivityStopped();
break;
case MSG_START_EXIT_TRANSITION:
startExit();
break;
}
}
// -------------------- calls that can be overridden by subclasses --------------------
/**
* Called when MSG_SET_LISTENER is received. This will only be received by
* ExitTransitionCoordinator.
*/
protected void onSetResultReceiver() {}
/**
* Called when MSG_HIDE_SHARED_ELEMENTS is received
*/
protected void onHideSharedElements() {
setViewVisibility(getSharedElements(), View.INVISIBLE);
mListener.onSharedElementTransferred(getSharedElementNames(), getSharedElements());
}
/**
* Called when MSG_PREPARE_RESTORE is called. This will only be received by
* ExitTransitionCoordinator.
*/
protected void onPrepareRestore() {
mListener.onEnterReady();
}
/**
* Called when MSG_EXIT_TRANSITION_COMPLETE is received -- the remote coordinator has
* completed its exit transition. This can be called by the ExitTransitionCoordinator when
* starting an Activity or EnterTransitionCoordinator when called with finishWithTransition.
*/
protected void onRemoteSceneExitComplete() {
if (!allowOverlappingTransitions()) {
Transition transition = beginTransition(mEnteringViews, false, true, true);
onStartEnterTransition(transition, mEnteringViews);
}
mListener.onRemoteExitComplete();
}
/**
* Called when MSG_TAKE_SHARED_ELEMENTS is received. This means that the shared elements are
* in a stable state and ready to move to the Window.
* @param sharedElementNames The names of the shared elements to move.
* @param state Contains the shared element states (size & position)
*/
protected void onTakeSharedElements(ArrayList<String> sharedElementNames, Bundle state) {
setSharedElements();
reconcileSharedElements(sharedElementNames);
mEnteringViews.removeAll(mSharedElements);
final ArrayList<View> accepted = new ArrayList<View>();
final ArrayList<View> rejected = new ArrayList<View>();
createSharedElementImages(accepted, rejected, sharedElementNames, state);
setSharedElementState(state, accepted);
handleRejected(rejected);
if (getViewsTransition() != null) {
setViewVisibility(mEnteringViews, View.INVISIBLE);
}
setViewVisibility(mSharedElements, View.VISIBLE);
Transition transition = beginTransition(mEnteringViews, true, allowOverlappingTransitions(),
true);
if (allowOverlappingTransitions()) {
onStartEnterTransition(transition, mEnteringViews);
}
mRemoteResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null);
}
/**
* Called when MSG_ACTIVITY_STOPPED is received. This is received when Activity.onStop is
* called after running startActivity* is called using an Activity Transition.
*/
protected void onActivityStopped() {}
/**
* Called when the start transition is ready to run. This may be immediately after
* MSG_TAKE_SHARED_ELEMENTS or MSG_EXIT_TRANSITION_COMPLETE, depending on whether
* overlapping transitions are allowed.
* @param transition The transition currently started.
* @param enteringViews The views entering the scene. This won't include shared elements.
*/
protected void onStartEnterTransition(Transition transition, ArrayList<View> enteringViews) {
if (getViewsTransition() != null) {
setViewVisibility(enteringViews, View.VISIBLE);
}
mEnteringViews = null;
mListener.onStartEnterTransition(getSharedElementNames(), getSharedElements());
}
/**
* Called when the exit transition has started.
* @param exitingViews The views leaving the scene. This won't include shared elements.
*/
protected void onStartExitTransition(ArrayList<View> exitingViews) {}
/**
* Called during the exit when the shared element transition has completed.
*/
protected void onSharedElementTransitionEnd() {
Bundle bundle = new Bundle();
int[] tempLoc = new int[2];
for (int i = 0; i < mSharedElements.size(); i++) {
View sharedElement = mSharedElements.get(i);
String name = mTargetSharedNames.get(i);
captureSharedElementState(sharedElement, name, bundle, tempLoc);
}
Bundle allValues = new Bundle();
allValues.putStringArrayList(KEY_SHARED_ELEMENT_NAMES, getSharedElementNames());
allValues.putBundle(KEY_SHARED_ELEMENT_STATE, bundle);
sharedElementTransitionComplete(allValues);
mListener.onSharedElementExitTransitionComplete();
}
/**
* Called after the shared element transition is complete to pass the shared element state
* to the remote coordinator.
* @param bundle The Bundle to send to the coordinator containing the shared element state.
*/
protected abstract void sharedElementTransitionComplete(Bundle bundle);
/**
* Called when the exit transition finishes.
*/
protected void onExitTransitionEnd() {
mListener.onExitTransitionComplete();
}
/**
* Called to start the exit transition. Launched from ActivityOptions#dispatchStartExit
*/
protected abstract void startExit();
/**
* A non-null transition indicates that the Views of the Window should be made INVISIBLE.
* @return The Transition used to cause transitioning views to either enter or exit the scene.
*/
protected abstract Transition getViewsTransition();
/**
* @return The Transition used to move the shared elements from the start position and size
* to the end position and size.
*/
protected abstract Transition getSharedElementTransition();
/**
* @return When the enter transition should overlap with the exit transition of the
* remote controller.
*/
protected abstract boolean allowOverlappingTransitions();
// called by subclasses
protected void notifySharedElementTransitionComplete(Bundle sharedElements) {
if (!mNotifiedSharedElementTransitionComplete) {
mNotifiedSharedElementTransitionComplete = true;
mRemoteResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, sharedElements);
}
}
protected void notifyExitTransitionComplete() {
if (!mNotifiedExitTransitionComplete) {
mNotifiedExitTransitionComplete = true;
mRemoteResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null);
}
}
protected void notifyPrepareRestore() {
mRemoteResultReceiver.send(MSG_PREPARE_RESTORE, null);
}
protected void setRemoteResultReceiver(ResultReceiver resultReceiver) {
mRemoteResultReceiver = resultReceiver;
}
protected void notifySetListener() {
Bundle bundle = new Bundle();
bundle.putParcelable(KEY_TRANSITION_RESULTS_RECEIVER, this);
mRemoteResultReceiver.send(MSG_SET_LISTENER, bundle);
}
protected void setEnteringViews(ArrayList<View> views) {
mEnteringViews = views;
}
protected void setSharedElements() {
Pair<View, String>[] sharedElements = mListener.getSharedElementsMapping();
mSharedElements.clear();
mTargetSharedNames.clear();
if (sharedElements == null) {
ArrayMap<String, View> map = new ArrayMap<String, View>();
if (getViewsTransition() != null) {
setViewVisibility(mEnteringViews, View.VISIBLE);
}
getDecor().findSharedElements(map);
if (getViewsTransition() != null) {
setViewVisibility(mEnteringViews, View.INVISIBLE);
}
for (int i = 0; i < map.size(); i++) {
View view = map.valueAt(i);
String name = map.keyAt(i);
mSharedElements.add(view);
mTargetSharedNames.add(name);
}
} else {
for (int i = 0; i < sharedElements.length; i++) {
Pair<View, String> viewStringPair = sharedElements[i];
View view = viewStringPair.first;
String name = viewStringPair.second;
mSharedElements.add(view);
mTargetSharedNames.add(name);
}
}
}
protected ArrayList<View> getSharedElements() {
return mSharedElements;
}
protected ArrayList<String> getSharedElementNames() {
return mTargetSharedNames;
}
protected Window getWindow() {
return mWindow;
}
protected ViewGroup getDecor() {
return (mWindow == null) ? null : (ViewGroup) mWindow.getDecorView();
}
protected void startExitTransition(ArrayList<String> sharedElements) {
setSharedElements();
reconcileSharedElements(sharedElements);
ArrayList<View> transitioningViews = captureTransitioningViews();
beginTransition(transitioningViews, true, true, false);
onStartExitTransition(transitioningViews);
if (getViewsTransition() != null) {
setViewVisibility(transitioningViews, View.INVISIBLE);
}
mListener.onStartExitTransition(getSharedElementNames(), getSharedElements());
}
protected void clearConnections() {
mRemoteResultReceiver = null;
}
// public API
public void setActivityTransitionListener(ActivityOptions.ActivityTransitionListener listener) {
if (listener == null) {
mListener = new ActivityOptions.ActivityTransitionListener();
} else {
mListener = listener;
}
}
// private methods
private Transition configureTransition(Transition transition) {
if (transition != null) {
transition = transition.clone();
transition.setEpicenterCallback(mEpicenterCallback);
}
return transition;
}
private void reconcileSharedElements(ArrayList<String> sharedElementNames) {
// keep only those that are in sharedElementNames.
int numSharedElements = sharedElementNames.size();
int targetIndex = 0;
for (int i = 0; i < numSharedElements; i++) {
String name = sharedElementNames.get(i);
int index = mTargetSharedNames.indexOf(name);
if (index >= 0) {
// Swap the items at the indexes if necessary.
if (index != targetIndex) {
View temp = mSharedElements.get(index);
mSharedElements.set(index, mSharedElements.get(targetIndex));
mSharedElements.set(targetIndex, temp);
mTargetSharedNames.set(index, mTargetSharedNames.get(targetIndex));
mTargetSharedNames.set(targetIndex, name);
}
targetIndex++;
}
}
for (int i = mSharedElements.size() - 1; i >= targetIndex; i--) {
mSharedElements.remove(i);
mTargetSharedNames.remove(i);
}
Rect epicenter = null;
if (!mTargetSharedNames.isEmpty()
&& mTargetSharedNames.get(0).equals(sharedElementNames.get(0))) {
epicenter = calcEpicenter(mSharedElements.get(0));
}
mEpicenterCallback.setEpicenter(epicenter);
}
private void setSharedElementState(Bundle sharedElementState,
final ArrayList<View> acceptedOverlayViews) {
final int[] tempLoc = new int[2];
if (sharedElementState != null) {
for (int i = 0; i < mSharedElements.size(); i++) {
View sharedElement = mSharedElements.get(i);
View parent = (View) sharedElement.getParent();
parent.getLocationOnScreen(tempLoc);
String name = mTargetSharedNames.get(i);
setSharedElementState(sharedElement, name, sharedElementState, tempLoc);
sharedElement.requestLayout();
}
}
mListener.onCaptureSharedElementStart(mTargetSharedNames, mSharedElements,
acceptedOverlayViews);
getDecor().getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
mListener.onCaptureSharedElementEnd(mTargetSharedNames, mSharedElements,
acceptedOverlayViews);
mSharedElementTransitionStarted = true;
return true;
}
}
);
}
/**
* Sets the captured values from a previous
* {@link #captureSharedElementState(android.view.View, String, android.os.Bundle, int[])}
* @param view The View to apply placement changes to.
* @param name The shared element name given from the source Activity.
* @param transitionArgs A <code>Bundle</code> containing all placementinformation for named
* shared elements in the scene.
* @param parentLoc The x and y coordinates of the parent's screen position.
*/
private static void setSharedElementState(View view, String name, Bundle transitionArgs,
int[] parentLoc) {
Bundle sharedElementBundle = transitionArgs.getBundle(name);
if (sharedElementBundle == null) {
return;
}
float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z);
view.setTranslationZ(z);
int x = sharedElementBundle.getInt(KEY_SCREEN_X);
int y = sharedElementBundle.getInt(KEY_SCREEN_Y);
int width = sharedElementBundle.getInt(KEY_WIDTH);
int height = sharedElementBundle.getInt(KEY_HEIGHT);
int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
view.measure(widthSpec, heightSpec);
int left = x - parentLoc[0];
int top = y - parentLoc[1];
int right = left + width;
int bottom = top + height;
view.layout(left, top, right, bottom);
}
/**
* Captures placement information for Views with a shared element name for
* Activity Transitions.
* @param view The View to capture the placement information for.
* @param name The shared element name in the target Activity to apply the placement
* information for.
* @param transitionArgs Bundle to store shared element placement information.
* @param tempLoc A temporary int[2] for capturing the current location of views.
* @see #setSharedElementState(android.view.View, String, android.os.Bundle, int[])
*/
private static void captureSharedElementState(View view, String name, Bundle transitionArgs,
int[] tempLoc) {
Bundle sharedElementBundle = new Bundle();
view.getLocationOnScreen(tempLoc);
float scaleX = view.getScaleX();
sharedElementBundle.putInt(KEY_SCREEN_X, tempLoc[0]);
int width = Math.round(view.getWidth() * scaleX);
sharedElementBundle.putInt(KEY_WIDTH, width);
float scaleY = view.getScaleY();
sharedElementBundle.putInt(KEY_SCREEN_Y, tempLoc[1]);
int height= Math.round(view.getHeight() * scaleY);
sharedElementBundle.putInt(KEY_HEIGHT, height);
sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ());
sharedElementBundle.putString(KEY_NAME, view.getSharedElementName());
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
sharedElementBundle.putParcelable(KEY_BITMAP, bitmap);
transitionArgs.putBundle(name, sharedElementBundle);
}
private static Rect calcEpicenter(View view) {
int[] loc = new int[2];
view.getLocationOnScreen(loc);
int left = loc[0] + Math.round(view.getTranslationX());
int top = loc[1] + Math.round(view.getTranslationY());
int right = left + view.getWidth();
int bottom = top + view.getHeight();
return new Rect(left, top, right, bottom);
}
public static void setViewVisibility(Collection<View> views, int visibility) {
if (views != null) {
for (View view : views) {
view.setVisibility(visibility);
}
}
}
private static Transition addTransitionTargets(Transition transition, Collection<View> views) {
if (transition == null || views == null || views.isEmpty()) {
return null;
}
TransitionSet set = new TransitionSet();
set.addTransition(transition.clone());
if (views != null) {
for (View view: views) {
set.addTarget(view);
}
}
return set;
}
private ArrayList<View> captureTransitioningViews() {
if (getViewsTransition() == null) {
return null;
}
ArrayList<View> transitioningViews = new ArrayList<View>();
getDecor().captureTransitioningViews(transitioningViews);
transitioningViews.removeAll(getSharedElements());
return transitioningViews;
}
private Transition getSharedElementTransition(boolean isEnter) {
Transition transition = getSharedElementTransition();
if (transition == null) {
return null;
}
transition = configureTransition(transition);
if (!isEnter) {
transition.addListener(mSharedElementListener);
}
return transition;
}
private Transition getViewsTransition(ArrayList<View> transitioningViews, boolean isEnter) {
Transition transition = getViewsTransition();
if (transition == null) {
return null;
}
transition = configureTransition(transition);
if (!isEnter) {
transition.addListener(mExitListener);
}
return addTransitionTargets(transition, transitioningViews);
}
private Transition beginTransition(ArrayList<View> transitioningViews,
boolean transitionSharedElement, boolean transitionViews, boolean isEnter) {
Transition sharedElementTransition = null;
if (transitionSharedElement) {
sharedElementTransition = getSharedElementTransition(isEnter);
if (!isEnter && sharedElementTransition == null) {
onSharedElementTransitionEnd();
}
}
Transition viewsTransition = null;
if (transitionViews) {
viewsTransition = getViewsTransition(transitioningViews, isEnter);
if (!isEnter && viewsTransition == null) {
onExitTransitionEnd();
}
}
Transition transition = null;
if (sharedElementTransition == null) {
transition = viewsTransition;
} else if (viewsTransition == null) {
transition = sharedElementTransition;
} else {
TransitionSet set = new TransitionSet();
set.addTransition(sharedElementTransition);
set.addTransition(viewsTransition);
transition = set;
}
if (transition != null) {
TransitionManager.beginDelayedTransition(getDecor(), transition);
if (transitionSharedElement && !mSharedElements.isEmpty()) {
mSharedElements.get(0).invalidate();
} else if (transitionViews && !transitioningViews.isEmpty()) {
transitioningViews.get(0).invalidate();
}
}
return transition;
}
private void handleRejected(final ArrayList<View> rejected) {
int numRejected = rejected.size();
if (numRejected == 0) {
return;
}
boolean rejectionHandled = mListener.handleRejectedSharedElements(rejected);
if (rejectionHandled) {
return;
}
ViewGroupOverlay overlay = getDecor().getOverlay();
ObjectAnimator animator = null;
for (int i = 0; i < numRejected; i++) {
View view = rejected.get(i);
overlay.add(view);
animator = ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0);
animator.start();
}
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
ViewGroupOverlay overlay = getDecor().getOverlay();
for (int i = rejected.size() - 1; i >= 0; i--) {
overlay.remove(rejected.get(i));
}
}
});
}
private void createSharedElementImages(ArrayList<View> accepted, ArrayList<View> rejected,
ArrayList<String> sharedElementNames, Bundle state) {
int numSharedElements = sharedElementNames.size();
Context context = getWindow().getContext();
int[] parentLoc = new int[2];
getDecor().getLocationOnScreen(parentLoc);
for (int i = 0; i < numSharedElements; i++) {
String name = sharedElementNames.get(i);
Bundle sharedElementBundle = state.getBundle(name);
if (sharedElementBundle != null) {
Bitmap bitmap = sharedElementBundle.getParcelable(KEY_BITMAP);
ImageView imageView = new ImageView(context);
imageView.setId(com.android.internal.R.id.shared_element);
imageView.setScaleType(ImageView.ScaleType.CENTER);
imageView.setImageBitmap(bitmap);
imageView.setSharedElementName(name);
setSharedElementState(imageView, name, state, parentLoc);
if (mTargetSharedNames.contains(name)) {
accepted.add(imageView);
} else {
rejected.add(imageView);
}
}
}
}
private static class FixedEpicenterCallback extends Transition.EpicenterCallback {
private Rect mEpicenter;
public void setEpicenter(Rect epicenter) { mEpicenter = epicenter; }
@Override
public Rect getEpicenter(Transition transition) {
return mEpicenter;
}
}
}