blob: cbb8359fd1ca43472fbe3c3a065c2cc2df6202ce [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.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.transition.Transition;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.Window;
import java.util.ArrayList;
/**
* This ActivityTransitionCoordinator is created by the Activity to manage
* the enter scene and shared element transfer as well as Activity#finishWithTransition
* exiting the Scene and transferring shared elements back to the called Activity.
*/
class EnterTransitionCoordinator extends ActivityTransitionCoordinator
implements ViewTreeObserver.OnPreDrawListener {
private static final String TAG = "EnterTransitionCoordinator";
// The background fade in/out duration. 150ms is pretty quick, but not abrupt.
private static final int FADE_BACKGROUND_DURATION_MS = 150;
/**
* The shared element names sent by the ExitTransitionCoordinator and may be
* shared when exiting back.
*/
private ArrayList<String> mEnteringSharedElementNames;
/**
* The Activity that has created this coordinator. This is used solely to make the
* Window translucent/opaque.
*/
private Activity mActivity;
/**
* True if the Window was opaque at the start and we should make it opaque again after
* enter transitions have completed.
*/
private boolean mWasOpaque;
/**
* During exit, is the background alpha == 0?
*/
private boolean mBackgroundFadedOut;
/**
* During exit, has the shared element transition completed?
*/
private boolean mSharedElementTransitionComplete;
/**
* Has the exit started? We don't want to accidentally exit multiple times. e.g. when
* back is hit twice during the exit animation.
*/
private boolean mExitTransitionStarted;
/**
* Has the exit transition ended?
*/
private boolean mExitTransitionComplete;
/**
* We only want to make the Window transparent and set the background alpha once. After that,
* the Activity won't want the same enter transition.
*/
private boolean mMadeReady;
/**
* True if Window.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS) -- this means that
* enter and exit transitions should be active.
*/
private boolean mSupportsTransition;
/**
* Background alpha animations may complete prior to receiving the callback for
* onTranslucentConversionComplete. If so, we need to immediately call to make the Window
* opaque.
*/
private boolean mMakeOpaque;
public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver) {
super(activity.getWindow());
mActivity = activity;
setRemoteResultReceiver(resultReceiver);
}
public void readyToEnter() {
if (!mMadeReady) {
mMadeReady = true;
mSupportsTransition = getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS);
if (mSupportsTransition) {
Window window = getWindow();
window.getDecorView().getViewTreeObserver().addOnPreDrawListener(this);
mActivity.overridePendingTransition(0, 0);
mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() {
@Override
public void onTranslucentConversionComplete(boolean drawComplete) {
mWasOpaque = true;
if (mMakeOpaque) {
mActivity.convertFromTranslucent();
}
}
});
Drawable background = getDecor().getBackground();
if (background != null) {
window.setBackgroundDrawable(null);
background.setAlpha(0);
window.setBackgroundDrawable(background);
}
}
}
}
@Override
protected void onTakeSharedElements(ArrayList<String> sharedElementNames, Bundle state) {
mEnteringSharedElementNames = new ArrayList<String>();
mEnteringSharedElementNames.addAll(sharedElementNames);
super.onTakeSharedElements(sharedElementNames, state);
}
@Override
protected void sharedElementTransitionComplete(Bundle bundle) {
notifySharedElementTransitionComplete(bundle);
exitAfterSharedElementTransition();
}
@Override
public boolean onPreDraw() {
getWindow().getDecorView().getViewTreeObserver().removeOnPreDrawListener(this);
setEnteringViews(readyEnteringViews());
notifySetListener();
onPrepareRestore();
return false;
}
@Override
public void startExit() {
if (!mExitTransitionStarted) {
mExitTransitionStarted = true;
startExitTransition(mEnteringSharedElementNames);
}
}
@Override
protected Transition getViewsTransition() {
if (!mSupportsTransition) {
return null;
}
return getWindow().getEnterTransition();
}
@Override
protected Transition getSharedElementTransition() {
if (!mSupportsTransition) {
return null;
}
return getWindow().getSharedElementEnterTransition();
}
@Override
protected void onStartEnterTransition(Transition transition, ArrayList<View> enteringViews) {
Drawable background = getDecor().getBackground();
if (background != null) {
ObjectAnimator animator = ObjectAnimator.ofInt(background, "alpha", 255);
animator.setDuration(FADE_BACKGROUND_DURATION_MS);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mMakeOpaque = true;
if (mWasOpaque) {
mActivity.convertFromTranslucent();
}
}
});
animator.start();
} else if (mWasOpaque) {
transition.addListener(new Transition.TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
mMakeOpaque = true;
mActivity.convertFromTranslucent();
}
});
}
super.onStartEnterTransition(transition, enteringViews);
}
public ArrayList<View> readyEnteringViews() {
ArrayList<View> enteringViews = new ArrayList<View>();
getDecor().captureTransitioningViews(enteringViews);
if (getViewsTransition() != null) {
setViewVisibility(enteringViews, View.INVISIBLE);
}
return enteringViews;
}
@Override
protected void startExitTransition(ArrayList<String> sharedElements) {
mMakeOpaque = false;
notifyPrepareRestore();
if (getDecor().getBackground() == null) {
ColorDrawable black = new ColorDrawable(0xFF000000);
getWindow().setBackgroundDrawable(black);
}
if (mWasOpaque) {
mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() {
@Override
public void onTranslucentConversionComplete(boolean drawComplete) {
fadeOutBackground();
}
});
} else {
fadeOutBackground();
}
super.startExitTransition(sharedElements);
}
private void fadeOutBackground() {
ObjectAnimator animator = ObjectAnimator.ofInt(getDecor().getBackground(),
"alpha", 0);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mBackgroundFadedOut = true;
if (mSharedElementTransitionComplete) {
EnterTransitionCoordinator.super.onSharedElementTransitionEnd();
}
}
});
animator.setDuration(FADE_BACKGROUND_DURATION_MS);
animator.start();
}
@Override
protected void onExitTransitionEnd() {
mExitTransitionComplete = true;
exitAfterSharedElementTransition();
super.onExitTransitionEnd();
}
@Override
protected void onSharedElementTransitionEnd() {
mSharedElementTransitionComplete = true;
if (mBackgroundFadedOut) {
super.onSharedElementTransitionEnd();
}
}
@Override
protected boolean allowOverlappingTransitions() {
return getWindow().getAllowEnterTransitionOverlap();
}
private void exitAfterSharedElementTransition() {
if (mSharedElementTransitionComplete && mExitTransitionComplete && mBackgroundFadedOut) {
mActivity.finish();
if (mSupportsTransition) {
mActivity.overridePendingTransition(0, 0);
}
notifyExitTransitionComplete();
clearConnections();
}
}
}