/* * Copyright (C) 2020 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; import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.graphics.Insets; import android.view.animation.Interpolator; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; /** * Class representing an animation of a set of windows that cause insets. */ public final class WindowInsetsAnimation { @WindowInsets.Type.InsetsType private final int mTypeMask; private float mFraction; @Nullable private final Interpolator mInterpolator; private final long mDurationMillis; private float mAlpha; /** * Creates a new {@link WindowInsetsAnimation} object. *
* This should only be used for testing, as usually the system creates this object for the * application to listen to with {@link Callback}. *
* @param typeMask The bitmask of {@link WindowInsets.Type}s that are animating. * @param interpolator The interpolator of the animation. * @param durationMillis The duration of the animation in * {@link java.util.concurrent.TimeUnit#MILLISECONDS}. */ public WindowInsetsAnimation( @WindowInsets.Type.InsetsType int typeMask, @Nullable Interpolator interpolator, long durationMillis) { mTypeMask = typeMask; mInterpolator = interpolator; mDurationMillis = durationMillis; } /** * @return The bitmask of {@link WindowInsets.Type}s that are animating. */ @WindowInsets.Type.InsetsType public int getTypeMask() { return mTypeMask; } /** * Returns the raw fractional progress of this animation between * start state of the animation and the end state of the animation. Note * that this progress is the global progress of the animation, whereas * {@link Callback#onProgress} will only dispatch the insets that may * be inset with {@link WindowInsets#inset} by parents of views in the hierarchy. * Progress per insets animation is global for the entire animation. One animation animates * all things together (in, out, ...). If they don't animate together, we'd have * multiple animations. ** Note: In case the application is controlling the animation, the valued returned here will * be the same as the application passed into * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)}. *
* @return The current progress of this animation. */ @FloatRange(from = 0f, to = 1f) public float getFraction() { return mFraction; } /** * Returns the interpolated fractional progress of this animation between * start state of the animation and the end state of the animation. Note * that this progress is the global progress of the animation, whereas * {@link Callback#onProgress} will only dispatch the insets that may * be inset with {@link WindowInsets#inset} by parents of views in the hierarchy. * Progress per insets animation is global for the entire animation. One animation animates * all things together (in, out, ...). If they don't animate together, we'd have * multiple animations. ** Note: In case the application is controlling the animation, the valued returned here will * be the same as the application passed into * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)}, * interpolated with the interpolator passed into * {@link WindowInsetsController#controlWindowInsetsAnimation}. *
** Note: For system-initiated animations, this will always return a valid value between 0 * and 1. *
* @see #getFraction() for raw fraction. * @return The current interpolated progress of this animation. */ public float getInterpolatedFraction() { if (mInterpolator != null) { return mInterpolator.getInterpolation(mFraction); } return mFraction; } /** * Retrieves the interpolator used for this animation, or {@code null} if this animation * doesn't follow an interpolation curved. For system-initiated animations, this will never * return {@code null}. * * @return The interpolator used for this animation. */ @Nullable public Interpolator getInterpolator() { return mInterpolator; } /** * @return duration of animation in {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or * -1 if the animation doesn't have a fixed duration. */ public long getDurationMillis() { return mDurationMillis; } /** * Set fraction of the progress if {@link WindowInsets.Type} animation is * controlled by the app. ** Note: This should only be used for testing, as the system fills in the fraction for the * application or the fraction that was passed into * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)} is being * used. *
* @param fraction fractional progress between 0 and 1 where 0 represents hidden and * zero progress and 1 represent fully shown final state. * @see #getFraction() */ public void setFraction(@FloatRange(from = 0f, to = 1f) float fraction) { mFraction = fraction; } /** * Retrieves the translucency of the windows that are animating. * * @return Alpha of windows that cause insets of type {@link WindowInsets.Type}. */ @FloatRange(from = 0f, to = 1f) public float getAlpha() { return mAlpha; } /** * Sets the translucency of the windows that are animating. ** Note: This should only be used for testing, as the system fills in the alpha for the * application or the alpha that was passed into * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)} is being * used. *
* @param alpha Alpha of windows that cause insets of type {@link WindowInsets.Type}. * @see #getAlpha() */ public void setAlpha(@FloatRange(from = 0f, to = 1f) float alpha) { mAlpha = alpha; } /** * Class representing the range of an {@link WindowInsetsAnimation} */ public static final class Bounds { private final Insets mLowerBound; private final Insets mUpperBound; public Bounds(@NonNull Insets lowerBound, @NonNull Insets upperBound) { mLowerBound = lowerBound; mUpperBound = upperBound; } /** * Queries the lower inset bound of the animation. If the animation is about showing or * hiding a window that cause insets, the lower bound is {@link Insets#NONE} and the upper * bound is the same as {@link WindowInsets#getInsets(int)} for the fully shown state. This * is the same as {@link WindowInsetsAnimationController#getHiddenStateInsets} and * {@link WindowInsetsAnimationController#getShownStateInsets} in case the listener gets * invoked because of an animation that originates from * {@link WindowInsetsAnimationController}. ** However, if the size of a window that causes insets is changing, these are the * lower/upper bounds of that size animation. *
* There are no overlapping animations for a specific type, but there may be multiple * animations running at the same time for different inset types. * * @see #getUpperBound() * @see WindowInsetsAnimationController#getHiddenStateInsets */ @NonNull public Insets getLowerBound() { return mLowerBound; } /** * Queries the upper inset bound of the animation. If the animation is about showing or * hiding a window that cause insets, the lower bound is {@link Insets#NONE} * nd the upper bound is the same as {@link WindowInsets#getInsets(int)} for the fully * shown state. This is the same as * {@link WindowInsetsAnimationController#getHiddenStateInsets} and * {@link WindowInsetsAnimationController#getShownStateInsets} in case the listener gets * invoked because of an animation that originates from * {@link WindowInsetsAnimationController}. ** However, if the size of a window that causes insets is changing, these are the * lower/upper bounds of that size animation. *
* There are no overlapping animations for a specific type, but there may be multiple * animations running at the same time for different inset types. * * @see #getLowerBound() * @see WindowInsetsAnimationController#getShownStateInsets */ @NonNull public Insets getUpperBound() { return mUpperBound; } /** * Insets both the lower and upper bound by the specified insets. This is to be used in * {@link Callback#onStart} to indicate that a part of the insets has * been used to offset or clip its children, and the children shouldn't worry about that * part anymore. * * @param insets The amount to inset. * @return A copy of this instance inset in the given directions. * @see WindowInsets#inset * @see Callback#onStart */ @NonNull public Bounds inset(@NonNull Insets insets) { return new Bounds( // TODO: refactor so that WindowInsets.insetInsets() is in a more appropriate // place eventually. WindowInsets.insetInsets( mLowerBound, insets.left, insets.top, insets.right, insets.bottom), WindowInsets.insetInsets( mUpperBound, insets.left, insets.top, insets.right, insets.bottom)); } @Override public String toString() { return "Bounds{lower=" + mLowerBound + " upper=" + mUpperBound + "}"; } } /** * Interface that allows the application to listen to animation events for windows that cause * insets. */ @SuppressLint("CallbackMethodName") // TODO(b/149430296) Should be on method, not class. public abstract static class Callback { /** * Return value for {@link #getDispatchMode()}: Dispatching of animation events should * stop at this level in the view hierarchy, and no animation events should be dispatch to * the subtree of the view hierarchy. */ public static final int DISPATCH_MODE_STOP = 0; /** * Return value for {@link #getDispatchMode()}: Dispatching of animation events should * continue in the view hierarchy. */ public static final int DISPATCH_MODE_CONTINUE_ON_SUBTREE = 1; /** @hide */ @IntDef(prefix = { "DISPATCH_MODE_" }, value = { DISPATCH_MODE_STOP, DISPATCH_MODE_CONTINUE_ON_SUBTREE }) @Retention(RetentionPolicy.SOURCE) public @interface DispatchMode {} @DispatchMode private final int mDispatchMode; /** * Creates a new {@link WindowInsetsAnimation} callback with the given * {@link #getDispatchMode() dispatch mode}. * * @param dispatchMode The dispatch mode for this callback. See {@link #getDispatchMode()}. */ public Callback(@DispatchMode int dispatchMode) { mDispatchMode = dispatchMode; } /** * Retrieves the dispatch mode of this listener. Dispatch of the all animation events is * hierarchical: It will starts at the root of the view hierarchy and then traverse it and * invoke the callback of the specific {@link View} that is being traversed. * The method may return either {@link #DISPATCH_MODE_CONTINUE_ON_SUBTREE} to indicate that * animation events should be propagated to the subtree of the view hierarchy, or * {@link #DISPATCH_MODE_STOP} to stop dispatching. In that case, all animation callbacks * related to the animation passed in will be stopped from propagating to the subtree of the * hierarchy. *
* Also note that {@link #DISPATCH_MODE_STOP} behaves the same way as * returning {@link WindowInsets#CONSUMED} during the regular insets dispatch in * {@link View#onApplyWindowInsets}. * * @return Either {@link #DISPATCH_MODE_CONTINUE_ON_SUBTREE} to indicate that dispatching of * animation events will continue to the subtree of the view hierarchy, or * {@link #DISPATCH_MODE_STOP} to indicate that animation events will stop * dispatching. */ @DispatchMode @SuppressLint("CallbackMethodName") // TODO(b/149430296) False positive: not a callback. public final int getDispatchMode() { return mDispatchMode; } /** * Called when an insets animation is about to start and before the views have been laid out * in the end state of the animation. The ordering of events during an insets animation is * the following: *
*
* This ordering allows the application to inspect the end state after the animation has * finished, and then revert to the starting state of the animation in the first * {@link #onProgress} callback by using post-layout view properties like {@link View#setX} * and related methods. * *
Note that the animation might be cancelled before {@link #onStart} is dispatched. On * {@link android.os.Build.VERSION_CODES#S S} and later, {@link #onEnd} is immediately * dispatched without an {@link #onStart} in that case. * On {@link android.os.Build.VERSION_CODES#R R}, no callbacks are dispatched after * {@code #onPrepare} for such an animation. * *
* Note: If the animation is application controlled by using * {@link WindowInsetsController#controlWindowInsetsAnimation}, the end state of the * animation is undefined as the application may decide on the end state only by passing in * {@code shown} parameter when calling {@link WindowInsetsAnimationController#finish}. In * this situation, the system will dispatch the insets in the opposite visibility state * before the animation starts. Example: When controlling the input method with * {@link WindowInsetsController#controlWindowInsetsAnimation} and the input method is * currently showing, {@link View#onApplyWindowInsets} will receive a {@link WindowInsets} * instance for which {@link WindowInsets#isVisible} will return {@code false} for * {@link WindowInsets.Type#ime}. * * @param animation The animation that is about to start. */ public void onPrepare(@NonNull WindowInsetsAnimation animation) { } /** * Called when an insets animation gets started. *
* Note that, like {@link #onProgress}, dispatch of the animation start event is * hierarchical: It will starts at the root of the view hierarchy and then traverse it * and invoke the callback of the specific {@link View} that is being traversed. * The method may return a modified * instance of the bounds by calling {@link Bounds#inset} to indicate that a part of * the insets have been used to offset or clip its children, and the children shouldn't * worry about that part anymore. Furthermore, if {@link #getDispatchMode()} returns * {@link #DISPATCH_MODE_STOP}, children of this view will not receive the callback anymore. * * @param animation The animation that is about to start. * @param bounds The bounds in which animation happens. * @return The animation representing the part of the insets that should be dispatched to * the subtree of the hierarchy. */ @NonNull public Bounds onStart( @NonNull WindowInsetsAnimation animation, @NonNull Bounds bounds) { return bounds; } /** * Called when the insets change as part of running an animation. Note that even if multiple * animations for different types are running, there will only be one progress callback per * frame. The {@code insets} passed as an argument represents the overall state and will * include all types, regardless of whether they are animating or not. *
* Note that insets dispatch is hierarchical: It will start at the root of the view
* hierarchy, and then traverse it and invoke the callback of the specific {@link View}
* being traversed. The method may return a modified instance by calling
* {@link WindowInsets#inset(int, int, int, int)} to indicate that a part of the insets have
* been used to offset or clip its children, and the children shouldn't worry about that
* part anymore. Furthermore, if {@link #getDispatchMode()} returns
* {@link #DISPATCH_MODE_STOP}, children of this view will not receive the callback anymore.
*
* @param insets The current insets.
* @param runningAnimations The currently running animations.
* @return The insets to dispatch to the subtree of the hierarchy.
*/
@NonNull
public abstract WindowInsets onProgress(@NonNull WindowInsets insets,
@NonNull List