From afaa932ecee84c4d2d0104b22508d3047dd69a14 Mon Sep 17 00:00:00 2001 From: Vladislav Kaznacheev Date: Thu, 9 Feb 2017 11:43:28 -0800 Subject: [PATCH] Do not move PopupWindow when its anchor is detached The current PopupWindow implementation might call the anchor view's getLocationInWindow even if the anchor is currently detached from the window (and the location is a meaningless (0,0)). This results in the popup jumping to the top of the screen. This patch is adding tracking of the anchor's attachment state. The position is never updated while the anchor is detached. When the anchor is re-attached, the popup position is updated. Bug: 34853580 Test: manual (see bug) Change-Id: Icca1b9b558a70ee3edbe6236e076d2d08a1f8f11 --- core/java/android/widget/PopupWindow.java | 44 +++++++++++++++-------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 989927e0c22d2..db876f6f0cbf5 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -208,6 +208,21 @@ public class PopupWindow { com.android.internal.R.attr.state_above_anchor }; + private final OnAttachStateChangeListener mOnAnchorDetachedListener = + new OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + // Anchor might have been reattached in a different position. + alignToAnchor(); + } + + @Override + public void onViewDetachedFromWindow(View v) { + // Leave the popup in its current position. + // The anchor might become attached again. + } + }; + private final OnAttachStateChangeListener mOnAnchorRootDetachedListener = new OnAttachStateChangeListener() { @Override @@ -223,20 +238,7 @@ public class PopupWindow { private WeakReference mAnchorRoot; private boolean mIsAnchorRootAttached; - private final OnScrollChangedListener mOnScrollChangedListener = new OnScrollChangedListener() { - @Override - public void onScrollChanged() { - final View anchor = mAnchor != null ? mAnchor.get() : null; - if (anchor != null && mDecorView != null) { - final WindowManager.LayoutParams p = (WindowManager.LayoutParams) - mDecorView.getLayoutParams(); - - updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff, - p.width, p.height, mAnchoredGravity, false)); - update(p.x, p.y, -1, -1, true); - } - } - }; + private final OnScrollChangedListener mOnScrollChangedListener = this::alignToAnchor; private int mAnchorXoff; private int mAnchorYoff; @@ -2214,6 +2216,7 @@ public class PopupWindow { if (anchor != null) { final ViewTreeObserver vto = anchor.getViewTreeObserver(); vto.removeOnScrollChangedListener(mOnScrollChangedListener); + anchor.removeOnAttachStateChangeListener(mOnAnchorDetachedListener); } final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null; @@ -2233,6 +2236,7 @@ public class PopupWindow { if (vto != null) { vto.addOnScrollChangedListener(mOnScrollChangedListener); } + anchor.addOnAttachStateChangeListener(mOnAnchorDetachedListener); final View anchorRoot = anchor.getRootView(); anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener); @@ -2247,6 +2251,18 @@ public class PopupWindow { mAnchoredGravity = gravity; } + private void alignToAnchor() { + final View anchor = mAnchor != null ? mAnchor.get() : null; + if (anchor != null && anchor.isAttachedToWindow() && mDecorView != null) { + final WindowManager.LayoutParams p = (WindowManager.LayoutParams) + mDecorView.getLayoutParams(); + + updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff, + p.width, p.height, mAnchoredGravity, false)); + update(p.x, p.y, -1, -1, true); + } + } + private class PopupDecorView extends FrameLayout { private TransitionListenerAdapter mPendingExitListener;