Commit 7c8e44dd authored by dtrainor@chromium.org's avatar dtrainor@chromium.org

Add per-item animations to the menu

- Animate the normal menu items down into the menu while fading in.
- Animate the icon row to the left while fading in.

BUG=394898
NOTRY=true

Review URL: https://codereview.chromium.org/388803006

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@284781 0039d316-1c4b-4281-b951-d872f2087c98
parent 3c893eca
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2014 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<resources>
<item name="menu_item_enter_anim_id" type="id" />
</resources>
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
package org.chromium.chrome.browser.appmenu; package org.chromium.chrome.browser.appmenu;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Rect; import android.graphics.Rect;
...@@ -147,6 +149,15 @@ public class AppMenu implements OnItemClickListener, OnKeyListener { ...@@ -147,6 +149,15 @@ public class AppMenu implements OnItemClickListener, OnKeyListener {
mPopup.getListView().setVerticalFadingEdgeEnabled(true); mPopup.getListView().setVerticalFadingEdgeEnabled(true);
mPopup.getListView().setFadingEdgeLength(mVerticalFadeDistance); mPopup.getListView().setFadingEdgeLength(mVerticalFadeDistance);
} }
mPopup.getListView().addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
mPopup.getListView().removeOnLayoutChangeListener(this);
runMenuItemEnterAnimations();
}
});
} }
private void setPopupOffset(ListPopupWindow popup, int screenRotation, Rect appRect) { private void setPopupOffset(ListPopupWindow popup, int screenRotation, Rect appRect) {
...@@ -225,7 +236,9 @@ public class AppMenu implements OnItemClickListener, OnKeyListener { ...@@ -225,7 +236,9 @@ public class AppMenu implements OnItemClickListener, OnKeyListener {
*/ */
void dismiss() { void dismiss() {
mHandler.appMenuDismissed(); mHandler.appMenuDismissed();
if (isShowing()) mPopup.dismiss(); if (isShowing()) {
mPopup.dismiss();
}
} }
/** /**
...@@ -277,4 +290,24 @@ public class AppMenu implements OnItemClickListener, OnKeyListener { ...@@ -277,4 +290,24 @@ public class AppMenu implements OnItemClickListener, OnKeyListener {
mPopup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT); mPopup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
} }
} }
private void runMenuItemEnterAnimations() {
AnimatorSet animation = new AnimatorSet();
AnimatorSet.Builder builder = null;
ViewGroup list = mPopup.getListView();
for (int i = 0; i < list.getChildCount(); i++) {
View view = list.getChildAt(i);
Object animatorObject = view.getTag(R.id.menu_item_enter_anim_id);
if (animatorObject != null) {
if (builder == null) {
builder = animation.play((Animator) animatorObject);
} else {
builder.with((Animator) animatorObject);
}
}
}
animation.start();
}
} }
...@@ -4,6 +4,10 @@ ...@@ -4,6 +4,10 @@
package org.chromium.chrome.browser.appmenu; package org.chromium.chrome.browser.appmenu;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MenuItem; import android.view.MenuItem;
...@@ -12,10 +16,12 @@ import android.view.View.OnClickListener; ...@@ -12,10 +16,12 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.BaseAdapter; import android.widget.BaseAdapter;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListView; import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.ui.interpolators.BakedBezierInterpolator;
import java.util.List; import java.util.List;
...@@ -43,11 +49,19 @@ class AppMenuAdapter extends BaseAdapter { ...@@ -43,11 +49,19 @@ class AppMenuAdapter extends BaseAdapter {
*/ */
private static final int FOUR_BUTTON_MENU_ITEM = 3; private static final int FOUR_BUTTON_MENU_ITEM = 3;
/** MenuItem Animation Constants */
private static final int ENTER_ITEM_DURATION_MS = 350;
private static final int ENTER_ITEM_BASE_DELAY_MS = 80;
private static final int ENTER_ITEM_ADDL_DELAY_MS = 30;
private static final float ENTER_STANDARD_ITEM_OFFSET_Y_DP = -10.f;
private static final float ENTER_STANDARD_ITEM_OFFSET_X_DP = 10.f;
private final AppMenu mAppMenu; private final AppMenu mAppMenu;
private final LayoutInflater mInflater; private final LayoutInflater mInflater;
private final List<MenuItem> mMenuItems; private final List<MenuItem> mMenuItems;
private final int mNumMenuItems; private final int mNumMenuItems;
private final boolean mShowMenuButton; private final boolean mShowMenuButton;
private final float mDpToPx;
public AppMenuAdapter(AppMenu appMenu, List<MenuItem> menuItems, LayoutInflater inflater, public AppMenuAdapter(AppMenu appMenu, List<MenuItem> menuItems, LayoutInflater inflater,
boolean showMenuButton) { boolean showMenuButton) {
...@@ -56,6 +70,7 @@ class AppMenuAdapter extends BaseAdapter { ...@@ -56,6 +70,7 @@ class AppMenuAdapter extends BaseAdapter {
mInflater = inflater; mInflater = inflater;
mNumMenuItems = menuItems.size(); mNumMenuItems = menuItems.size();
mShowMenuButton = showMenuButton; mShowMenuButton = showMenuButton;
mDpToPx = inflater.getContext().getResources().getDisplayMetrics().density;
} }
@Override @Override
...@@ -99,6 +114,7 @@ class AppMenuAdapter extends BaseAdapter { ...@@ -99,6 +114,7 @@ class AppMenuAdapter extends BaseAdapter {
@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
final boolean hasMenuButton = mShowMenuButton && position == 0;
final MenuItem item = getItem(position); final MenuItem item = getItem(position);
switch (getItemViewType(position)) { switch (getItemViewType(position)) {
case STANDARD_MENU_ITEM: { case STANDARD_MENU_ITEM: {
...@@ -109,6 +125,8 @@ class AppMenuAdapter extends BaseAdapter { ...@@ -109,6 +125,8 @@ class AppMenuAdapter extends BaseAdapter {
holder.text = (TextView) convertView.findViewById(R.id.menu_item_text); holder.text = (TextView) convertView.findViewById(R.id.menu_item_text);
holder.image = (AppMenuItemIcon) convertView.findViewById(R.id.menu_item_icon); holder.image = (AppMenuItemIcon) convertView.findViewById(R.id.menu_item_icon);
convertView.setTag(holder); convertView.setTag(holder);
convertView.setTag(R.id.menu_item_enter_anim_id,
buildStandardItemEnterAnimator(convertView, position));
} else { } else {
holder = (StandardMenuItemViewHolder) convertView.getTag(); holder = (StandardMenuItemViewHolder) convertView.getTag();
} }
...@@ -138,21 +156,23 @@ class AppMenuAdapter extends BaseAdapter { ...@@ -138,21 +156,23 @@ class AppMenuAdapter extends BaseAdapter {
if (convertView == null) { if (convertView == null) {
holder = new ThreeButtonMenuItemViewHolder(); holder = new ThreeButtonMenuItemViewHolder();
convertView = mInflater.inflate(R.layout.three_button_menu_item, parent, false); convertView = mInflater.inflate(R.layout.three_button_menu_item, parent, false);
holder.buttonOne = (ImageButton) convertView.findViewById(R.id.button_one); holder.buttons[0] = (ImageButton) convertView.findViewById(R.id.button_one);
holder.buttonTwo = (ImageButton) convertView.findViewById(R.id.button_two); holder.buttons[1] = (ImageButton) convertView.findViewById(R.id.button_two);
holder.buttonThree = (ImageButton) convertView.findViewById(R.id.button_three); holder.buttons[2] = (ImageButton) convertView.findViewById(R.id.button_three);
convertView.setTag(holder); convertView.setTag(holder);
convertView.setTag(R.id.menu_item_enter_anim_id,
buildIconItemEnterAnimator(holder.buttons, hasMenuButton));
} else { } else {
holder = (ThreeButtonMenuItemViewHolder) convertView.getTag(); holder = (ThreeButtonMenuItemViewHolder) convertView.getTag();
} }
setupImageButton(holder.buttonOne, item.getSubMenu().getItem(0)); setupImageButton(holder.buttons[0], item.getSubMenu().getItem(0));
setupImageButton(holder.buttonTwo, item.getSubMenu().getItem(1)); setupImageButton(holder.buttons[1], item.getSubMenu().getItem(1));
if (hasMenuButton) {
if (mShowMenuButton && position == 0) { setupMenuButton(holder.buttons[3]);
setupMenuButton(holder.buttonThree);
} else { } else {
setupImageButton(holder.buttonThree, item.getSubMenu().getItem(2)); setupImageButton(holder.buttons[2], item.getSubMenu().getItem(2));
} }
convertView.setFocusable(false); convertView.setFocusable(false);
convertView.setEnabled(false); convertView.setEnabled(false);
break; break;
...@@ -162,22 +182,23 @@ class AppMenuAdapter extends BaseAdapter { ...@@ -162,22 +182,23 @@ class AppMenuAdapter extends BaseAdapter {
if (convertView == null) { if (convertView == null) {
holder = new FourButtonMenuItemViewHolder(); holder = new FourButtonMenuItemViewHolder();
convertView = mInflater.inflate(R.layout.four_button_menu_item, parent, false); convertView = mInflater.inflate(R.layout.four_button_menu_item, parent, false);
holder.buttonOne = (ImageButton) convertView.findViewById(R.id.button_one); holder.buttons[0] = (ImageButton) convertView.findViewById(R.id.button_one);
holder.buttonTwo = (ImageButton) convertView.findViewById(R.id.button_two); holder.buttons[1] = (ImageButton) convertView.findViewById(R.id.button_two);
holder.buttonThree = (ImageButton) convertView.findViewById(R.id.button_three); holder.buttons[2] = (ImageButton) convertView.findViewById(R.id.button_three);
holder.buttonFour = (ImageButton) convertView.findViewById(R.id.button_four); holder.buttons[3] = (ImageButton) convertView.findViewById(R.id.button_four);
convertView.setTag(holder); convertView.setTag(holder);
convertView.setTag(R.id.menu_item_enter_anim_id,
buildIconItemEnterAnimator(holder.buttons, hasMenuButton));
} else { } else {
holder = (FourButtonMenuItemViewHolder) convertView.getTag(); holder = (FourButtonMenuItemViewHolder) convertView.getTag();
} }
setupImageButton(holder.buttonOne, item.getSubMenu().getItem(0)); setupImageButton(holder.buttons[0], item.getSubMenu().getItem(0));
setupImageButton(holder.buttonTwo, item.getSubMenu().getItem(1)); setupImageButton(holder.buttons[1], item.getSubMenu().getItem(1));
setupImageButton(holder.buttonThree, item.getSubMenu().getItem(2)); setupImageButton(holder.buttons[2], item.getSubMenu().getItem(2));
if (hasMenuButton) {
if (mShowMenuButton && position == 0) { setupMenuButton(holder.buttons[3]);
setupMenuButton(holder.buttonFour);
} else { } else {
setupImageButton(holder.buttonFour, item.getSubMenu().getItem(3)); setupImageButton(holder.buttons[3], item.getSubMenu().getItem(3));
} }
convertView.setFocusable(false); convertView.setFocusable(false);
convertView.setEnabled(false); convertView.setEnabled(false);
...@@ -191,6 +212,8 @@ class AppMenuAdapter extends BaseAdapter { ...@@ -191,6 +212,8 @@ class AppMenuAdapter extends BaseAdapter {
holder.title = (TextView) convertView.findViewById(R.id.title); holder.title = (TextView) convertView.findViewById(R.id.title);
holder.button = (ImageButton) convertView.findViewById(R.id.button); holder.button = (ImageButton) convertView.findViewById(R.id.button);
convertView.setTag(holder); convertView.setTag(holder);
convertView.setTag(R.id.menu_item_enter_anim_id,
buildStandardItemEnterAnimator(convertView, position));
} else { } else {
holder = (TitleButtonMenuItemViewHolder) convertView.getTag(); holder = (TitleButtonMenuItemViewHolder) convertView.getTag();
} }
...@@ -205,7 +228,7 @@ class AppMenuAdapter extends BaseAdapter { ...@@ -205,7 +228,7 @@ class AppMenuAdapter extends BaseAdapter {
} }
}); });
if (mShowMenuButton && position == 0) { if (hasMenuButton) {
holder.button.setVisibility(View.VISIBLE); holder.button.setVisibility(View.VISIBLE);
setupMenuButton(holder.button); setupMenuButton(holder.button);
} else if (item.getSubMenu().getItem(1).getIcon() != null) { } else if (item.getSubMenu().getItem(1).getIcon() != null) {
...@@ -254,22 +277,91 @@ class AppMenuAdapter extends BaseAdapter { ...@@ -254,22 +277,91 @@ class AppMenuAdapter extends BaseAdapter {
}); });
} }
/**
* This builds an {@link Animator} for the enter animation of a standard menu item. This means
* it will animate the alpha from 0 to 1 and translate the view from -10dp to 0dp on the y axis.
*
* @param view The menu item {@link View} to be animated.
* @param position The position in the menu. This impacts the start delay of the animation.
* @return The {@link Animator}.
*/
private Animator buildStandardItemEnterAnimator(final View view, int position) {
final float offsetYPx = ENTER_STANDARD_ITEM_OFFSET_Y_DP * mDpToPx;
final int startDelay = ENTER_ITEM_BASE_DELAY_MS + ENTER_ITEM_ADDL_DELAY_MS * position;
AnimatorSet animation = new AnimatorSet();
animation.playTogether(
ObjectAnimator.ofFloat(view, View.ALPHA, 0.f, 1.f),
ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, offsetYPx, 0.f));
animation.setDuration(ENTER_ITEM_DURATION_MS);
animation.setStartDelay(startDelay);
animation.setInterpolator(BakedBezierInterpolator.FADE_IN_CURVE);
animation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
view.setAlpha(0.f);
}
});
return animation;
}
/**
* This builds an {@link Animator} for the enter animation of icon row menu items. This means
* it will animate the alpha from 0 to 1 and translate the views from 10dp to 0dp on the x axis.
*
* @param views The list if icons in the menu item that should be animated.
* @param skipLastItem Whether or not the last item should be animated or not.
* @return The {@link Animator}.
*/
private Animator buildIconItemEnterAnimator(final ImageView[] views, boolean skipLastItem) {
final float offsetXPx = ENTER_STANDARD_ITEM_OFFSET_X_DP * mDpToPx;
final int maxViewsToAnimate = views.length - (skipLastItem ? 1 : 0);
AnimatorSet animation = new AnimatorSet();
AnimatorSet.Builder builder = null;
for (int i = 0; i < maxViewsToAnimate; i++) {
final int startDelay = ENTER_ITEM_ADDL_DELAY_MS * i;
Animator alpha = ObjectAnimator.ofFloat(views[i], View.ALPHA, 0.f, 1.f);
Animator translate = ObjectAnimator.ofFloat(views[i], View.TRANSLATION_X, offsetXPx, 0);
alpha.setStartDelay(startDelay);
translate.setStartDelay(startDelay);
alpha.setDuration(ENTER_ITEM_DURATION_MS);
translate.setDuration(ENTER_ITEM_DURATION_MS);
if (builder == null) {
builder = animation.play(alpha);
} else {
builder.with(alpha);
}
builder.with(translate);
}
animation.setStartDelay(ENTER_ITEM_BASE_DELAY_MS);
animation.setInterpolator(BakedBezierInterpolator.FADE_IN_CURVE);
animation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
for (int i = 0; i < maxViewsToAnimate; i++) {
views[i].setAlpha(0.f);
}
}
});
return animation;
}
static class StandardMenuItemViewHolder { static class StandardMenuItemViewHolder {
public TextView text; public TextView text;
public AppMenuItemIcon image; public AppMenuItemIcon image;
} }
static class ThreeButtonMenuItemViewHolder { static class ThreeButtonMenuItemViewHolder {
public ImageButton buttonOne; public ImageButton[] buttons = new ImageButton[3];
public ImageButton buttonTwo;
public ImageButton buttonThree;
} }
static class FourButtonMenuItemViewHolder { static class FourButtonMenuItemViewHolder {
public ImageButton buttonOne; public ImageButton[] buttons = new ImageButton[4];
public ImageButton buttonTwo;
public ImageButton buttonThree;
public ImageButton buttonFour;
} }
static class TitleButtonMenuItemViewHolder { static class TitleButtonMenuItemViewHolder {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment