/* * Copyright (C) 2016 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.support.transition; import android.animation.Animator; import android.animation.TimeInterpolator; import android.os.Build; import android.support.annotation.IdRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.view.SurfaceView; import android.view.TextureView; import android.view.View; import android.view.ViewGroup; import android.widget.Spinner; import java.util.List; /** * A Transition holds information about animations that will be run on its * targets during a scene change. Subclasses of this abstract class may * choreograph several child transitions ({@link TransitionSet} or they may * perform custom animations themselves. Any Transition has two main jobs: * (1) capture property values, and (2) play animations based on changes to * captured property values. A custom transition knows what property values * on View objects are of interest to it, and also knows how to animate * changes to those values. For example, the {@link Fade} transition tracks * changes to visibility-related properties and is able to construct and run * animations that fade items in or out based on changes to those properties. * * <p>Note: Transitions may not work correctly with either {@link SurfaceView} * or {@link TextureView}, due to the way that these views are displayed * on the screen. For SurfaceView, the problem is that the view is updated from * a non-UI thread, so changes to the view due to transitions (such as moving * and resizing the view) may be out of sync with the display inside those bounds. * TextureView is more compatible with transitions in general, but some * specific transitions (such as {@link Fade}) may not be compatible * with TextureView because they rely on {@link android.view.ViewOverlay} * functionality, which does not currently work with TextureView.</p> * * <p>Unlike the platform version, this does not support declaration by XML resources.</p> */ public abstract class Transition implements TransitionInterface { /* package */ TransitionImpl mImpl; /** * Constructs a Transition object with no target objects. A transition with * no targets defaults to running on all target objects in the scene hierarchy * (if the transition is not contained in a TransitionSet), or all target * objects passed down from its parent (if it is in a TransitionSet). */ public Transition() { this(false); } // Hidden constructor for built-in transitions Transition(boolean deferred) { if (!deferred) { if (Build.VERSION.SDK_INT >= 23) { mImpl = new TransitionApi23(); } else if (Build.VERSION.SDK_INT >= 19) { mImpl = new TransitionKitKat(); } else { mImpl = new TransitionIcs(); } mImpl.init(this); } } /** * Adds a listener to the set of listeners that are sent events through the * life of an animation, such as start, repeat, and end. * * @param listener the listener to be added to the current set of listeners * for this animation. * @return This transition object. */ @NonNull public Transition addListener(@NonNull TransitionListener listener) { mImpl.addListener(listener); return this; } /** * Sets the target view instances that this Transition is interested in * animating. By default, there are no targets, and a Transition will * listen for changes on every view in the hierarchy below the sceneRoot * of the Scene being transitioned into. Setting targets constrains * the Transition to only listen for, and act on, these views. * All other views will be ignored. * * <p>The target list is like the {@link #addTarget(int) targetId} * list except this list specifies the actual View instances, not the ids * of the views. This is an important distinction when scene changes involve * view hierarchies which have been inflated separately; different views may * share the same id but not actually be the same instance. If the transition * should treat those views as the same, then {@link #addTarget(int)} should be used * instead of {@link #addTarget(View)}. If, on the other hand, scene changes involve * changes all within the same view hierarchy, among views which do not * necessarily have ids set on them, then the target list of views may be more * convenient.</p> * * @param target A View on which the Transition will act, must be non-null. * @return The Transition to which the target is added. * Returning the same object makes it easier to chain calls during * construction, such as * <code>transitionSet.addTransitions(new Fade()).addTarget(someView);</code> * @see #addTarget(int) */ @NonNull public Transition addTarget(@NonNull View target) { mImpl.addTarget(target); return this; } /** * Adds the id of a target view that this Transition is interested in * animating. By default, there are no targetIds, and a Transition will * listen for changes on every view in the hierarchy below the sceneRoot * of the Scene being transitioned into. Setting targetIds constrains * the Transition to only listen for, and act on, views with these IDs. * Views with different IDs, or no IDs whatsoever, will be ignored. * * <p>Note that using ids to specify targets implies that ids should be unique * within the view hierarchy underneath the scene root.</p> * * @param targetId The id of a target view, must be a positive number. * @return The Transition to which the targetId is added. * Returning the same object makes it easier to chain calls during * construction, such as * <code>transitionSet.addTransitions(new Fade()).addTarget(someId);</code> * @see View#getId() */ @NonNull public Transition addTarget(@IdRes int targetId) { mImpl.addTarget(targetId); return this; } /** * Captures the values in the end scene for the properties that this * transition monitors. These values are then passed as the endValues * structure in a later call to * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}. * The main concern for an implementation is what the * properties are that the transition cares about and what the values are * for all of those properties. The start and end values will be compared * later during the * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)} * method to determine what, if any, animations, should be run. * * <p>Subclasses must implement this method. The method should only be called by the * transition system; it is not intended to be called from external classes.</p> * * @param transitionValues The holder for any values that the Transition * wishes to store. Values are stored in the <code>values</code> field * of this TransitionValues object and are keyed from * a String value. For example, to store a view's rotation value, * a transition might call * <code>transitionValues.values.put("appname:transitionname:rotation", * view.getRotation())</code>. The target view will already be stored * in * the transitionValues structure when this method is called. * @see #captureStartValues(TransitionValues) * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues) */ @Override public abstract void captureEndValues(@NonNull TransitionValues transitionValues); /** * Captures the values in the start scene for the properties that this * transition monitors. These values are then passed as the startValues * structure in a later call to * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}. * The main concern for an implementation is what the * properties are that the transition cares about and what the values are * for all of those properties. The start and end values will be compared * later during the * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)} * method to determine what, if any, animations, should be run. * * <p>Subclasses must implement this method. The method should only be called by the * transition system; it is not intended to be called from external classes.</p> * * @param transitionValues The holder for any values that the Transition * wishes to store. Values are stored in the <code>values</code> field * of this TransitionValues object and are keyed from * a String value. For example, to store a view's rotation value, * a transition might call * <code>transitionValues.values.put("appname:transitionname:rotation", * view.getRotation())</code>. The target view will already be stored * in * the transitionValues structure when this method is called. * @see #captureEndValues(TransitionValues) * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues) */ @Override public abstract void captureStartValues(@NonNull TransitionValues transitionValues); /** * This method creates an animation that will be run for this transition * given the information in the startValues and endValues structures captured * earlier for the start and end scenes. Subclasses of Transition should override * this method. The method should only be called by the transition system; it is * not intended to be called from external classes. * * <p>This method is called by the transition's parent (all the way up to the * topmost Transition in the hierarchy) with the sceneRoot and start/end * values that the transition may need to set up initial target values * and construct an appropriate animation. For example, if an overall * Transition is a {@link TransitionSet} consisting of several * child transitions in sequence, then some of the child transitions may * want to set initial values on target views prior to the overall * Transition commencing, to put them in an appropriate state for the * delay between that start and the child Transition start time. For * example, a transition that fades an item in may wish to set the starting * alpha value to 0, to avoid it blinking in prior to the transition * actually starting the animation. This is necessary because the scene * change that triggers the Transition will automatically set the end-scene * on all target views, so a Transition that wants to animate from a * different value should set that value prior to returning from this method.</p> * * <p>Additionally, a Transition can perform logic to determine whether * the transition needs to run on the given target and start/end values. * For example, a transition that resizes objects on the screen may wish * to avoid running for views which are not present in either the start * or end scenes.</p> * * <p>If there is an animator created and returned from this method, the * transition mechanism will apply any applicable duration, startDelay, * and interpolator to that animation and start it. A return value of * <code>null</code> indicates that no animation should run. The default * implementation returns null.</p> * * <p>The method is called for every applicable target object, which is * stored in the {@link TransitionValues#view} field.</p> * * @param sceneRoot The root of the transition hierarchy. * @param startValues The values for a specific target in the start scene. * @param endValues The values for the target in the end scene. * @return A Animator to be started at the appropriate time in the * overall transition for this scene change. A null value means no animation * should be run. */ @Override @Nullable public Animator createAnimator(@NonNull ViewGroup sceneRoot, @Nullable TransitionValues startValues, @Nullable TransitionValues endValues) { return null; } /** * Whether to add the children of given target to the list of target children * to exclude from this transition. The <code>exclude</code> parameter specifies * whether the target should be added to or removed from the excluded list. * * <p>Excluding targets is a general mechanism for allowing transitions to run on * a view hierarchy while skipping target views that should not be part of * the transition. For example, you may want to avoid animating children * of a specific ListView or Spinner. Views can be excluded either by their * id, or by their instance reference, or by the Class of that view * (eg, {@link Spinner}).</p> * * @param target The target to ignore when running this transition. * @param exclude Whether to add the target to or remove the target from the * current list of excluded targets. * @return This transition object. * @see #excludeTarget(View, boolean) * @see #excludeChildren(int, boolean) * @see #excludeChildren(Class, boolean) */ @NonNull public Transition excludeChildren(@NonNull View target, boolean exclude) { mImpl.excludeChildren(target, exclude); return this; } /** * Whether to add the children of the given id to the list of targets to exclude * from this transition. The <code>exclude</code> parameter specifies whether * the children of the target should be added to or removed from the excluded list. * Excluding children in this way provides a simple mechanism for excluding all * children of specific targets, rather than individually excluding each * child individually. * * <p>Excluding targets is a general mechanism for allowing transitions to run on * a view hierarchy while skipping target views that should not be part of * the transition. For example, you may want to avoid animating children * of a specific ListView or Spinner. Views can be excluded either by their * id, or by their instance reference, or by the Class of that view * (eg, {@link Spinner}).</p> * * @param targetId The id of a target whose children should be ignored when running * this transition. * @param exclude Whether to add the target to or remove the target from the * current list of excluded-child targets. * @return This transition object. * @see #excludeTarget(int, boolean) * @see #excludeChildren(View, boolean) * @see #excludeChildren(Class, boolean) */ @NonNull public Transition excludeChildren(@IdRes int targetId, boolean exclude) { mImpl.excludeChildren(targetId, exclude); return this; } /** * Whether to add the given type to the list of types whose children should * be excluded from this transition. The <code>exclude</code> parameter * specifies whether the target type should be added to or removed from * the excluded list. * * <p>Excluding targets is a general mechanism for allowing transitions to run on * a view hierarchy while skipping target views that should not be part of * the transition. For example, you may want to avoid animating children * of a specific ListView or Spinner. Views can be excluded either by their * id, or by their instance reference, or by the Class of that view * (eg, {@link Spinner}).</p> * * @param type The type to ignore when running this transition. * @param exclude Whether to add the target type to or remove it from the * current list of excluded target types. * @return This transition object. * @see #excludeTarget(Class, boolean) * @see #excludeChildren(int, boolean) * @see #excludeChildren(View, boolean) */ @NonNull public Transition excludeChildren(@NonNull Class type, boolean exclude) { mImpl.excludeChildren(type, exclude); return this; } /** * Whether to add the given target to the list of targets to exclude from this * transition. The <code>exclude</code> parameter specifies whether the target * should be added to or removed from the excluded list. * * <p>Excluding targets is a general mechanism for allowing transitions to run on * a view hierarchy while skipping target views that should not be part of * the transition. For example, you may want to avoid animating children * of a specific ListView or Spinner. Views can be excluded either by their * id, or by their instance reference, or by the Class of that view * (eg, {@link Spinner}).</p> * * @param target The target to ignore when running this transition. * @param exclude Whether to add the target to or remove the target from the * current list of excluded targets. * @return This transition object. * @see #excludeChildren(View, boolean) * @see #excludeTarget(int, boolean) * @see #excludeTarget(Class, boolean) */ @NonNull public Transition excludeTarget(@NonNull View target, boolean exclude) { mImpl.excludeTarget(target, exclude); return this; } /** * Whether to add the given id to the list of target ids to exclude from this * transition. The <code>exclude</code> parameter specifies whether the target * should be added to or removed from the excluded list. * * <p>Excluding targets is a general mechanism for allowing transitions to run on * a view hierarchy while skipping target views that should not be part of * the transition. For example, you may want to avoid animating children * of a specific ListView or Spinner. Views can be excluded either by their * id, or by their instance reference, or by the Class of that view * (eg, {@link Spinner}).</p> * * @param targetId The id of a target to ignore when running this transition. * @param exclude Whether to add the target to or remove the target from the * current list of excluded targets. * @return This transition object. * @see #excludeChildren(int, boolean) * @see #excludeTarget(View, boolean) * @see #excludeTarget(Class, boolean) */ @NonNull public Transition excludeTarget(@IdRes int targetId, boolean exclude) { mImpl.excludeTarget(targetId, exclude); return this; } /** * Whether to add the given type to the list of types to exclude from this * transition. The <code>exclude</code> parameter specifies whether the target * type should be added to or removed from the excluded list. * * <p>Excluding targets is a general mechanism for allowing transitions to run on * a view hierarchy while skipping target views that should not be part of * the transition. For example, you may want to avoid animating children * of a specific ListView or Spinner. Views can be excluded either by their * id, or by their instance reference, or by the Class of that view * (eg, {@link Spinner}).</p> * * @param type The type to ignore when running this transition. * @param exclude Whether to add the target type to or remove it from the * current list of excluded target types. * @return This transition object. * @see #excludeChildren(Class, boolean) * @see #excludeTarget(int, boolean) * @see #excludeTarget(View, boolean) */ @NonNull public Transition excludeTarget(@NonNull Class type, boolean exclude) { mImpl.excludeTarget(type, exclude); return this; } /** * Returns the duration set on this transition. If no duration has been set, * the returned value will be negative, indicating that resulting animators will * retain their own durations. * * @return The duration set on this transition, in milliseconds, if one has been * set, otherwise returns a negative number. */ public long getDuration() { return mImpl.getDuration(); } /** * Sets the duration of this transition. By default, there is no duration * (indicated by a negative number), which means that the Animator created by * the transition will have its own specified duration. If the duration of a * Transition is set, that duration will override the Animator duration. * * @param duration The length of the animation, in milliseconds. * @return This transition object. * @attr name android:duration */ @NonNull public Transition setDuration(long duration) { mImpl.setDuration(duration); return this; } /** * Returns the interpolator set on this transition. If no interpolator has been set, * the returned value will be null, indicating that resulting animators will * retain their own interpolators. * * @return The interpolator set on this transition, if one has been set, otherwise * returns null. */ @Nullable public TimeInterpolator getInterpolator() { return mImpl.getInterpolator(); } /** * Sets the interpolator of this transition. By default, the interpolator * is null, which means that the Animator created by the transition * will have its own specified interpolator. If the interpolator of a * Transition is set, that interpolator will override the Animator interpolator. * * @param interpolator The time interpolator used by the transition * @return This transition object. * @attr name android:interpolator */ @NonNull public Transition setInterpolator(@Nullable TimeInterpolator interpolator) { mImpl.setInterpolator(interpolator); return this; } /** * Returns the name of this Transition. This name is used internally to distinguish * between different transitions to determine when interrupting transitions overlap. * For example, a ChangeBounds running on the same target view as another ChangeBounds * should determine whether the old transition is animating to different end values * and should be canceled in favor of the new transition. * * <p>By default, a Transition's name is simply the value of {@link Class#getName()}, * but subclasses are free to override and return something different.</p> * * @return The name of this transition. */ @NonNull public String getName() { return mImpl.getName(); } /** * Returns the startDelay set on this transition. If no startDelay has been set, * the returned value will be negative, indicating that resulting animators will * retain their own startDelays. * * @return The startDelay set on this transition, in milliseconds, if one has * been set, otherwise returns a negative number. */ public long getStartDelay() { return mImpl.getStartDelay(); } /** * Sets the startDelay of this transition. By default, there is no delay * (indicated by a negative number), which means that the Animator created by * the transition will have its own specified startDelay. If the delay of a * Transition is set, that delay will override the Animator delay. * * @param startDelay The length of the delay, in milliseconds. * @return This transition object. * @attr name android:startDelay */ @NonNull public Transition setStartDelay(long startDelay) { mImpl.setStartDelay(startDelay); return this; } /** * Returns the array of target IDs that this transition limits itself to * tracking and animating. If the array is null for both this method and * {@link #getTargets()}, then this transition is * not limited to specific views, and will handle changes to any views * in the hierarchy of a scene change. * * @return the list of target IDs */ @NonNull public List<Integer> getTargetIds() { return mImpl.getTargetIds(); } /** * Returns the array of target views that this transition limits itself to * tracking and animating. If the array is null for both this method and * {@link #getTargetIds()}, then this transition is * not limited to specific views, and will handle changes to any views * in the hierarchy of a scene change. * * @return the list of target views */ @NonNull public List<View> getTargets() { return mImpl.getTargets(); } /** * Returns the set of property names used stored in the {@link TransitionValues} * object passed into {@link #captureStartValues(TransitionValues)} that * this transition cares about for the purposes of canceling overlapping animations. * When any transition is started on a given scene root, all transitions * currently running on that same scene root are checked to see whether the * properties on which they based their animations agree with the end values of * the same properties in the new transition. If the end values are not equal, * then the old animation is canceled since the new transition will start a new * animation to these new values. If the values are equal, the old animation is * allowed to continue and no new animation is started for that transition. * * <p>A transition does not need to override this method. However, not doing so * will mean that the cancellation logic outlined in the previous paragraph * will be skipped for that transition, possibly leading to artifacts as * old transitions and new transitions on the same targets run in parallel, * animating views toward potentially different end values.</p> * * @return An array of property names as described in the class documentation for * {@link TransitionValues}. The default implementation returns <code>null</code>. */ @Nullable public String[] getTransitionProperties() { return mImpl.getTransitionProperties(); } /** * This method can be called by transitions to get the TransitionValues for * any particular view during the transition-playing process. This might be * necessary, for example, to query the before/after state of related views * for a given transition. */ @NonNull public TransitionValues getTransitionValues(@NonNull View view, boolean start) { return mImpl.getTransitionValues(view, start); } /** * Removes a listener from the set listening to this animation. * * @param listener the listener to be removed from the current set of * listeners for this transition. * @return This transition object. */ @NonNull public Transition removeListener(@NonNull TransitionListener listener) { mImpl.removeListener(listener); return this; } /** * Removes the given target from the list of targets that this Transition * is interested in animating. * * @param target The target view, must be non-null. * @return Transition The Transition from which the target is removed. * Returning the same object makes it easier to chain calls during * construction, such as * <code>transitionSet.addTransitions(new Fade()).removeTarget(someView);</code> */ @NonNull public Transition removeTarget(@NonNull View target) { mImpl.removeTarget(target); return this; } /** * Removes the given targetId from the list of ids that this Transition * is interested in animating. * * @param targetId The id of a target view, must be a positive number. * @return The Transition from which the targetId is removed. * Returning the same object makes it easier to chain calls during * construction, such as * <code>transitionSet.addTransitions(new Fade()).removeTargetId(someId);</code> */ @NonNull public Transition removeTarget(@IdRes int targetId) { mImpl.removeTarget(targetId); return this; } @Override public String toString() { return mImpl.toString(); } /** * A transition listener receives notifications from a transition. * Notifications indicate transition lifecycle events. */ public interface TransitionListener extends TransitionInterfaceListener<Transition> { /** * Notification about the start of the transition. * * @param transition The started transition. */ @Override void onTransitionStart(@NonNull Transition transition); /** * Notification about the end of the transition. Canceled transitions * will always notify listeners of both the cancellation and end * events. That is, {@link #onTransitionEnd(Transition)} is always called, * regardless of whether the transition was canceled or played * through to completion. * * @param transition The transition which reached its end. */ @Override void onTransitionEnd(@NonNull Transition transition); /** * Notification about the cancellation of the transition. * Note that cancel may be called by a parent {@link TransitionSet} on * a child transition which has not yet started. This allows the child * transition to restore state on target objects which was set at * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues) * createAnimator()} time. * * @param transition The transition which was canceled. */ @Override void onTransitionCancel(@NonNull Transition transition); /** * Notification when a transition is paused. * Note that createAnimator() may be called by a parent {@link TransitionSet} on * a child transition which has not yet started. This allows the child * transition to restore state on target objects which was set at * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues) * createAnimator()} time. * * @param transition The transition which was paused. */ @Override void onTransitionPause(@NonNull Transition transition); /** * Notification when a transition is resumed. * Note that resume() may be called by a parent {@link TransitionSet} on * a child transition which has not yet started. This allows the child * transition to restore state which may have changed in an earlier call * to {@link #onTransitionPause(Transition)}. * * @param transition The transition which was resumed. */ @Override void onTransitionResume(@NonNull Transition transition); } }