diff options
-rw-r--r-- | api/current.txt | 10 | ||||
-rw-r--r-- | core/java/android/view/View.java | 31 | ||||
-rw-r--r-- | core/java/android/view/ViewGroup.java | 49 | ||||
-rw-r--r-- | core/java/android/view/WindowInsets.java | 32 | ||||
-rw-r--r-- | core/java/android/view/WindowInsetsAnimationCallback.java | 60 | ||||
-rw-r--r-- | tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java | 5 |
6 files changed, 150 insertions, 37 deletions
diff --git a/api/current.txt b/api/current.txt index 645eae1eb4a6..73b7d4ae9a13 100644 --- a/api/current.txt +++ b/api/current.txt @@ -53682,9 +53682,9 @@ package android.view { public final class WindowInsets { ctor public WindowInsets(android.view.WindowInsets); - method @NonNull public android.view.WindowInsets consumeDisplayCutout(); - method @NonNull public android.view.WindowInsets consumeStableInsets(); - method @NonNull public android.view.WindowInsets consumeSystemWindowInsets(); + method @Deprecated @NonNull public android.view.WindowInsets consumeDisplayCutout(); + method @Deprecated @NonNull public android.view.WindowInsets consumeStableInsets(); + method @Deprecated @NonNull public android.view.WindowInsets consumeSystemWindowInsets(); method @Nullable public android.view.DisplayCutout getDisplayCutout(); method @NonNull public android.graphics.Insets getInsets(int); method @NonNull public android.graphics.Insets getMandatorySystemGestureInsets(); @@ -53710,6 +53710,7 @@ package android.view { method public boolean isVisible(int); method @Deprecated @NonNull public android.view.WindowInsets replaceSystemWindowInsets(int, int, int, int); method @Deprecated @NonNull public android.view.WindowInsets replaceSystemWindowInsets(android.graphics.Rect); + field @NonNull public static final android.view.WindowInsets CONSUMED; } public static final class WindowInsets.Builder { @@ -53741,10 +53742,13 @@ package android.view { } public interface WindowInsetsAnimationCallback { + method public int getDispatchMode(); method public default void onFinish(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation); method public default void onPrepare(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation); method @NonNull public android.view.WindowInsets onProgress(@NonNull android.view.WindowInsets); method @NonNull public default android.view.WindowInsetsAnimationCallback.AnimationBounds onStart(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds); + field public static final int DISPATCH_MODE_CONTINUE_ON_SUBTREE = 1; // 0x1 + field public static final int DISPATCH_MODE_STOP = 0; // 0x0 } public static final class WindowInsetsAnimationCallback.AnimationBounds { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 6724e9d3289d..5356334245a0 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -22,6 +22,7 @@ import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LO import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP; import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION; import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; +import static android.view.WindowInsetsAnimationCallback.DISPATCH_MODE_CONTINUE_ON_SUBTREE; import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED; import static java.lang.Math.max; @@ -111,6 +112,7 @@ import android.view.AccessibilityIterators.WordTextSegmentIterator; import android.view.ContextMenu.ContextMenuInfo; import android.view.WindowInsetsAnimationCallback.AnimationBounds; import android.view.WindowInsetsAnimationCallback.InsetsAnimation; +import android.view.WindowInsetsAnimationCallback.DispatchMode; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityEventSource; import android.view.accessibility.AccessibilityManager; @@ -949,22 +951,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private static boolean sAcceptZeroSizeDragShadow; /** - * Prior to Q, {@link #dispatchApplyWindowInsets} had some issues: - * <ul> - * <li>The modified insets changed by {@link #onApplyWindowInsets} were passed to the - * entire view hierarchy in prefix order, including siblings as well as siblings of parents - * further down the hierarchy. This violates the basic concepts of the view hierarchy, and - * thus, the hierarchical dispatching mechanism was hard to use for apps.</li> - * - * <li>Dispatch was stopped after the insets were fully consumed. This is somewhat confusing - * for developers, but more importantly, by adding more granular information to - * {@link WindowInsets} it becomes really cumbersome to define what consumed actually means - * </li> - * </ul> - * + * Prior to R, {@link #dispatchApplyWindowInsets} had an issue: + * <p>The modified insets changed by {@link #onApplyWindowInsets} were passed to the + * entire view hierarchy in prefix order, including siblings as well as siblings of parents + * further down the hierarchy. This violates the basic concepts of the view hierarchy, and + * thus, the hierarchical dispatching mechanism was hard to use for apps. + * <p> * In order to make window inset dispatching work properly, we dispatch window insets - * in the view hierarchy in a proper hierarchical manner and don't stop dispatching if the - * insets are consumed if this flag is set to {@code false}. + * in the view hierarchy in a proper hierarchical manner if this flag is set to {@code false}. */ static boolean sBrokenInsetsDispatch; @@ -5231,7 +5225,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, sAcceptZeroSizeDragShadow = targetSdkVersion < Build.VERSION_CODES.P; sBrokenInsetsDispatch = ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL - || targetSdkVersion < Build.VERSION_CODES.Q; + || targetSdkVersion < Build.VERSION_CODES.R; sBrokenWindowBackground = targetSdkVersion < Build.VERSION_CODES.Q; @@ -11100,7 +11094,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Sets a {@link WindowInsetsAnimationCallback} to be notified about animations of windows that * cause insets. - * + * <p> + * When setting a listener, it's {@link WindowInsetsAnimationCallback#getDispatchMode() dispatch + * mode} will be retrieved and recorded until another listener will be set. + * </p> * @param listener The listener to set. */ public void setWindowInsetsAnimationCallback(@Nullable WindowInsetsAnimationCallback listener) { diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 047d7da7536f..e6470a7d1e27 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -17,11 +17,14 @@ package android.view; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; +import static android.view.WindowInsetsAnimationCallback.DISPATCH_MODE_CONTINUE_ON_SUBTREE; +import static android.view.WindowInsetsAnimationCallback.DISPATCH_MODE_STOP; import android.animation.LayoutTransition; import android.annotation.CallSuper; import android.annotation.IdRes; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.TestApi; import android.annotation.UiThread; import android.compat.annotation.UnsupportedAppUsage; @@ -45,6 +48,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Parcelable; import android.os.SystemClock; +import android.util.ArraySet; import android.util.AttributeSet; import android.util.Log; import android.util.Pools; @@ -52,6 +56,7 @@ import android.util.Pools.SynchronizedPool; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.view.WindowInsetsAnimationCallback.AnimationBounds; +import android.view.WindowInsetsAnimationCallback.DispatchMode; import android.view.WindowInsetsAnimationCallback.InsetsAnimation; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; @@ -607,6 +612,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager int mChildUnhandledKeyListeners = 0; /** + * Current dispatch mode of animation events + * + * @see WindowInsetsAnimationCallback#getDispatchMode() + */ + private @DispatchMode int mInsetsAnimationDispatchMode = DISPATCH_MODE_CONTINUE_ON_SUBTREE; + + /** * Empty ActionMode used as a sentinel in recursive entries to startActionModeForChild. * * @see #startActionModeForChild(View, android.view.ActionMode.Callback) @@ -7170,6 +7182,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @Override public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) { insets = super.dispatchApplyWindowInsets(insets); + if (insets.isConsumed()) { + return insets; + } if (View.sBrokenInsetsDispatch) { return brokenDispatchApplyWindowInsets(insets); } else { @@ -7178,13 +7193,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } private WindowInsets brokenDispatchApplyWindowInsets(WindowInsets insets) { - if (!insets.isConsumed()) { - final int count = getChildCount(); - for (int i = 0; i < count; i++) { - insets = getChildAt(i).dispatchApplyWindowInsets(insets); - if (insets.isConsumed()) { - break; - } + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + insets = getChildAt(i).dispatchApplyWindowInsets(insets); + if (insets.isConsumed()) { + break; } } return insets; @@ -7199,9 +7212,20 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } @Override + public void setWindowInsetsAnimationCallback(@Nullable WindowInsetsAnimationCallback listener) { + super.setWindowInsetsAnimationCallback(listener); + mInsetsAnimationDispatchMode = listener != null + ? listener.getDispatchMode() + : DISPATCH_MODE_CONTINUE_ON_SUBTREE; + } + + @Override public void dispatchWindowInsetsAnimationPrepare( @NonNull InsetsAnimation animation) { super.dispatchWindowInsetsAnimationPrepare(animation); + if (mInsetsAnimationDispatchMode == DISPATCH_MODE_STOP) { + return; + } final int count = getChildCount(); for (int i = 0; i < count; i++) { getChildAt(i).dispatchWindowInsetsAnimationPrepare(animation); @@ -7212,7 +7236,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @NonNull public AnimationBounds dispatchWindowInsetsAnimationStart( @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) { - super.dispatchWindowInsetsAnimationStart(animation, bounds); + bounds = super.dispatchWindowInsetsAnimationStart(animation, bounds); + if (mInsetsAnimationDispatchMode == DISPATCH_MODE_STOP) { + return bounds; + } final int count = getChildCount(); for (int i = 0; i < count; i++) { getChildAt(i).dispatchWindowInsetsAnimationStart(animation, bounds); @@ -7224,6 +7251,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @NonNull public WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull WindowInsets insets) { insets = super.dispatchWindowInsetsAnimationProgress(insets); + if (mInsetsAnimationDispatchMode == DISPATCH_MODE_STOP) { + return insets; + } final int count = getChildCount(); for (int i = 0; i < count; i++) { getChildAt(i).dispatchWindowInsetsAnimationProgress(insets); @@ -7234,6 +7264,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @Override public void dispatchWindowInsetsAnimationFinish(@NonNull InsetsAnimation animation) { super.dispatchWindowInsetsAnimationFinish(animation); + if (mInsetsAnimationDispatchMode == DISPATCH_MODE_STOP) { + return; + } final int count = getChildCount(); for (int i = 0; i < count; i++) { getChildAt(i).dispatchWindowInsetsAnimationFinish(animation); diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index 06c24b0fa3f3..0a2a45b44523 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -42,6 +42,7 @@ import android.content.Intent; import android.graphics.Insets; import android.graphics.Rect; import android.util.SparseArray; +import android.view.View.OnApplyWindowInsetsListener; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.inputmethod.EditorInfo; @@ -96,13 +97,20 @@ public final class WindowInsets { private final boolean mCompatIgnoreVisibility; /** - * Since new insets may be added in the future that existing apps couldn't - * know about, this fully empty constant shouldn't be made available to apps - * since it would allow them to inadvertently consume unknown insets by returning it. - * @hide + * A {@link WindowInsets} instance for which {@link #isConsumed()} returns {@code true}. + * <p> + * This can be used during insets dispatch in the view hierarchy by returning this value from + * {@link View#onApplyWindowInsets(WindowInsets)} or + * {@link OnApplyWindowInsetsListener#onApplyWindowInsets(View, WindowInsets)} to stop dispatch + * the insets to its children to avoid traversing the entire view hierarchy. + * <p> + * The application should return this instance once it has taken care of all insets on a certain + * level in the view hierarchy, and doesn't need to dispatch to its children anymore for better + * performance. + * + * @see #isConsumed() */ - @UnsupportedAppUsage - public static final WindowInsets CONSUMED; + public static final @NonNull WindowInsets CONSUMED; static { CONSUMED = new WindowInsets((Rect) null, null, false, false, null); @@ -456,7 +464,11 @@ public final class WindowInsets { * Returns a copy of this WindowInsets with the cutout fully consumed. * * @return A modified copy of this WindowInsets + * @deprecated Consuming of different parts individually of a {@link WindowInsets} instance is + * deprecated, since {@link WindowInsets} contains many different insets. Use {@link #CONSUMED} + * instead to stop dispatching insets. */ + @Deprecated @NonNull public WindowInsets consumeDisplayCutout() { return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap, @@ -504,7 +516,11 @@ public final class WindowInsets { * Returns a copy of this WindowInsets with the system window insets fully consumed. * * @return A modified copy of this WindowInsets + * @deprecated Consuming of different parts individually of a {@link WindowInsets} instance is + * deprecated, since {@link WindowInsets} contains many different insets. Use {@link #CONSUMED} + * instead to stop dispatching insets. */ + @Deprecated @NonNull public WindowInsets consumeSystemWindowInsets() { return new WindowInsets(null, null, @@ -754,7 +770,11 @@ public final class WindowInsets { * Returns a copy of this WindowInsets with the stable insets fully consumed. * * @return A modified copy of this WindowInsets + * @deprecated Consuming of different parts individually of a {@link WindowInsets} instance is + * deprecated, since {@link WindowInsets} contains many different insets. Use {@link #CONSUMED} + * instead to stop dispatching insets. */ + @Deprecated @NonNull public WindowInsets consumeStableInsets() { return consumeSystemWindowInsets(); diff --git a/core/java/android/view/WindowInsetsAnimationCallback.java b/core/java/android/view/WindowInsetsAnimationCallback.java index e84c3e33c000..53d493985b32 100644 --- a/core/java/android/view/WindowInsetsAnimationCallback.java +++ b/core/java/android/view/WindowInsetsAnimationCallback.java @@ -17,6 +17,7 @@ package android.view; import android.annotation.FloatRange; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Insets; @@ -24,6 +25,9 @@ import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.animation.Interpolator; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Interface that allows the application to listen to animation events for windows that cause * insets. @@ -31,6 +35,52 @@ import android.view.animation.Interpolator; public interface WindowInsetsAnimationCallback { /** + * 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. + */ + int DISPATCH_MODE_STOP = 0; + + /** + * Return value for {@link #getDispatchMode()}: Dispatching of animation events should + * continue in the view hierarchy. + */ + int DISPATCH_MODE_CONTINUE_ON_SUBTREE = 1; + + /** @hide */ + @IntDef(prefix = { "DISPATCH_MODE_" }, value = { + DISPATCH_MODE_STOP, + DISPATCH_MODE_CONTINUE_ON_SUBTREE + }) + @Retention(RetentionPolicy.SOURCE) + @interface 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. + * <p> + * Note that this method will only be invoked once when + * {@link View#setWindowInsetsAnimationCallback setting the listener} and then the framework + * will use the recorded result. + * <p> + * Also note that returning {@link #DISPATCH_MODE_STOP} here 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 + int getDispatchMode(); + + /** * 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: @@ -75,10 +125,11 @@ public interface WindowInsetsAnimationCallback { * <p> * 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 my return a modified + * of the specific {@link View} that is being traversed. The method may return a modified * instance of the bounds by calling {@link AnimationBounds#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. + * 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. @@ -102,7 +153,9 @@ public interface WindowInsetsAnimationCallback { * 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. + * anymore. Furthermore, if {@link #getDispatchMode()} returns + * {@link #DISPATCH_MODE_STOP}, children of this view will not receive the callback anymore. + * * TODO: Introduce a way to map (type -> InsetAnimation) so app developer can query animation * for a given type e.g. callback.getAnimation(type) OR controller.getAnimation(type). * Or on the controller directly? @@ -237,6 +290,7 @@ public interface WindowInsetsAnimationCallback { * Class representing the range of an {@link InsetsAnimation} */ final class AnimationBounds { + private final Insets mLowerBound; private final Insets mUpperBound; diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java index b8b2de5141a7..f3c89d8addf6 100644 --- a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java +++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java @@ -97,6 +97,11 @@ public class WindowInsetsActivity extends Activity { mRoot.setWindowInsetsAnimationCallback(new WindowInsetsAnimationCallback() { @Override + public int getDispatchMode() { + return DISPATCH_MODE_STOP; + } + + @Override public void onPrepare(InsetsAnimation animation) { if ((animation.getTypeMask() & Type.ime()) != 0) { imeAnim = animation; |