Animate context popup menu around origin point

Also enables the context popup menu enter transition.

Bug: 25801140
Change-Id: Id7fb384e8ac8974189b32a052352bd2f6cbb176e
This commit is contained in:
Alan Viverette
2016-01-19 14:07:31 -05:00
parent 82b425f853
commit 91098574f9
7 changed files with 113 additions and 11 deletions

View File

@@ -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);

View File

@@ -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.
* <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 @@ 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.
* <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 @@ 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) {

View File

@@ -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);

View File

@@ -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).

View File

@@ -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.
* <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 @@ 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();
}

View File

@@ -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();

View File

@@ -877,7 +877,6 @@ please see styles_device_defaults.xml.
<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">