diff options
Diffstat (limited to 'core/java/android/transition/Crossfade.java')
-rw-r--r-- | core/java/android/transition/Crossfade.java | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/core/java/android/transition/Crossfade.java b/core/java/android/transition/Crossfade.java new file mode 100644 index 0000000..69ce872 --- /dev/null +++ b/core/java/android/transition/Crossfade.java @@ -0,0 +1,296 @@ +/* + * 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); + } +} |