summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt10
-rw-r--r--core/java/android/view/View.java31
-rw-r--r--core/java/android/view/ViewGroup.java49
-rw-r--r--core/java/android/view/WindowInsets.java32
-rw-r--r--core/java/android/view/WindowInsetsAnimationCallback.java60
-rw-r--r--tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java5
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;