Animate context popup menu around origin point
Also enables the context popup menu enter transition.
Bug: 25801140
Change-Id: Id7fb384e8ac8974189b32a052352bd2f6cbb176e
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java
index 595adc2..dcadb6a 100644
--- a/core/java/android/widget/ListPopupWindow.java
+++ b/core/java/android/widget/ListPopupWindow.java
@@ -74,6 +74,7 @@
private int mDropDownVerticalOffset;
private int mDropDownWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
private boolean mDropDownVerticalOffsetSet;
+ private boolean mIsAnimatedFromAnchor = true;
private int mDropDownGravity = Gravity.NO_GRAVITY;
@@ -101,7 +102,13 @@
private final Handler mHandler;
- private Rect mTempRect = new Rect();
+ private final Rect mTempRect = new Rect();
+
+ /**
+ * Optional anchor-relative bounds to be used as the transition epicenter.
+ * When {@code null}, the anchor bounds are used as the epicenter.
+ */
+ private Rect mEpicenterBounds;
private boolean mModal;
@@ -452,6 +459,17 @@
}
/**
+ * Specifies the anchor-relative bounds of the popup's transition
+ * epicenter.
+ *
+ * @param bounds anchor-relative bounds
+ * @hide
+ */
+ public void setEpicenterBounds(Rect bounds) {
+ mEpicenterBounds = bounds;
+ }
+
+ /**
* Set the gravity of the dropdown list. This is commonly used to
* set gravity to START or END for alignment with the anchor.
*
@@ -649,6 +667,7 @@
// only set this if the dropdown is not always visible
mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
mPopup.setTouchInterceptor(mTouchInterceptor);
+ mPopup.setEpicenterBounds(mEpicenterBounds);
mPopup.showAsDropDown(getAnchorView(), mDropDownHorizontalOffset,
mDropDownVerticalOffset, mDropDownGravity);
mDropDownList.setSelection(ListView.INVALID_POSITION);
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 7e98193..584df08 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -150,6 +150,7 @@
private Transition mEnterTransition;
private Transition mExitTransition;
+ private Rect mEpicenterBounds;
private boolean mAboveAnchor;
private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
@@ -345,6 +346,25 @@
mExitTransition = exitTransition;
}
+ /**
+ * Sets the bounds used as the epicenter of the enter and exit transitions.
+ * <p>
+ * Transitions use a point or Rect, referred to as the epicenter, to orient
+ * the direction of travel. For popup windows, the anchor view bounds are
+ * used as the default epicenter.
+ * <p>
+ * See {@link Transition#setEpicenterCallback(EpicenterCallback)} for more
+ * information about how transition epicenters.
+ *
+ * @param bounds the epicenter bounds relative to the anchor view, or
+ * {@code null} to use the default epicenter
+ * @see #getTransitionEpicenter()
+ * @hide
+ */
+ public void setEpicenterBounds(Rect bounds) {
+ mEpicenterBounds = bounds;
+ }
+
private Transition getTransition(int resId) {
if (resId != 0 && resId != R.transition.no_transition) {
final TransitionInflater inflater = TransitionInflater.from(mContext);
@@ -1621,7 +1641,7 @@
p.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
mWindowManager.updateViewLayout(decorView, p);
- final Rect epicenter = getRelativeAnchorBounds();
+ final Rect epicenter = getTransitionEpicenter();
exitTransition.setEpicenterCallback(new EpicenterCallback() {
@Override
public Rect onGetEpicenter(Transition transition) {
@@ -1646,7 +1666,17 @@
}
}
- private Rect getRelativeAnchorBounds() {
+ /**
+ * Returns the window-relative epicenter bounds to be used by enter and
+ * exit transitions.
+ * <p>
+ * <strong>Note:</strong> This is distinct from the rect passed to
+ * {@link #setEpicenterBounds(Rect)}, which is anchor-relative.
+ *
+ * @return the window-relative epicenter bounds to be used by enter and
+ * exit transitions
+ */
+ private Rect getTransitionEpicenter() {
final View anchor = mAnchor != null ? mAnchor.get() : null;
final View decor = mDecorView;
if (anchor == null || decor == null) {
@@ -1659,6 +1689,15 @@
// Compute the position of the anchor relative to the popup.
final Rect bounds = new Rect(0, 0, anchor.getWidth(), anchor.getHeight());
bounds.offset(anchorLocation[0] - popupLocation[0], anchorLocation[1] - popupLocation[1]);
+
+ // Use anchor-relative epicenter, if specified.
+ if (mEpicenterBounds != null) {
+ final int offsetX = bounds.left;
+ final int offsetY = bounds.top;
+ bounds.set(mEpicenterBounds);
+ bounds.offset(offsetX, offsetY);
+ }
+
return bounds;
}
@@ -2031,7 +2070,7 @@
observer.removeOnGlobalLayoutListener(this);
}
- final Rect epicenter = getRelativeAnchorBounds();
+ final Rect epicenter = getTransitionEpicenter();
enterTransition.setEpicenterCallback(new EpicenterCallback() {
@Override
public Rect onGetEpicenter(Transition transition) {
diff --git a/core/java/com/android/internal/view/menu/CascadingMenuPopup.java b/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
index 320de90..a502fcc 100644
--- a/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
+++ b/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
@@ -362,6 +362,7 @@
final int x;
final int y;
+ final Rect epicenterBounds;
if (parentView != null) {
// This menu is a cascading submenu anchored to a parent view.
popupWindow.setTouchModal(false);
@@ -396,13 +397,16 @@
}
y = parentOffsetTop;
+ epicenterBounds = null;
} else {
x = mInitXOffset;
y = mInitYOffset;
+ epicenterBounds = getEpicenterBounds();
}
popupWindow.setHorizontalOffset(x);
popupWindow.setVerticalOffset(y);
+ popupWindow.setEpicenterBounds(epicenterBounds);
final CascadingMenuInfo menuInfo = new CascadingMenuInfo(popupWindow, menu, mLastPosition);
mShowingMenus.add(menuInfo);
diff --git a/core/java/com/android/internal/view/menu/MenuPopup.java b/core/java/com/android/internal/view/menu/MenuPopup.java
index b151f34..42b1a56 100644
--- a/core/java/com/android/internal/view/menu/MenuPopup.java
+++ b/core/java/com/android/internal/view/menu/MenuPopup.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.graphics.Rect;
import android.view.MenuItem;
import android.view.View;
import android.view.View.MeasureSpec;
@@ -37,6 +38,7 @@
*/
public abstract class MenuPopup implements ShowableListMenu, MenuPresenter,
AdapterView.OnItemClickListener {
+ private Rect mEpicenterBounds;
public abstract void setForceShowIcon(boolean forceShow);
@@ -60,6 +62,23 @@
public abstract void setVerticalOffset(int y);
/**
+ * Specifies the anchor-relative bounds of the popup's transition
+ * epicenter.
+ *
+ * @param bounds anchor-relative bounds
+ */
+ public void setEpicenterBounds(Rect bounds) {
+ mEpicenterBounds = bounds;
+ }
+
+ /**
+ * @return anchor-relative bounds of the popup's transition epicenter
+ */
+ public Rect getEpicenterBounds() {
+ return mEpicenterBounds;
+ }
+
+ /**
* Set whether a title entry should be shown in the popup menu (if a title exists for the
* menu).
*
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index 044ee6e..1f1e594 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -23,6 +23,8 @@
import android.annotation.Nullable;
import android.annotation.StyleRes;
import android.content.Context;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.View;
import android.widget.PopupWindow.OnDismissListener;
@@ -31,6 +33,8 @@
* Presents a menu as a small, simple popup anchored to another view.
*/
public class MenuPopupHelper implements MenuHelper {
+ private static final int TOUCH_EPICENTER_SIZE_DP = 48;
+
private final Context mContext;
// Immutable cached popup menu properties.
@@ -163,6 +167,11 @@
* Shows the popup menu and makes a best-effort to anchor it to the
* specified (x,y) coordinate relative to the anchor view.
* <p>
+ * Additionally, the popup's transition epicenter (see
+ * {@link android.widget.PopupWindow#setEpicenterBounds(Rect)} will be
+ * centered on the specified coordinate, rather than using the bounds of
+ * the anchor view.
+ * <p>
* If the popup's resolved gravity is {@link Gravity#LEFT}, this will
* display the popup with its top-left corner at (x,y) relative to the
* anchor view. If the resolved gravity is {@link Gravity#RIGHT}, the
@@ -222,8 +231,11 @@
return popup;
}
- private void showPopup(int xOffset, int yOffset, boolean resolveOffsets, boolean showTitle) {
- if (resolveOffsets) {
+ private void showPopup(int xOffset, int yOffset, boolean useOffsets, boolean showTitle) {
+ final MenuPopup popup = getPopup();
+ popup.setShowTitle(showTitle);
+
+ if (useOffsets) {
// If the resolved drop-down gravity is RIGHT, the popup's right
// edge will be aligned with the anchor view. Adjust by the anchor
// width such that the top-right corner is at the X offset.
@@ -232,12 +244,21 @@
if (hgrav == Gravity.RIGHT) {
xOffset -= mAnchorView.getWidth();
}
+
+ popup.setHorizontalOffset(xOffset);
+ popup.setVerticalOffset(yOffset);
+
+ // Set the transition epicenter to be roughly finger (or mouse
+ // cursor) sized and centered around the offset position. This
+ // will give the appearance that the window is emerging from
+ // the touch point.
+ final float density = mContext.getResources().getDisplayMetrics().density;
+ final int halfSize = (int) (TOUCH_EPICENTER_SIZE_DP * density / 2);
+ final Rect epicenter = new Rect(xOffset - halfSize, yOffset - halfSize,
+ xOffset + halfSize, yOffset + halfSize);
+ popup.setEpicenterBounds(epicenter);
}
- final MenuPopup popup = getPopup();
- popup.setHorizontalOffset(xOffset);
- popup.setVerticalOffset(yOffset);
- popup.setShowTitle(showTitle);
popup.show();
}
diff --git a/core/java/com/android/internal/view/menu/StandardMenuPopup.java b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
index a67e43a..2d4baa2 100644
--- a/core/java/com/android/internal/view/menu/StandardMenuPopup.java
+++ b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
@@ -176,6 +176,7 @@
mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
mPopup.setHorizontalOffset(mXOffset);
mPopup.setVerticalOffset(mYOffset);
+ mPopup.setEpicenterBounds(getEpicenterBounds());
mPopup.show();
ListView listView = mPopup.getListView();
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 3d5f6ab..84d9a2a 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -877,7 +877,6 @@
<style name="Widget.Material.ContextPopupMenu" parent="Widget.Material.ListPopupWindow">
<item name="overlapAnchor">true</item>
- <item name="popupEnterTransition">@null</item>
</style>
<style name="Widget.Material.ActionButton">