System gesture exclusion rects for Views
Allow views to register a list of rects where the device's system UI should not intercept complex (read: down+move+up) gestures on specific sub-regions of the view. This should not be used for large-scale, full-view gesture recognition such as scrolling, but rather for specific areas such as SeekBar's scroll thumb or DrawerLayout's edge strip for swiping open a navigation drawer. Add ability for ViewTreeObserver to observe transformed exclusion rects Bug: 126360272 Test: atest android.view.cts.SystemGestureExclusionRectsTest Change-Id: If89b6f66637e40efa12955d6408f6e37b25cb46f
This commit is contained in:
@@ -50515,6 +50515,7 @@ package android.view {
|
||||
method public android.animation.StateListAnimator getStateListAnimator();
|
||||
method protected int getSuggestedMinimumHeight();
|
||||
method protected int getSuggestedMinimumWidth();
|
||||
method @NonNull public java.util.List<android.graphics.Rect> getSystemGestureExclusionRects();
|
||||
method public int getSystemUiVisibility();
|
||||
method @android.view.ViewDebug.ExportedProperty public Object getTag();
|
||||
method public Object getTag(int);
|
||||
@@ -50853,6 +50854,7 @@ package android.view {
|
||||
method public void setSelected(boolean);
|
||||
method public void setSoundEffectsEnabled(boolean);
|
||||
method public void setStateListAnimator(android.animation.StateListAnimator);
|
||||
method public void setSystemGestureExclusionRects(@NonNull java.util.List<android.graphics.Rect>);
|
||||
method public void setSystemUiVisibility(int);
|
||||
method public void setTag(Object);
|
||||
method public void setTag(int, Object);
|
||||
@@ -51681,6 +51683,7 @@ package android.view {
|
||||
method public void addOnGlobalLayoutListener(android.view.ViewTreeObserver.OnGlobalLayoutListener);
|
||||
method public void addOnPreDrawListener(android.view.ViewTreeObserver.OnPreDrawListener);
|
||||
method public void addOnScrollChangedListener(android.view.ViewTreeObserver.OnScrollChangedListener);
|
||||
method public void addOnSystemGestureExclusionRectsChangedListener(java.util.function.Consumer<java.util.List<android.graphics.Rect>>);
|
||||
method public void addOnTouchModeChangeListener(android.view.ViewTreeObserver.OnTouchModeChangeListener);
|
||||
method public void addOnWindowAttachListener(android.view.ViewTreeObserver.OnWindowAttachListener);
|
||||
method public void addOnWindowFocusChangeListener(android.view.ViewTreeObserver.OnWindowFocusChangeListener);
|
||||
@@ -51695,6 +51698,7 @@ package android.view {
|
||||
method public void removeOnGlobalLayoutListener(android.view.ViewTreeObserver.OnGlobalLayoutListener);
|
||||
method public void removeOnPreDrawListener(android.view.ViewTreeObserver.OnPreDrawListener);
|
||||
method public void removeOnScrollChangedListener(android.view.ViewTreeObserver.OnScrollChangedListener);
|
||||
method public void removeOnSystemGestureExclusionRectsChangedListener(java.util.function.Consumer<java.util.List<android.graphics.Rect>>);
|
||||
method public void removeOnTouchModeChangeListener(android.view.ViewTreeObserver.OnTouchModeChangeListener);
|
||||
method public void removeOnWindowAttachListener(android.view.ViewTreeObserver.OnWindowAttachListener);
|
||||
method public void removeOnWindowFocusChangeListener(android.view.ViewTreeObserver.OnWindowFocusChangeListener);
|
||||
|
||||
125
core/java/android/view/GestureExclusionTracker.java
Normal file
125
core/java/android/view/GestureExclusionTracker.java
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.graphics.Rect;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Used by {@link ViewRootImpl} to track system gesture exclusion rects reported by views.
|
||||
*/
|
||||
class GestureExclusionTracker {
|
||||
private boolean mGestureExclusionViewsChanged = false;
|
||||
private List<GestureExclusionViewInfo> mGestureExclusionViewInfos = new ArrayList<>();
|
||||
private List<Rect> mGestureExclusionRects = Collections.emptyList();
|
||||
|
||||
public void updateRectsForView(@NonNull View view) {
|
||||
boolean found = false;
|
||||
final Iterator<GestureExclusionViewInfo> i = mGestureExclusionViewInfos.iterator();
|
||||
while (i.hasNext()) {
|
||||
final GestureExclusionViewInfo info = i.next();
|
||||
final View v = info.getView();
|
||||
if (v == null || !v.isAttachedToWindow()) {
|
||||
mGestureExclusionViewsChanged = true;
|
||||
i.remove();
|
||||
continue;
|
||||
}
|
||||
if (v == view) {
|
||||
found = true;
|
||||
info.mDirty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found && view.isAttachedToWindow()) {
|
||||
mGestureExclusionViewInfos.add(new GestureExclusionViewInfo(view));
|
||||
mGestureExclusionViewsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public List<Rect> computeChangedRects() {
|
||||
boolean changed = false;
|
||||
final Iterator<GestureExclusionViewInfo> i = mGestureExclusionViewInfos.iterator();
|
||||
final List<Rect> rects = new ArrayList<>();
|
||||
while (i.hasNext()) {
|
||||
final GestureExclusionViewInfo info = i.next();
|
||||
switch (info.update()) {
|
||||
case GestureExclusionViewInfo.CHANGED:
|
||||
changed = true;
|
||||
// Deliberate fall-through
|
||||
case GestureExclusionViewInfo.UNCHANGED:
|
||||
rects.addAll(info.mExclusionRects);
|
||||
break;
|
||||
case GestureExclusionViewInfo.GONE:
|
||||
mGestureExclusionViewsChanged = true;
|
||||
i.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (changed || mGestureExclusionViewsChanged) {
|
||||
mGestureExclusionViewsChanged = false;
|
||||
if (!mGestureExclusionRects.equals(rects)) {
|
||||
mGestureExclusionRects = rects;
|
||||
return rects;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class GestureExclusionViewInfo {
|
||||
public static final int CHANGED = 0;
|
||||
public static final int UNCHANGED = 1;
|
||||
public static final int GONE = 2;
|
||||
|
||||
private final WeakReference<View> mView;
|
||||
boolean mDirty = true;
|
||||
List<Rect> mExclusionRects = Collections.emptyList();
|
||||
|
||||
GestureExclusionViewInfo(View view) {
|
||||
mView = new WeakReference<>(view);
|
||||
}
|
||||
|
||||
public View getView() {
|
||||
return mView.get();
|
||||
}
|
||||
|
||||
public int update() {
|
||||
final View excludedView = getView();
|
||||
if (excludedView == null || !excludedView.isAttachedToWindow()) return GONE;
|
||||
final List<Rect> localRects = excludedView.getSystemGestureExclusionRects();
|
||||
final List<Rect> newRects = new ArrayList<>(localRects.size());
|
||||
for (Rect src : localRects) {
|
||||
Rect mappedRect = new Rect(src);
|
||||
ViewParent p = excludedView.getParent();
|
||||
if (p != null && p.getChildVisibleRect(excludedView, mappedRect, null)) {
|
||||
newRects.add(mappedRect);
|
||||
}
|
||||
}
|
||||
|
||||
if (mExclusionRects.equals(localRects)) return UNCHANGED;
|
||||
mExclusionRects = newRects;
|
||||
return CHANGED;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,7 +210,7 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb
|
||||
|
||||
public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
mRenderNode.requestPositionUpdates(mPositionListener);
|
||||
mRenderNode.addPositionUpdateListener(mPositionListener);
|
||||
|
||||
setWillNotDraw(true);
|
||||
}
|
||||
|
||||
@@ -4603,6 +4603,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
private ArrayList<OnUnhandledKeyEventListener> mUnhandledKeyListeners;
|
||||
|
||||
private WindowInsetsAnimationListener mWindowInsetsAnimationListener;
|
||||
|
||||
/**
|
||||
* This lives here since it's only valid for interactive views.
|
||||
*/
|
||||
private List<Rect> mSystemGestureExclusionRects;
|
||||
|
||||
/**
|
||||
* Used to track {@link #mSystemGestureExclusionRects}
|
||||
*/
|
||||
public RenderNode.PositionUpdateListener mPositionUpdateListener;
|
||||
}
|
||||
|
||||
@UnsupportedAppUsage
|
||||
@@ -10973,6 +10983,95 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a list of areas within this view's post-layout coordinate space where the system
|
||||
* should not intercept touch or other pointing device gestures. <em>This method should
|
||||
* be called by {@link #onLayout(boolean, int, int, int, int)} or {@link #onDraw(Canvas)}.</em>
|
||||
*
|
||||
* <p>Use this to tell the system which specific sub-areas of a view need to receive gesture
|
||||
* input in order to function correctly in the presence of global system gestures that may
|
||||
* conflict. For example, if the system wishes to capture swipe-in-from-screen-edge gestures
|
||||
* to provide system-level navigation functionality, a view such as a navigation drawer
|
||||
* container can mark the left (or starting) edge of itself as requiring gesture capture
|
||||
* priority using this API. The system may then choose to relax its own gesture recognition
|
||||
* to allow the app to consume the user's gesture. It is not necessary for an app to register
|
||||
* exclusion rects for broadly spanning regions such as the entirety of a
|
||||
* <code>ScrollView</code> or for simple press and release click targets such as
|
||||
* <code>Button</code>. Mark an exclusion rect when interacting with a view requires
|
||||
* a precision touch gesture in a small area in either the X or Y dimension, such as
|
||||
* an edge swipe or dragging a <code>SeekBar</code> thumb.</p>
|
||||
*
|
||||
* <p>Do not modify the provided list after this method is called.</p>
|
||||
*
|
||||
* @param rects A list of precision gesture regions that this view needs to function correctly
|
||||
*/
|
||||
public void setSystemGestureExclusionRects(@NonNull List<Rect> rects) {
|
||||
if (rects.isEmpty() && mListenerInfo == null) return;
|
||||
|
||||
final ListenerInfo info = getListenerInfo();
|
||||
if (rects.isEmpty()) {
|
||||
info.mSystemGestureExclusionRects = null;
|
||||
if (info.mPositionUpdateListener != null) {
|
||||
mRenderNode.removePositionUpdateListener(info.mPositionUpdateListener);
|
||||
}
|
||||
} else {
|
||||
info.mSystemGestureExclusionRects = rects;
|
||||
if (info.mPositionUpdateListener == null) {
|
||||
info.mPositionUpdateListener = new RenderNode.PositionUpdateListener() {
|
||||
@Override
|
||||
public void positionChanged(long n, int l, int t, int r, int b) {
|
||||
postUpdateSystemGestureExclusionRects();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void positionLost(long frameNumber) {
|
||||
postUpdateSystemGestureExclusionRects();
|
||||
}
|
||||
};
|
||||
mRenderNode.addPositionUpdateListener(info.mPositionUpdateListener);
|
||||
}
|
||||
}
|
||||
postUpdateSystemGestureExclusionRects();
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING: this can be called by a hwui worker thread, not just the UI thread!
|
||||
*/
|
||||
void postUpdateSystemGestureExclusionRects() {
|
||||
// Potentially racey from a background thread. It's ok if it's not perfect.
|
||||
final Handler h = getHandler();
|
||||
if (h != null) {
|
||||
h.postAtFrontOfQueue(this::updateSystemGestureExclusionRects);
|
||||
}
|
||||
}
|
||||
|
||||
void updateSystemGestureExclusionRects() {
|
||||
final AttachInfo ai = mAttachInfo;
|
||||
if (ai != null) {
|
||||
ai.mViewRootImpl.updateSystemGestureExclusionRectsForView(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the list of areas within this view's post-layout coordinate space where the system
|
||||
* should not intercept touch or other pointing device gestures.
|
||||
*
|
||||
* <p>Do not modify the returned list.</p>
|
||||
*
|
||||
* @return the list set by {@link #setSystemGestureExclusionRects(List)}
|
||||
*/
|
||||
@NonNull
|
||||
public List<Rect> getSystemGestureExclusionRects() {
|
||||
final ListenerInfo info = mListenerInfo;
|
||||
if (info != null) {
|
||||
final List<Rect> list = info.mSystemGestureExclusionRects;
|
||||
if (list != null) {
|
||||
return list;
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the view's coordinate within the surface.
|
||||
*
|
||||
|
||||
@@ -605,6 +605,8 @@ public final class ViewRootImpl implements ViewParent,
|
||||
|
||||
private final InsetsController mInsetsController = new InsetsController(this);
|
||||
|
||||
private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker();
|
||||
|
||||
static final class SystemUiVisibilityInfo {
|
||||
int seq;
|
||||
int globalVisibility;
|
||||
@@ -3977,6 +3979,20 @@ public final class ViewRootImpl implements ViewParent,
|
||||
return mAttachInfo.mAccessibilityFocusDrawable;
|
||||
}
|
||||
|
||||
void updateSystemGestureExclusionRectsForView(View view) {
|
||||
mGestureExclusionTracker.updateRectsForView(view);
|
||||
mHandler.sendEmptyMessage(MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED);
|
||||
}
|
||||
|
||||
void systemGestureExclusionChanged() {
|
||||
final List<Rect> rectsForWindowManager = mGestureExclusionTracker.computeChangedRects();
|
||||
if (rectsForWindowManager != null) {
|
||||
// TODO Send to WM
|
||||
mAttachInfo.mTreeObserver
|
||||
.dispatchOnSystemGestureExclusionRectsChanged(rectsForWindowManager);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that the root render node is invalidated next time we perform a draw, such that
|
||||
* {@link WindowCallbacks#onPostDraw} gets called.
|
||||
@@ -4433,35 +4449,36 @@ public final class ViewRootImpl implements ViewParent,
|
||||
}
|
||||
}
|
||||
|
||||
private final static int MSG_INVALIDATE = 1;
|
||||
private final static int MSG_INVALIDATE_RECT = 2;
|
||||
private final static int MSG_DIE = 3;
|
||||
private final static int MSG_RESIZED = 4;
|
||||
private final static int MSG_RESIZED_REPORT = 5;
|
||||
private final static int MSG_WINDOW_FOCUS_CHANGED = 6;
|
||||
private final static int MSG_DISPATCH_INPUT_EVENT = 7;
|
||||
private final static int MSG_DISPATCH_APP_VISIBILITY = 8;
|
||||
private final static int MSG_DISPATCH_GET_NEW_SURFACE = 9;
|
||||
private final static int MSG_DISPATCH_KEY_FROM_IME = 11;
|
||||
private final static int MSG_DISPATCH_KEY_FROM_AUTOFILL = 12;
|
||||
private final static int MSG_CHECK_FOCUS = 13;
|
||||
private final static int MSG_CLOSE_SYSTEM_DIALOGS = 14;
|
||||
private final static int MSG_DISPATCH_DRAG_EVENT = 15;
|
||||
private final static int MSG_DISPATCH_DRAG_LOCATION_EVENT = 16;
|
||||
private final static int MSG_DISPATCH_SYSTEM_UI_VISIBILITY = 17;
|
||||
private final static int MSG_UPDATE_CONFIGURATION = 18;
|
||||
private final static int MSG_PROCESS_INPUT_EVENTS = 19;
|
||||
private final static int MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST = 21;
|
||||
private final static int MSG_INVALIDATE_WORLD = 22;
|
||||
private final static int MSG_WINDOW_MOVED = 23;
|
||||
private final static int MSG_SYNTHESIZE_INPUT_EVENT = 24;
|
||||
private final static int MSG_DISPATCH_WINDOW_SHOWN = 25;
|
||||
private final static int MSG_REQUEST_KEYBOARD_SHORTCUTS = 26;
|
||||
private final static int MSG_UPDATE_POINTER_ICON = 27;
|
||||
private final static int MSG_POINTER_CAPTURE_CHANGED = 28;
|
||||
private final static int MSG_DRAW_FINISHED = 29;
|
||||
private final static int MSG_INSETS_CHANGED = 30;
|
||||
private final static int MSG_INSETS_CONTROL_CHANGED = 31;
|
||||
private static final int MSG_INVALIDATE = 1;
|
||||
private static final int MSG_INVALIDATE_RECT = 2;
|
||||
private static final int MSG_DIE = 3;
|
||||
private static final int MSG_RESIZED = 4;
|
||||
private static final int MSG_RESIZED_REPORT = 5;
|
||||
private static final int MSG_WINDOW_FOCUS_CHANGED = 6;
|
||||
private static final int MSG_DISPATCH_INPUT_EVENT = 7;
|
||||
private static final int MSG_DISPATCH_APP_VISIBILITY = 8;
|
||||
private static final int MSG_DISPATCH_GET_NEW_SURFACE = 9;
|
||||
private static final int MSG_DISPATCH_KEY_FROM_IME = 11;
|
||||
private static final int MSG_DISPATCH_KEY_FROM_AUTOFILL = 12;
|
||||
private static final int MSG_CHECK_FOCUS = 13;
|
||||
private static final int MSG_CLOSE_SYSTEM_DIALOGS = 14;
|
||||
private static final int MSG_DISPATCH_DRAG_EVENT = 15;
|
||||
private static final int MSG_DISPATCH_DRAG_LOCATION_EVENT = 16;
|
||||
private static final int MSG_DISPATCH_SYSTEM_UI_VISIBILITY = 17;
|
||||
private static final int MSG_UPDATE_CONFIGURATION = 18;
|
||||
private static final int MSG_PROCESS_INPUT_EVENTS = 19;
|
||||
private static final int MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST = 21;
|
||||
private static final int MSG_INVALIDATE_WORLD = 22;
|
||||
private static final int MSG_WINDOW_MOVED = 23;
|
||||
private static final int MSG_SYNTHESIZE_INPUT_EVENT = 24;
|
||||
private static final int MSG_DISPATCH_WINDOW_SHOWN = 25;
|
||||
private static final int MSG_REQUEST_KEYBOARD_SHORTCUTS = 26;
|
||||
private static final int MSG_UPDATE_POINTER_ICON = 27;
|
||||
private static final int MSG_POINTER_CAPTURE_CHANGED = 28;
|
||||
private static final int MSG_DRAW_FINISHED = 29;
|
||||
private static final int MSG_INSETS_CHANGED = 30;
|
||||
private static final int MSG_INSETS_CONTROL_CHANGED = 31;
|
||||
private static final int MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED = 32;
|
||||
|
||||
final class ViewRootHandler extends Handler {
|
||||
@Override
|
||||
@@ -4519,6 +4536,10 @@ public final class ViewRootImpl implements ViewParent,
|
||||
return "MSG_DRAW_FINISHED";
|
||||
case MSG_INSETS_CHANGED:
|
||||
return "MSG_INSETS_CHANGED";
|
||||
case MSG_INSETS_CONTROL_CHANGED:
|
||||
return "MSG_INSETS_CONTROL_CHANGED";
|
||||
case MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED:
|
||||
return "MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED";
|
||||
}
|
||||
return super.getMessageName(message);
|
||||
}
|
||||
@@ -4750,6 +4771,9 @@ public final class ViewRootImpl implements ViewParent,
|
||||
case MSG_DRAW_FINISHED: {
|
||||
pendingDrawFinished();
|
||||
} break;
|
||||
case MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED: {
|
||||
systemGestureExclusionChanged();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,9 @@ import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A view tree observer is used to register listeners that can be notified of global
|
||||
@@ -57,6 +59,7 @@ public final class ViewTreeObserver {
|
||||
private CopyOnWriteArray<OnScrollChangedListener> mOnScrollChangedListeners;
|
||||
private CopyOnWriteArray<OnPreDrawListener> mOnPreDrawListeners;
|
||||
private CopyOnWriteArray<OnWindowShownListener> mOnWindowShownListeners;
|
||||
private CopyOnWriteArray<Consumer<List<Rect>>> mGestureExclusionListeners;
|
||||
|
||||
// These listeners cannot be mutated during dispatch
|
||||
private boolean mInDispatchOnDraw;
|
||||
@@ -450,6 +453,14 @@ public final class ViewTreeObserver {
|
||||
}
|
||||
}
|
||||
|
||||
if (observer.mGestureExclusionListeners != null) {
|
||||
if (mGestureExclusionListeners != null) {
|
||||
mGestureExclusionListeners.addAll(observer.mGestureExclusionListeners);
|
||||
} else {
|
||||
mGestureExclusionListeners = observer.mGestureExclusionListeners;
|
||||
}
|
||||
}
|
||||
|
||||
observer.kill();
|
||||
}
|
||||
|
||||
@@ -913,6 +924,35 @@ public final class ViewTreeObserver {
|
||||
mOnEnterAnimationCompleteListeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener to be notified when the tree's <em>transformed</em> gesture exclusion rects
|
||||
* change. This could be the result of an animation or other layout change, or a view calling
|
||||
* {@link View#setSystemGestureExclusionRects(List)}.
|
||||
*
|
||||
* @param listener listener to add
|
||||
* @see View#setSystemGestureExclusionRects(List)
|
||||
*/
|
||||
public void addOnSystemGestureExclusionRectsChangedListener(Consumer<List<Rect>> listener) {
|
||||
checkIsAlive();
|
||||
if (mGestureExclusionListeners == null) {
|
||||
mGestureExclusionListeners = new CopyOnWriteArray<>();
|
||||
}
|
||||
mGestureExclusionListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe the given listener from gesture exclusion rect changes.
|
||||
* @see #addOnSystemGestureExclusionRectsChangedListener(Consumer)
|
||||
* @see View#setSystemGestureExclusionRects(List)
|
||||
*/
|
||||
public void removeOnSystemGestureExclusionRectsChangedListener(Consumer<List<Rect>> listener) {
|
||||
checkIsAlive();
|
||||
if (mGestureExclusionListeners == null) {
|
||||
return;
|
||||
}
|
||||
mGestureExclusionListeners.remove(listener);
|
||||
}
|
||||
|
||||
private void checkIsAlive() {
|
||||
if (!mAlive) {
|
||||
throw new IllegalStateException("This ViewTreeObserver is not alive, call "
|
||||
@@ -1178,6 +1218,21 @@ public final class ViewTreeObserver {
|
||||
}
|
||||
}
|
||||
|
||||
void dispatchOnSystemGestureExclusionRectsChanged(@NonNull List<Rect> rects) {
|
||||
final CopyOnWriteArray<Consumer<List<Rect>>> listeners = mGestureExclusionListeners;
|
||||
if (listeners != null && listeners.size() > 0) {
|
||||
CopyOnWriteArray.Access<Consumer<List<Rect>>> access = listeners.start();
|
||||
try {
|
||||
final int count = access.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
access.get(i).accept(rects);
|
||||
}
|
||||
} finally {
|
||||
listeners.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy on write array. This array is not thread safe, and only one loop can
|
||||
* iterate over this array at any given time. This class avoids allocations
|
||||
|
||||
@@ -27,6 +27,8 @@ import android.view.RenderNodeAnimator;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
|
||||
import dalvik.annotation.optimization.CriticalNative;
|
||||
import dalvik.annotation.optimization.FastNative;
|
||||
|
||||
@@ -179,6 +181,10 @@ public final class RenderNode {
|
||||
private final AnimationHost mAnimationHost;
|
||||
private RecordingCanvas mCurrentRecordingCanvas;
|
||||
|
||||
// Will be null if not currently registered
|
||||
@Nullable
|
||||
private CompositePositionUpdateListener mCompositePositionUpdateListener;
|
||||
|
||||
/**
|
||||
* Creates a new RenderNode that can be used to record batches of
|
||||
* drawing operations, and store / apply render properties when drawn.
|
||||
@@ -248,15 +254,70 @@ public final class RenderNode {
|
||||
|
||||
}
|
||||
|
||||
private static final class CompositePositionUpdateListener implements PositionUpdateListener {
|
||||
private final PositionUpdateListener[] mListeners;
|
||||
|
||||
CompositePositionUpdateListener(PositionUpdateListener... listeners) {
|
||||
mListeners = listeners;
|
||||
}
|
||||
|
||||
public CompositePositionUpdateListener with(PositionUpdateListener listener) {
|
||||
return new CompositePositionUpdateListener(
|
||||
ArrayUtils.appendElement(PositionUpdateListener.class, mListeners, listener));
|
||||
}
|
||||
|
||||
public CompositePositionUpdateListener without(PositionUpdateListener listener) {
|
||||
return new CompositePositionUpdateListener(
|
||||
ArrayUtils.removeElement(PositionUpdateListener.class, mListeners, listener));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void positionChanged(long frameNumber, int left, int top, int right, int bottom) {
|
||||
for (PositionUpdateListener pul : mListeners) {
|
||||
pul.positionChanged(frameNumber, left, top, right, bottom);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void positionLost(long frameNumber) {
|
||||
for (PositionUpdateListener pul : mListeners) {
|
||||
pul.positionLost(frameNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable callbacks for position changes.
|
||||
* Enable callbacks for position changes. Call only from the UI thread or with
|
||||
* external synchronization.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void requestPositionUpdates(PositionUpdateListener listener) {
|
||||
nRequestPositionUpdates(mNativeRenderNode, listener);
|
||||
public void addPositionUpdateListener(@NonNull PositionUpdateListener listener) {
|
||||
CompositePositionUpdateListener comp = mCompositePositionUpdateListener;
|
||||
if (comp == null) {
|
||||
comp = new CompositePositionUpdateListener(listener);
|
||||
} else {
|
||||
comp = comp.with(listener);
|
||||
}
|
||||
mCompositePositionUpdateListener = comp;
|
||||
nRequestPositionUpdates(mNativeRenderNode, comp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable a callback for position changes. Call only from the UI thread or with
|
||||
* external synchronization.
|
||||
*
|
||||
* @param listener Callback to remove
|
||||
* @hide
|
||||
*/
|
||||
public void removePositionUpdateListener(@NonNull PositionUpdateListener listener) {
|
||||
CompositePositionUpdateListener comp = mCompositePositionUpdateListener;
|
||||
if (comp != null) {
|
||||
comp = comp.without(listener);
|
||||
mCompositePositionUpdateListener = comp;
|
||||
nRequestPositionUpdates(mNativeRenderNode, comp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts recording a display list for the render node. All
|
||||
|
||||
@@ -53,7 +53,7 @@ public class PositionListenerActivity extends Activity {
|
||||
MyPositionReporter(Context c) {
|
||||
super(c);
|
||||
mNode = new RenderNode("positionListener");
|
||||
mNode.requestPositionUpdates(this);
|
||||
mNode.addPositionUpdateListener(this);
|
||||
setTextAlignment(TEXT_ALIGNMENT_VIEW_START);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user