diff options
author | Jorim Jaggi <jjaggi@google.com> | 2020-01-17 00:55:23 +0100 |
---|---|---|
committer | Jorim Jaggi <jjaggi@google.com> | 2020-01-21 17:03:04 +0100 |
commit | 6d13bf4a545c6b4edde044918e9f77067d606219 (patch) | |
tree | 8a749135f03e96c24fcaef4b708a6055503729ae | |
parent | 0865f08f5a0b065648c786243a4f36393409f9cf (diff) |
Fix up new insets dispatch behavior
- Fix up target SDK check
- Keep WindowInsets.CONSUMED concept to stop dispatch for
performance optimization. If the insets are handled at some point
in the hierarchy, we really don't need to dispatch it to all the
children. However, deprecate all the individual consuming methods
instead.
Bug: 118118435
Test: ViewGroupTest
Change-Id: I6b6627fb9c0a43444ee3f9dbeb978109f9138fbd
-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; |