blob: e3b140e35ceeec59b005f7a643aaf34fd947e4db [file] [log] [blame]
/*
* 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 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
protected Transition(boolean deferred) {
if (!deferred) {
mImpl = Build.VERSION.SDK_INT < 19 ? new TransitionIcs() : new TransitionKitKat();
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 underneat 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)
*/
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)
*/
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.
*/
@Nullable
public abstract Animator createAnimator(@NonNull ViewGroup sceneRoot,
@NonNull TransitionValues startValues, @NonNull TransitionValues endValues);
/**
* 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 ref android.R.styleable#Transition_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 ref android.R.styleable#Transition_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 ref android.R.styleable#Transition_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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
void onTransitionResume(@NonNull Transition transition);
}
}