Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit faebd8f0 authored by Chet Haase's avatar Chet Haase
Browse files

First draft of Scenes & Transitions feature

This checkin has preliminary API (in flux, definitely changes still
to be made) and implementation for a new "Scenes & Transitions" feature.
The current implementation allows you to define different Scenes
(via layout resource IDs or callbacks) and Transitions to be used when
changing to those scenes. By default, scene changes will use AutoTransition,
which generally does the right thing.

There are no overview docs or tutorials yet. The best way to learn how things
work is to see the code for the various tests in
frameworks/base/tests/TransitionTests.

Expect the API to change. Expect the implementation to change (mostly to add
more functionality). Expect bugs, but tell me if things do not work
as expected.

Change-Id: Ib025a9f565678b225afa4759325cf6d496cc7215
parent b3caa920
Loading
Loading
Loading
Loading
+145 −0
Original line number Diff line number Diff line
@@ -512,6 +512,7 @@ package android {
    field public static final int freezesText = 16843116; // 0x101016c
    field public static final int fromAlpha = 16843210; // 0x10101ca
    field public static final int fromDegrees = 16843187; // 0x10101b3
    field public static final int fromScene = 16843737; // 0x10103d9
    field public static final int fromXDelta = 16843206; // 0x10101c6
    field public static final int fromXScale = 16843202; // 0x10101c2
    field public static final int fromYDelta = 16843208; // 0x10101c8
@@ -1012,6 +1013,7 @@ package android {
    field public static final int targetActivity = 16843266; // 0x1010202
    field public static final int targetClass = 16842799; // 0x101002f
    field public static final int targetDescriptions = 16843680; // 0x10103a0
    field public static final int targetID = 16843736; // 0x10103d8
    field public static final int targetPackage = 16842785; // 0x1010021
    field public static final int targetSdkVersion = 16843376; // 0x1010270
    field public static final int taskAffinity = 16842770; // 0x1010012
@@ -1100,6 +1102,7 @@ package android {
    field public static final int titleTextStyle = 16843512; // 0x10102f8
    field public static final int toAlpha = 16843211; // 0x10101cb
    field public static final int toDegrees = 16843188; // 0x10101b4
    field public static final int toScene = 16843738; // 0x10103da
    field public static final int toXDelta = 16843207; // 0x10101c7
    field public static final int toXScale = 16843203; // 0x10101c3
    field public static final int toYDelta = 16843209; // 0x10101c9
@@ -1114,6 +1117,7 @@ package android {
    field public static final int transcriptMode = 16843008; // 0x1010100
    field public static final int transformPivotX = 16843552; // 0x1010320
    field public static final int transformPivotY = 16843553; // 0x1010321
    field public static final int transition = 16843739; // 0x10103db
    field public static final int translationX = 16843554; // 0x1010322
    field public static final int translationY = 16843555; // 0x1010323
    field public static final int type = 16843169; // 0x10101a1
@@ -25876,6 +25880,7 @@ package android.view {
    method public java.lang.CharSequence getContentDescription();
    method public final android.content.Context getContext();
    method protected android.view.ContextMenu.ContextMenuInfo getContextMenuInfo();
    method public android.view.transition.Scene getCurrentScene();
    method public static int getDefaultSize(int, int);
    method public android.view.Display getDisplay();
    method public final int[] getDrawableState();
@@ -28023,6 +28028,146 @@ package android.view.textservice {
}
package android.view.transition {
  public class AutoTransition extends android.view.transition.TransitionGroup {
    ctor public AutoTransition();
  }
  public class Crossfade extends android.view.transition.Transition {
    ctor public Crossfade();
    method protected void captureValues(android.view.transition.TransitionValues, boolean);
    method protected android.animation.Animator play(android.view.ViewGroup, android.view.transition.TransitionValues, android.view.transition.TransitionValues);
  }
  public class Fade extends android.view.transition.Visibility {
    ctor public Fade();
    ctor public Fade(int);
    field public static final int IN = 1; // 0x1
    field public static final int OUT = 2; // 0x2
  }
  public class Move extends android.view.transition.Transition {
    ctor public Move();
    method protected void captureValues(android.view.transition.TransitionValues, boolean);
    method protected android.animation.Animator play(android.view.ViewGroup, android.view.transition.TransitionValues, android.view.transition.TransitionValues);
    method public void setReparent(boolean);
    method public void setResizeClip(boolean);
  }
  public class Recolor extends android.view.transition.Transition {
    ctor public Recolor();
    method protected void captureValues(android.view.transition.TransitionValues, boolean);
    method protected android.animation.Animator play(android.view.ViewGroup, android.view.transition.TransitionValues, android.view.transition.TransitionValues);
  }
  public class Rotate extends android.view.transition.Transition {
    ctor public Rotate();
    method protected void captureValues(android.view.transition.TransitionValues, boolean);
    method protected android.animation.Animator play(android.view.ViewGroup, android.view.transition.TransitionValues, android.view.transition.TransitionValues);
  }
  public final class Scene {
    ctor public Scene(android.view.ViewGroup);
    ctor public Scene(android.view.ViewGroup, int, android.content.Context);
    ctor public Scene(android.view.ViewGroup, android.view.ViewGroup);
    method public void enter();
    method public void exit();
    method public android.view.ViewGroup getSceneRoot();
    method public void setEnterAction(java.lang.Runnable);
    method public void setExitAction(java.lang.Runnable);
  }
  public class Slide extends android.view.transition.Visibility {
    ctor public Slide();
  }
  public class TextChange extends android.view.transition.Transition {
    ctor public TextChange();
    method protected void captureValues(android.view.transition.TransitionValues, boolean);
    method protected android.animation.Animator play(android.view.ViewGroup, android.view.transition.TransitionValues, android.view.transition.TransitionValues);
  }
  public abstract class Transition {
    ctor public Transition();
    method public void addListener(android.view.transition.Transition.TransitionListener);
    method protected void cancelTransition();
    method protected abstract void captureValues(android.view.transition.TransitionValues, boolean);
    method public long getDuration();
    method public android.animation.TimeInterpolator getInterpolator();
    method public java.util.ArrayList<android.view.transition.Transition.TransitionListener> getListeners();
    method public long getStartDelay();
    method public int[] getTargetIds();
    method public android.view.View[] getTargets();
    method protected void onTransitionCancel();
    method protected void onTransitionEnd();
    method protected void onTransitionStart();
    method protected abstract android.animation.Animator play(android.view.ViewGroup, android.view.transition.TransitionValues, android.view.transition.TransitionValues);
    method protected boolean prePlay(android.view.ViewGroup, android.view.transition.TransitionValues, android.view.transition.TransitionValues);
    method public void removeListener(android.view.transition.Transition.TransitionListener);
    method public android.view.transition.Transition setDuration(long);
    method public void setInterpolator(android.animation.TimeInterpolator);
    method public void setStartDelay(long);
    method public android.view.transition.Transition setTargetIds(int...);
    method public android.view.transition.Transition setTargets(android.view.View...);
  }
  public static abstract interface Transition.TransitionListener {
    method public abstract void onTransitionCancel(android.view.transition.Transition);
    method public abstract void onTransitionEnd(android.view.transition.Transition);
    method public abstract void onTransitionStart(android.view.transition.Transition);
  }
  public class TransitionGroup extends android.view.transition.Transition {
    ctor public TransitionGroup();
    ctor public TransitionGroup(int);
    method public void addTransitions(android.view.transition.Transition...);
    method protected void captureValues(android.view.transition.TransitionValues, boolean);
    method protected android.animation.Animator play(android.view.ViewGroup, android.view.transition.TransitionValues, android.view.transition.TransitionValues);
    method public void removeTransition(android.view.transition.Transition);
    method public void setOrdering(int);
    field public static final int SEQUENTIALLY = 1; // 0x1
    field public static final int TOGETHER = 0; // 0x0
  }
  public class TransitionInflater {
    method public static android.view.transition.TransitionInflater from(android.content.Context);
    method public android.view.transition.Scene inflateScene(int, android.view.ViewGroup);
    method public android.view.transition.Transition inflateTransition(int);
    method public android.view.transition.TransitionManager inflateTransitionManager(int, android.view.ViewGroup);
  }
  public class TransitionManager {
    ctor public TransitionManager();
    method public android.view.transition.Transition getDefaultTransition();
    method public static void go(android.view.transition.Scene);
    method public static void go(android.view.transition.Scene, android.view.transition.Transition);
    method public static void go(android.view.ViewGroup, java.lang.Runnable);
    method public static void go(android.view.ViewGroup, java.lang.Runnable, android.view.transition.Transition);
    method public void setDefaultTransition(android.view.transition.Transition);
    method public void setTransition(android.view.transition.Scene, android.view.transition.Transition);
    method public void setTransition(android.view.transition.Scene, android.view.transition.Scene, android.view.transition.Transition);
    method public void transitionTo(android.view.transition.Scene);
  }
  public class TransitionValues {
    ctor public TransitionValues();
    field public final java.util.HashMap values;
    field public android.view.View view;
  }
  public abstract class Visibility extends android.view.transition.Transition {
    ctor public Visibility();
    method protected android.animation.Animator appear(android.view.ViewGroup, android.view.View, int, android.view.View, int);
    method protected void captureValues(android.view.transition.TransitionValues, boolean);
    method protected android.animation.Animator disappear(android.view.ViewGroup, android.view.View, int, android.view.View, int);
    method protected android.animation.Animator play(android.view.ViewGroup, android.view.transition.TransitionValues, android.view.transition.TransitionValues);
    method protected boolean preAppear(android.view.ViewGroup, android.view.View, int, android.view.View, int);
    method protected boolean preDisappear(android.view.ViewGroup, android.view.View, int, android.view.View, int);
  }
}
package android.webkit {
  public class ConsoleMessage {
+30 −0
Original line number Diff line number Diff line
@@ -73,6 +73,7 @@ import android.view.animation.Transformation;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.view.transition.Scene;
import android.widget.ScrollBarDrawable;
import static android.os.Build.VERSION_CODES.*;
@@ -1572,6 +1573,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     */
    protected Object mTag;
    private Scene mCurrentScene = null;
    // for mPrivateFlags:
    /** {@hide} */
    static final int PFLAG_WANTS_FOCUS                 = 0x00000001;
@@ -12037,6 +12040,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        mCurrentAnimation = null;
        mCurrentScene = null;
        resetAccessibilityStateChanged();
    }
@@ -17757,6 +17762,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        return mAnimator;
    }
    /**
     * Set the current Scene that this view is in. The current scene is set only
     * on the root view of a scene, not for every view in that hierarchy. This
     * information is used by Scene to determine whether there is a previous
     * scene which should be exited before the new scene is entered.
     *
     * @param scene The new scene being set on the view
     *
     * @hide
     */
    public void setCurrentScene(Scene scene) {
        mCurrentScene = scene;
    }
    /**
     * Gets the current {@link Scene} set on this view. A scene is set on a view
     * only if that view is the scene root.
     *
     * @return The current Scene set on this view. A value of null indicates that
     * no Scene is current set.
     */
    public Scene getCurrentScene() {
        return mCurrentScene;
    }
    /**
     * Interface definition for a callback to be invoked when a hardware key event is
     * dispatched to this view. The callback will be invoked before the key event is
+34 −0
Original line number Diff line number Diff line
/*
 * 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.view.transition;

/**
 * Utility class for creating a default transition that automatically fades,
 * moves, and resizes views during a scene change.
 */
public class AutoTransition extends TransitionGroup {

    /**
     * Constructs an AutoTransition object, which is a TransitionGroup which
     * first fades out disappearing targets, then moves and resizes existing
     * targets, and finally fades in appearing targets.
     *
     */
    public AutoTransition() {
        setOrdering(SEQUENTIALLY);
        addTransitions(new Fade(Fade.OUT), new Move(), new Fade(Fade.IN));
    }
}
+163 −0
Original line number Diff line number Diff line
/*
 * 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.view.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.graphics.drawable.Drawable;
import android.util.Log;
import android.view.SurfaceView;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;

import java.util.HashMap;

/**
 * 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>
 */
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();

    @Override
    protected boolean prePlay(ViewGroup sceneRoot, TransitionValues startValues,
            TransitionValues endValues) {
        if (startValues == null || endValues == null) {
            return false;
        }
        final View view = startValues.view;
        HashMap<String, Object> startVals = startValues.values;
        HashMap<String, Object> endVals = endValues.values;
        Bitmap startBitmap = (Bitmap) startVals.get(PROPNAME_BITMAP);
        Bitmap endBitmap = (Bitmap) endVals.get(PROPNAME_BITMAP);
        Drawable startDrawable = (Drawable) startVals.get(PROPNAME_DRAWABLE);
        Drawable endDrawable = (Drawable) 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)) {
            view.getOverlay().add(endDrawable);
            view.getOverlay().add(startDrawable);
            return true;
        } else {
            return false;
        }
    }

    @Override
    protected Animator play(ViewGroup sceneRoot, TransitionValues startValues,
            TransitionValues endValues) {
        if (startValues == null || endValues == null) {
            return null;
        }
        HashMap<String, Object> startVals = startValues.values;
        HashMap<String, Object> endVals = endValues.values;

        final View view = endValues.view;
        Rect startBounds = (Rect) startVals.get(PROPNAME_BOUNDS);
        Rect endBounds = (Rect) endVals.get(PROPNAME_BOUNDS);
        final BitmapDrawable startDrawable = (BitmapDrawable) startVals.get(PROPNAME_DRAWABLE);
        final BitmapDrawable endDrawable = (BitmapDrawable) endVals.get(PROPNAME_DRAWABLE);

        // 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 = 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());
            }
        });
        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) {
                view.getOverlay().remove(startDrawable);
                view.getOverlay().remove(endDrawable);
            }
        });
        AnimatorSet set = new AnimatorSet();
        set.playTogether(anim);
        if (!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);
            Animator anim3 = ObjectAnimator.ofObject(endDrawable, "bounds",
                    sRectEvaluator, startBounds, endBounds);
            set.playTogether(anim2);
            set.playTogether(anim3);
        }
        return set;
    }

    @Override
    protected void captureValues(TransitionValues values, boolean start) {
        View view = values.view;
        values.values.put(PROPNAME_BOUNDS, new Rect(0, 0,
                view.getWidth(), view.getHeight()));

        if (Transition.DBG) {
            Log.d(LOG_TAG, "Captured bounds " + values.values.get(PROPNAME_BOUNDS) + ": start = " +
                    start);
        }
        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);
        }
        values.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(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
        values.values.put(PROPNAME_DRAWABLE, drawable);
    }

}
+209 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading