summaryrefslogtreecommitdiff
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/widget/ActionMenuPresenter.java260
1 files changed, 247 insertions, 13 deletions
diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java
index 4fadc190f540..710d59eba635 100644
--- a/core/java/android/widget/ActionMenuPresenter.java
+++ b/core/java/android/widget/ActionMenuPresenter.java
@@ -16,6 +16,10 @@
package android.widget;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
@@ -24,6 +28,7 @@ import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.ActionProvider;
import android.view.Gravity;
@@ -32,6 +37,7 @@ import android.view.SoundEffectConstants;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ListPopupWindow.ForwardingListener;
import com.android.internal.transition.ActionBarTransition;
@@ -45,6 +51,7 @@ import com.android.internal.view.menu.MenuView;
import com.android.internal.view.menu.SubMenuBuilder;
import java.util.ArrayList;
+import java.util.List;
/**
* MenuPresenter for building action menus as seen in the action bar and action modes.
@@ -54,6 +61,7 @@ import java.util.ArrayList;
public class ActionMenuPresenter extends BaseMenuPresenter
implements ActionProvider.SubUiVisibilityListener {
private static final String TAG = "ActionMenuPresenter";
+ private static final int ITEM_ANIMATION_DURATION = 150;
private OverflowMenuButton mOverflowButton;
private boolean mReserveOverflow;
@@ -71,8 +79,6 @@ public class ActionMenuPresenter extends BaseMenuPresenter
// Group IDs that have been added as actions - used temporarily, allocated here for reuse.
private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray();
- private View mScrapActionButtonView;
-
private OverflowPopup mOverflowPopup;
private ActionButtonSubmenu mActionButtonPopup;
@@ -84,6 +90,18 @@ public class ActionMenuPresenter extends BaseMenuPresenter
final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback();
int mOpenSubMenuId;
+ // These collections are used to store pre- and post-layout information for menu items,
+ // which is used to determine appropriate animations to run for changed items.
+ private SparseArray<MenuItemLayoutInfo> mPreLayoutItems =
+ new SparseArray<MenuItemLayoutInfo>();
+ private SparseArray<MenuItemLayoutInfo> mPostLayoutItems =
+ new SparseArray<MenuItemLayoutInfo>();
+
+ // The list of currently running animations on menu items.
+ private List<ItemAnimationInfo> mRunningItemAnimations = new ArrayList<ItemAnimationInfo>();
+
+
+
public ActionMenuPresenter(Context context) {
super(context, com.android.internal.R.layout.action_menu_layout,
com.android.internal.R.layout.action_menu_item_layout);
@@ -125,9 +143,6 @@ public class ActionMenuPresenter extends BaseMenuPresenter
mActionItemWidthLimit = width;
mMinCellSize = (int) (ActionMenuView.MIN_CELL_SIZE * res.getDisplayMetrics().density);
-
- // Drop a scrap view as it may no longer reflect the proper context/config.
- mScrapActionButtonView = null;
}
public void onConfigurationChanged(Configuration newConfig) {
@@ -202,10 +217,186 @@ public class ActionMenuPresenter extends BaseMenuPresenter
return item.isActionButton();
}
+ /**
+ * Store layout information about current items in the menu. This is stored for
+ * both pre- and post-layout phases and compared in runItemAnimations() to determine
+ * the animations that need to be run on any item changes.
+ *
+ * @param preLayout Whether this is being called in the pre-layout phase. This is passed
+ * into the MenuItemLayoutInfo structure to store the appropriate position values.
+ */
+ private void computeMenuItemAnimationInfo(boolean preLayout) {
+ final ViewGroup menuViewParent = (ViewGroup) mMenuView;
+ final int count = menuViewParent.getChildCount();
+ SparseArray items = preLayout ? mPreLayoutItems : mPostLayoutItems;
+ for (int i = 0; i < count; ++i) {
+ View child = menuViewParent.getChildAt(i);
+ final int id = child.getId();
+ if (id > 0 && child.getWidth() != 0 && child.getHeight() != 0) {
+ MenuItemLayoutInfo info = new MenuItemLayoutInfo(child, preLayout);
+ items.put(id, info);
+ }
+ }
+ }
+
+ /**
+ * This method is called once both the pre-layout and post-layout steps have
+ * happened. It figures out which views are new (didn't exist prior to layout),
+ * gone (existed pre-layout, but are now gone), or changed (exist in both,
+ * but in a different location) and runs appropriate animations on those views.
+ * Items are tracked by ids, since the underlying views that represent items
+ * pre- and post-layout may be different.
+ */
+ private void runItemAnimations() {
+ for (int i = 0; i < mPreLayoutItems.size(); ++i) {
+ int id = mPreLayoutItems.keyAt(i);
+ final MenuItemLayoutInfo menuItemLayoutInfoPre = mPreLayoutItems.get(id);
+ final int postLayoutIndex = mPostLayoutItems.indexOfKey(id);
+ if (postLayoutIndex >= 0) {
+ // item exists pre and post: see if it's changed
+ final MenuItemLayoutInfo menuItemLayoutInfoPost =
+ mPostLayoutItems.valueAt(postLayoutIndex);
+ PropertyValuesHolder pvhX = null;
+ PropertyValuesHolder pvhY = null;
+ if (menuItemLayoutInfoPre.left != menuItemLayoutInfoPost.left) {
+ pvhX = PropertyValuesHolder.ofFloat(View.TRANSLATION_X,
+ (menuItemLayoutInfoPre.left - menuItemLayoutInfoPost.left), 0);
+ }
+ if (menuItemLayoutInfoPre.top != menuItemLayoutInfoPost.top) {
+ pvhY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y,
+ menuItemLayoutInfoPre.top - menuItemLayoutInfoPost.top, 0);
+ }
+ if (pvhX != null || pvhY != null) {
+ for (int j = 0; j < mRunningItemAnimations.size(); ++j) {
+ ItemAnimationInfo oldInfo = mRunningItemAnimations.get(j);
+ if (oldInfo.id == id && oldInfo.animType == ItemAnimationInfo.MOVE) {
+ oldInfo.animator.cancel();
+ }
+ }
+ ObjectAnimator anim;
+ if (pvhX != null) {
+ if (pvhY != null) {
+ anim = ObjectAnimator.ofPropertyValuesHolder(menuItemLayoutInfoPost.view,
+ pvhX, pvhY);
+ } else {
+ anim = ObjectAnimator.ofPropertyValuesHolder(menuItemLayoutInfoPost.view, pvhX);
+ }
+ } else {
+ anim = ObjectAnimator.ofPropertyValuesHolder(menuItemLayoutInfoPost.view, pvhY);
+ }
+ anim.setDuration(ITEM_ANIMATION_DURATION);
+ anim.start();
+ ItemAnimationInfo info = new ItemAnimationInfo(id, menuItemLayoutInfoPost, anim,
+ ItemAnimationInfo.MOVE);
+ mRunningItemAnimations.add(info);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ for (int j = 0; j < mRunningItemAnimations.size(); ++j) {
+ if (mRunningItemAnimations.get(j).animator == animation) {
+ mRunningItemAnimations.remove(j);
+ break;
+ }
+ }
+ }
+ });
+ }
+ mPostLayoutItems.remove(id);
+ } else {
+ // item used to be there, is now gone
+ float oldAlpha = 1;
+ for (int j = 0; j < mRunningItemAnimations.size(); ++j) {
+ ItemAnimationInfo oldInfo = mRunningItemAnimations.get(j);
+ if (oldInfo.id == id && oldInfo.animType == ItemAnimationInfo.FADE_IN) {
+ oldAlpha = oldInfo.menuItemLayoutInfo.view.getAlpha();
+ oldInfo.animator.cancel();
+ }
+ }
+ ObjectAnimator anim = ObjectAnimator.ofFloat(menuItemLayoutInfoPre.view, View.ALPHA,
+ oldAlpha, 0);
+ // Re-using the view from pre-layout assumes no view recycling
+ ((ViewGroup) mMenuView).getOverlay().add(menuItemLayoutInfoPre.view);
+ anim.setDuration(ITEM_ANIMATION_DURATION);
+ anim.start();
+ ItemAnimationInfo info = new ItemAnimationInfo(id, menuItemLayoutInfoPre, anim, ItemAnimationInfo.FADE_OUT);
+ mRunningItemAnimations.add(info);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ for (int j = 0; j < mRunningItemAnimations.size(); ++j) {
+ if (mRunningItemAnimations.get(j).animator == animation) {
+ mRunningItemAnimations.remove(j);
+ break;
+ }
+ }
+ ((ViewGroup) mMenuView).getOverlay().remove(menuItemLayoutInfoPre.view);
+ }
+ });
+ }
+ }
+ for (int i = 0; i < mPostLayoutItems.size(); ++i) {
+ int id = mPostLayoutItems.keyAt(i);
+ final int postLayoutIndex = mPostLayoutItems.indexOfKey(id);
+ if (postLayoutIndex >= 0) {
+ // item is new
+ final MenuItemLayoutInfo menuItemLayoutInfo =
+ mPostLayoutItems.valueAt(postLayoutIndex);
+ float oldAlpha = 0;
+ for (int j = 0; j < mRunningItemAnimations.size(); ++j) {
+ ItemAnimationInfo oldInfo = mRunningItemAnimations.get(j);
+ if (oldInfo.id == id && oldInfo.animType == ItemAnimationInfo.FADE_OUT) {
+ oldAlpha = oldInfo.menuItemLayoutInfo.view.getAlpha();
+ oldInfo.animator.cancel();
+ }
+ }
+ ObjectAnimator anim = ObjectAnimator.ofFloat(menuItemLayoutInfo.view, View.ALPHA,
+ oldAlpha, 1);
+ anim.start();
+ anim.setDuration(ITEM_ANIMATION_DURATION);
+ ItemAnimationInfo info = new ItemAnimationInfo(id, menuItemLayoutInfo, anim, ItemAnimationInfo.FADE_IN);
+ mRunningItemAnimations.add(info);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ for (int j = 0; j < mRunningItemAnimations.size(); ++j) {
+ if (mRunningItemAnimations.get(j).animator == animation) {
+ mRunningItemAnimations.remove(j);
+ break;
+ }
+ }
+ }
+ });
+ }
+ }
+ mPreLayoutItems.clear();
+ mPostLayoutItems.clear();
+ }
+
+ /**
+ * Gets position/existence information on menu items before and after layout,
+ * which is then fed into runItemAnimations()
+ */
+ private void setupItemAnimations() {
+ final ViewGroup menuViewParent = (ViewGroup) mMenuView;
+ computeMenuItemAnimationInfo(true);
+ final ViewTreeObserver observer = menuViewParent.getViewTreeObserver();
+ if (observer != null) {
+ observer.addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
+ @Override
+ public void onDraw() {
+ computeMenuItemAnimationInfo(false);
+ observer.removeOnDrawListener(this);
+ runItemAnimations();
+ }
+ });
+ }
+ }
+
@Override
public void updateMenuView(boolean cleared) {
final ViewGroup menuViewParent = (ViewGroup) ((View) mMenuView).getParent();
if (menuViewParent != null) {
+ setupItemAnimations();
ActionBarTransition.beginDelayedTransition(menuViewParent);
}
super.updateMenuView(cleared);
@@ -431,10 +622,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter
MenuItemImpl item = visibleItems.get(i);
if (item.requiresActionButton()) {
- View v = getItemView(item, mScrapActionButtonView, parent);
- if (mScrapActionButtonView == null) {
- mScrapActionButtonView = v;
- }
+ View v = getItemView(item, null, parent);
if (mStrictWidthLimit) {
cellsRemaining -= ActionMenuView.measureChildForCells(v,
cellSize, cellsRemaining, querySpec, 0);
@@ -460,10 +648,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter
(!mStrictWidthLimit || cellsRemaining > 0);
if (isAction) {
- View v = getItemView(item, mScrapActionButtonView, parent);
- if (mScrapActionButtonView == null) {
- mScrapActionButtonView = v;
- }
+ View v = getItemView(item, null, parent);
if (mStrictWidthLimit) {
final int cells = ActionMenuView.measureChildForCells(v,
cellSize, cellsRemaining, querySpec, 0);
@@ -819,4 +1004,53 @@ public class ActionMenuPresenter extends BaseMenuPresenter
boolean mHasTintMode;
boolean mHasTintList;
}
+
+ /**
+ * This class holds layout information for a menu item. This is used to determine
+ * pre- and post-layout information about menu items, which will then be used to
+ * determine appropriate item animations.
+ */
+ private static class MenuItemLayoutInfo {
+ View view;
+ int left;
+ int top;
+
+ MenuItemLayoutInfo(View view, boolean preLayout) {
+ left = view.getLeft();
+ top = view.getTop();
+ if (preLayout) {
+ // We track translation for pre-layout because a view might be mid-animation
+ // and we need this information to know where to animate from
+ left += view.getTranslationX();
+ top += view.getTranslationY();
+ }
+ this.view = view;
+ }
+ }
+
+ /**
+ * This class is used to store information about currently-running item animations.
+ * This is used when new animations are scheduled to determine whether any existing
+ * animations need to be canceled, based on whether the running animations overlap
+ * with any new animations. For example, if an item is currently animating from
+ * location A to B and another change dictates that it be animated to C, then the current
+ * A-B animation will be canceled and a new animation to C will be started.
+ */
+ private static class ItemAnimationInfo {
+ int id;
+ MenuItemLayoutInfo menuItemLayoutInfo;
+ Animator animator;
+ int animType;
+ static final int MOVE = 0;
+ static final int FADE_IN = 1;
+ static final int FADE_OUT = 2;
+
+ ItemAnimationInfo(int id, MenuItemLayoutInfo info, Animator anim, int animType) {
+ this.id = id;
+ menuItemLayoutInfo = info;
+ animator = anim;
+ this.animType = animType;
+ }
+ }
+
}