From 91098574f90277128415e9593cce1e495cc51465 Mon Sep 17 00:00:00 2001 From: Alan Viverette Date: Tue, 19 Jan 2016 14:07:31 -0500 Subject: [PATCH] Animate context popup menu around origin point Also enables the context popup menu enter transition. Bug: 25801140 Change-Id: Id7fb384e8ac8974189b32a052352bd2f6cbb176e --- core/java/android/widget/ListPopupWindow.java | 21 ++++++++- core/java/android/widget/PopupWindow.java | 45 +++++++++++++++++-- .../view/menu/CascadingMenuPopup.java | 4 ++ .../android/internal/view/menu/MenuPopup.java | 19 ++++++++ .../internal/view/menu/MenuPopupHelper.java | 33 +++++++++++--- .../internal/view/menu/StandardMenuPopup.java | 1 + core/res/res/values/styles_material.xml | 1 - 7 files changed, 113 insertions(+), 11 deletions(-) diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java index 595adc218e65a..dcadb6af7320a 100644 --- a/core/java/android/widget/ListPopupWindow.java +++ b/core/java/android/widget/ListPopupWindow.java @@ -74,6 +74,7 @@ public class ListPopupWindow implements ShowableListMenu { 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 @@ public class ListPopupWindow implements ShowableListMenu { 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; @@ -451,6 +458,17 @@ public class ListPopupWindow implements ShowableListMenu { mDropDownVerticalOffsetSet = true; } + /** + * 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 @@ public class ListPopupWindow implements ShowableListMenu { // 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 7e981939034b0..584df08412f22 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -150,6 +150,7 @@ public class PopupWindow { private Transition mEnterTransition; private Transition mExitTransition; + private Rect mEpicenterBounds; private boolean mAboveAnchor; private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; @@ -345,6 +346,25 @@ public class PopupWindow { mExitTransition = exitTransition; } + /** + * Sets the bounds used as the epicenter of the enter and exit transitions. + *

+ * 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. + *

+ * 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 @@ public class PopupWindow { 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 @@ public class PopupWindow { } } - private Rect getRelativeAnchorBounds() { + /** + * Returns the window-relative epicenter bounds to be used by enter and + * exit transitions. + *

+ * Note: 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 @@ public class PopupWindow { // 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 @@ public class PopupWindow { 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 320de90bbd4dc..a502fcc8219e6 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 class CascadingMenuPopup extends MenuPopup implements MenuPresenter, OnKey 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 @@ final class CascadingMenuPopup extends MenuPopup implements MenuPresenter, OnKey } 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 b151f348e58cc..42b1a56c3d1b1 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 @@ package com.android.internal.view.menu; 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 @@ import android.widget.PopupWindow; */ public abstract class MenuPopup implements ShowableListMenu, MenuPresenter, AdapterView.OnItemClickListener { + private Rect mEpicenterBounds; public abstract void setForceShowIcon(boolean forceShow); @@ -59,6 +61,23 @@ public abstract class MenuPopup implements ShowableListMenu, MenuPresenter, 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 044ee6e9bd825..1f1e594e755b3 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.NonNull; 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 @@ import android.widget.PopupWindow.OnDismissListener; * 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 @@ public class MenuPopupHelper implements MenuHelper { * Shows the popup menu and makes a best-effort to anchor it to the * specified (x,y) coordinate relative to the anchor view. *

+ * 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. + *

* 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 @@ public class MenuPopupHelper implements MenuHelper { 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 @@ public class MenuPopupHelper implements MenuHelper { 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 a67e43a1c647d..2d4baa2b894d9 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 @@ final class StandardMenuPopup extends MenuPopup implements OnDismissListener, On 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 3d5f6aba9a7b6..84d9a2a726016 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -877,7 +877,6 @@ please see styles_device_defaults.xml.