blob: 69ce872a24fbc9c5e6eb685259fa86ddd0907f61 [file] [log] [blame]
/*
* Copyright (C) 2013 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.transition;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.util.Log;
import android.view.SurfaceView;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOverlay;
import java.util.Map;
/**
* This transition captures bitmap representations of target views before and
* after the scene change and fades between them.
*
* <p>Note: This transition is not compatible with {@link TextureView}
* or {@link SurfaceView}.</p>
*
* @hide
*/
public class Crossfade extends Transition {
// TODO: Add a hook that lets a Transition call user code to query whether it should run on
// a given target view. This would save bitmap comparisons in this transition, for example.
private static final String LOG_TAG = "Crossfade";
private static final String PROPNAME_BITMAP = "android:crossfade:bitmap";
private static final String PROPNAME_DRAWABLE = "android:crossfade:drawable";
private static final String PROPNAME_BOUNDS = "android:crossfade:bounds";
private static RectEvaluator sRectEvaluator = new RectEvaluator();
private int mFadeBehavior = FADE_BEHAVIOR_REVEAL;
private int mResizeBehavior = RESIZE_BEHAVIOR_SCALE;
/**
* Flag specifying that the fading animation should cross-fade
* between the old and new representation of all affected target
* views. This means that the old representation will fade out
* while the new one fades in. This effect may work well on views
* without solid backgrounds, such as TextViews.
*
* @see #setFadeBehavior(int)
*/
public static final int FADE_BEHAVIOR_CROSSFADE = 0;
/**
* Flag specifying that the fading animation should reveal the
* new representation of all affected target views. This means
* that the old representation will fade out, gradually
* revealing the new representation, which remains opaque
* the whole time. This effect may work well on views
* with solid backgrounds, such as ImageViews.
*
* @see #setFadeBehavior(int)
*/
public static final int FADE_BEHAVIOR_REVEAL = 1;
/**
* Flag specifying that the fading animation should first fade
* out the original representation completely and then fade in the
* new one. This effect may be more suitable than the other
* fade behaviors for views with.
*
* @see #setFadeBehavior(int)
*/
public static final int FADE_BEHAVIOR_OUT_IN = 2;
/**
* Flag specifying that the transition should not animate any
* changes in size between the old and new target views.
* This means that no scaling will take place as a result of
* this transition
*
* @see #setResizeBehavior(int)
*/
public static final int RESIZE_BEHAVIOR_NONE = 0;
/**
* Flag specifying that the transition should animate any
* changes in size between the old and new target views.
* This means that the animation will scale the start/end
* representations of affected views from the starting size
* to the ending size over the course of the animation.
* This effect may work well on images, but is not recommended
* for text.
*
* @see #setResizeBehavior(int)
*/
public static final int RESIZE_BEHAVIOR_SCALE = 1;
// TODO: Add fade/resize behaviors to xml resources
/**
* Sets the type of fading animation that will be run, one of
* {@link #FADE_BEHAVIOR_CROSSFADE} and {@link #FADE_BEHAVIOR_REVEAL}.
*
* @param fadeBehavior The type of fading animation to use when this
* transition is run.
*/
public Crossfade setFadeBehavior(int fadeBehavior) {
if (fadeBehavior >= FADE_BEHAVIOR_CROSSFADE && fadeBehavior <= FADE_BEHAVIOR_OUT_IN) {
mFadeBehavior = fadeBehavior;
}
return this;
}
/**
* Returns the fading behavior of the animation.
*
* @return This crossfade object.
* @see #setFadeBehavior(int)
*/
public int getFadeBehavior() {
return mFadeBehavior;
}
/**
* Sets the type of resizing behavior that will be used during the
* transition animation, one of {@link #RESIZE_BEHAVIOR_NONE} and
* {@link #RESIZE_BEHAVIOR_SCALE}.
*
* @param resizeBehavior The type of resizing behavior to use when this
* transition is run.
*/
public Crossfade setResizeBehavior(int resizeBehavior) {
if (resizeBehavior >= RESIZE_BEHAVIOR_NONE && resizeBehavior <= RESIZE_BEHAVIOR_SCALE) {
mResizeBehavior = resizeBehavior;
}
return this;
}
/**
* Returns the resizing behavior of the animation.
*
* @return This crossfade object.
* @see #setResizeBehavior(int)
*/
public int getResizeBehavior() {
return mResizeBehavior;
}
@Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
TransitionValues endValues) {
if (startValues == null || endValues == null) {
return null;
}
final boolean useParentOverlay = mFadeBehavior != FADE_BEHAVIOR_REVEAL;
final View view = endValues.view;
Map<String, Object> startVals = startValues.values;
Map<String, Object> endVals = endValues.values;
Rect startBounds = (Rect) startVals.get(PROPNAME_BOUNDS);
Rect endBounds = (Rect) endVals.get(PROPNAME_BOUNDS);
Bitmap startBitmap = (Bitmap) startVals.get(PROPNAME_BITMAP);
Bitmap endBitmap = (Bitmap) endVals.get(PROPNAME_BITMAP);
final BitmapDrawable startDrawable = (BitmapDrawable) startVals.get(PROPNAME_DRAWABLE);
final BitmapDrawable endDrawable = (BitmapDrawable) endVals.get(PROPNAME_DRAWABLE);
if (Transition.DBG) {
Log.d(LOG_TAG, "StartBitmap.sameAs(endBitmap) = " + startBitmap.sameAs(endBitmap) +
" for start, end: " + startBitmap + ", " + endBitmap);
}
if (startDrawable != null && endDrawable != null && !startBitmap.sameAs(endBitmap)) {
ViewOverlay overlay = useParentOverlay ?
((ViewGroup) view.getParent()).getOverlay() : view.getOverlay();
if (mFadeBehavior == FADE_BEHAVIOR_REVEAL) {
overlay.add(endDrawable);
}
overlay.add(startDrawable);
// The transition works by placing the end drawable under the start drawable and
// gradually fading out the start drawable. So it's not really a cross-fade, but rather
// a reveal of the end scene over time. Also, animate the bounds of both drawables
// to mimic the change in the size of the view itself between scenes.
ObjectAnimator anim;
if (mFadeBehavior == FADE_BEHAVIOR_OUT_IN) {
// Fade out completely halfway through the transition
anim = ObjectAnimator.ofInt(startDrawable, "alpha", 255, 0, 0);
} else {
anim = ObjectAnimator.ofInt(startDrawable, "alpha", 0);
}
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// TODO: some way to auto-invalidate views based on drawable changes? callbacks?
view.invalidate(startDrawable.getBounds());
}
});
ObjectAnimator anim1 = null;
if (mFadeBehavior == FADE_BEHAVIOR_OUT_IN) {
// start fading in halfway through the transition
anim1 = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 0, 1);
} else if (mFadeBehavior == FADE_BEHAVIOR_CROSSFADE) {
anim1 = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1);
}
if (Transition.DBG) {
Log.d(LOG_TAG, "Crossfade: created anim " + anim + " for start, end values " +
startValues + ", " + endValues);
}
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
ViewOverlay overlay = useParentOverlay ?
((ViewGroup) view.getParent()).getOverlay() : view.getOverlay();
overlay.remove(startDrawable);
if (mFadeBehavior == FADE_BEHAVIOR_REVEAL) {
overlay.remove(endDrawable);
}
}
});
AnimatorSet set = new AnimatorSet();
set.playTogether(anim);
if (anim1 != null) {
set.playTogether(anim1);
}
if (mResizeBehavior == RESIZE_BEHAVIOR_SCALE && !startBounds.equals(endBounds)) {
if (Transition.DBG) {
Log.d(LOG_TAG, "animating from startBounds to endBounds: " +
startBounds + ", " + endBounds);
}
Animator anim2 = ObjectAnimator.ofObject(startDrawable, "bounds",
sRectEvaluator, startBounds, endBounds);
set.playTogether(anim2);
if (mResizeBehavior == RESIZE_BEHAVIOR_SCALE) {
// TODO: How to handle resizing with a CROSSFADE (vs. REVEAL) effect
// when we are animating the view directly?
Animator anim3 = ObjectAnimator.ofObject(endDrawable, "bounds",
sRectEvaluator, startBounds, endBounds);
set.playTogether(anim3);
}
}
return set;
} else {
return null;
}
}
private void captureValues(TransitionValues transitionValues) {
View view = transitionValues.view;
Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight());
if (mFadeBehavior != FADE_BEHAVIOR_REVEAL) {
bounds.offset(view.getLeft(), view.getTop());
}
transitionValues.values.put(PROPNAME_BOUNDS, bounds);
if (Transition.DBG) {
Log.d(LOG_TAG, "Captured bounds " + transitionValues.values.get(PROPNAME_BOUNDS));
}
Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
Bitmap.Config.ARGB_8888);
if (view instanceof TextureView) {
bitmap = ((TextureView) view).getBitmap();
} else {
Canvas c = new Canvas(bitmap);
view.draw(c);
}
transitionValues.values.put(PROPNAME_BITMAP, bitmap);
// TODO: I don't have resources, can't call the non-deprecated method?
BitmapDrawable drawable = new BitmapDrawable(bitmap);
// TODO: lrtb will be wrong if the view has transXY set
drawable.setBounds(bounds);
transitionValues.values.put(PROPNAME_DRAWABLE, drawable);
}
@Override
public void captureStartValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
@Override
public void captureEndValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
}