1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 package org.chromium.chrome.browser.appmenu;
7 import android.animation.Animator;
8 import android.animation.AnimatorListenerAdapter;
9 import android.animation.AnimatorSet;
10 import android.animation.ObjectAnimator;
11 import android.graphics.drawable.Drawable;
12 import android.view.LayoutInflater;
13 import android.view.MenuItem;
14 import android.view.View;
15 import android.view.View.OnClickListener;
16 import android.view.ViewGroup;
17 import android.widget.BaseAdapter;
18 import android.widget.ImageView;
19 import android.widget.ImageView.ScaleType;
20 import android.widget.LinearLayout;
21 import android.widget.ListView;
22 import android.widget.TextView;
24 import org.chromium.base.ApiCompatibilityUtils;
25 import org.chromium.chrome.R;
26 import org.chromium.chrome.browser.widget.TintedImageButton;
27 import org.chromium.ui.base.LocalizationUtils;
28 import org.chromium.ui.interpolators.BakedBezierInterpolator;
30 import java.util.List;
33 * ListAdapter to customize the view of items in the list.
35 class AppMenuAdapter extends BaseAdapter {
37 * Regular Android menu item that contains a title and an icon if icon is specified.
39 private static final int STANDARD_MENU_ITEM = 0;
41 * Menu item that has two buttons, the first one is a title and the second one is an icon.
42 * It is different from the regular menu item because it contains two separate buttons.
44 private static final int TITLE_BUTTON_MENU_ITEM = 1;
46 * Menu item that has two buttons. Every one of these buttons is displayed as an icon.
48 private static final int TWO_BUTTON_MENU_ITEM = 2;
50 * Menu item that has three buttons. Every one of these buttons is displayed as an icon.
52 private static final int THREE_BUTTON_MENU_ITEM = 3;
54 * Menu item that has four buttons. Every one of these buttons is displayed as an icon.
56 private static final int FOUR_BUTTON_MENU_ITEM = 4;
58 * Menu item that has two buttons, the first one is a title and the second is a menu icon.
59 * This is similar to {@link #TITLE_BUTTON_MENU_ITEM} but has some slight layout differences.
61 private static final int MENU_BUTTON_MENU_ITEM = 5;
64 * The number of view types specified above. If you add a view type you MUST increment this.
66 private static final int VIEW_TYPE_COUNT = 6;
68 /** MenuItem Animation Constants */
69 private static final int ENTER_ITEM_DURATION_MS = 350;
70 private static final int ENTER_ITEM_BASE_DELAY_MS = 80;
71 private static final int ENTER_ITEM_ADDL_DELAY_MS = 30;
72 private static final float ENTER_STANDARD_ITEM_OFFSET_Y_DP = -10.f;
73 private static final float ENTER_STANDARD_ITEM_OFFSET_X_DP = 10.f;
75 /** Menu Button Layout Constants */
76 private static final float MENU_BUTTON_WIDTH_DP = 59.f;
78 private final AppMenu mAppMenu;
79 private final LayoutInflater mInflater;
80 private final List<MenuItem> mMenuItems;
81 private final int mNumMenuItems;
82 private final boolean mShowMenuButton;
83 private final int mMenuButtonStartPaddingPx;
84 private final float mDpToPx;
86 public AppMenuAdapter(AppMenu appMenu, List<MenuItem> menuItems, LayoutInflater inflater,
87 boolean showMenuButton, int menuButtonStartPaddingPx) {
89 mMenuItems = menuItems;
91 mNumMenuItems = menuItems.size();
92 mShowMenuButton = showMenuButton;
93 mDpToPx = inflater.getContext().getResources().getDisplayMetrics().density;
94 mMenuButtonStartPaddingPx = menuButtonStartPaddingPx;
98 public int getCount() {
103 public int getViewTypeCount() {
104 return VIEW_TYPE_COUNT;
108 public int getItemViewType(int position) {
109 MenuItem item = getItem(position);
110 boolean hasMenuButton = mShowMenuButton && position == 0;
111 int viewCount = item.hasSubMenu() ? item.getSubMenu().size() : 1;
112 if (hasMenuButton) viewCount++;
114 if (viewCount == 4) {
115 return FOUR_BUTTON_MENU_ITEM;
116 } else if (viewCount == 3) {
117 return THREE_BUTTON_MENU_ITEM;
118 } else if (viewCount == 2) {
120 && ((!mShowMenuButton && item.getSubMenu().getItem(0).getIcon() != null)
121 || (hasMenuButton && item.getIcon() != null))) {
122 return TWO_BUTTON_MENU_ITEM;
124 return hasMenuButton ? MENU_BUTTON_MENU_ITEM : TITLE_BUTTON_MENU_ITEM;
126 return STANDARD_MENU_ITEM;
130 public long getItemId(int position) {
131 return getItem(position).getItemId();
135 public MenuItem getItem(int position) {
136 if (position == ListView.INVALID_POSITION) return null;
137 assert position >= 0;
138 assert position < mMenuItems.size();
139 return mMenuItems.get(position);
143 public View getView(int position, View convertView, ViewGroup parent) {
144 final boolean hasMenuButton = mShowMenuButton && position == 0;
145 final MenuItem item = getItem(position);
146 switch (getItemViewType(position)) {
147 case STANDARD_MENU_ITEM: {
148 StandardMenuItemViewHolder holder = null;
149 if (convertView == null) {
150 holder = new StandardMenuItemViewHolder();
151 convertView = mInflater.inflate(R.layout.menu_item, parent, false);
152 holder.text = (TextView) convertView.findViewById(R.id.menu_item_text);
153 holder.image = (AppMenuItemIcon) convertView.findViewById(R.id.menu_item_icon);
154 convertView.setTag(holder);
155 convertView.setTag(R.id.menu_item_enter_anim_id,
156 buildStandardItemEnterAnimator(convertView, position));
158 holder = (StandardMenuItemViewHolder) convertView.getTag();
161 convertView.setOnClickListener(new OnClickListener() {
163 public void onClick(View v) {
164 mAppMenu.onItemClick(item);
168 Drawable icon = item.getIcon();
169 holder.image.setImageDrawable(icon);
170 holder.image.setVisibility(icon == null ? View.GONE : View.VISIBLE);
171 holder.image.setChecked(item.isChecked());
172 holder.text.setText(item.getTitle());
173 holder.text.setContentDescription(item.getTitleCondensed());
175 boolean isEnabled = item.isEnabled();
176 // Set the text color (using a color state list).
177 holder.text.setEnabled(isEnabled);
178 // This will ensure that the item is not highlighted when selected.
179 convertView.setEnabled(isEnabled);
182 case TWO_BUTTON_MENU_ITEM: {
183 TwoButtonMenuItemViewHolder holder = null;
184 if (convertView == null) {
185 holder = new TwoButtonMenuItemViewHolder();
186 convertView = mInflater.inflate(R.layout.two_button_menu_item, parent, false);
188 (TintedImageButton) convertView.findViewById(R.id.button_one);
190 (TintedImageButton) convertView.findViewById(R.id.button_two);
191 convertView.setTag(holder);
192 convertView.setTag(R.id.menu_item_enter_anim_id,
193 buildIconItemEnterAnimator(holder.buttons, hasMenuButton));
195 holder = (TwoButtonMenuItemViewHolder) convertView.getTag();
197 setupImageButton(holder.buttons[0], item.getSubMenu().getItem(0));
199 setupMenuButton(holder.buttons[1]);
201 setupImageButton(holder.buttons[1], item.getSubMenu().getItem(1));
204 convertView.setFocusable(false);
205 convertView.setEnabled(false);
208 case THREE_BUTTON_MENU_ITEM: {
209 ThreeButtonMenuItemViewHolder holder = null;
210 if (convertView == null) {
211 holder = new ThreeButtonMenuItemViewHolder();
212 convertView = mInflater.inflate(R.layout.three_button_menu_item, parent, false);
214 (TintedImageButton) convertView.findViewById(R.id.button_one);
216 (TintedImageButton) convertView.findViewById(R.id.button_two);
218 (TintedImageButton) convertView.findViewById(R.id.button_three);
219 convertView.setTag(holder);
220 convertView.setTag(R.id.menu_item_enter_anim_id,
221 buildIconItemEnterAnimator(holder.buttons, hasMenuButton));
223 holder = (ThreeButtonMenuItemViewHolder) convertView.getTag();
225 setupImageButton(holder.buttons[0], item.getSubMenu().getItem(0));
226 setupImageButton(holder.buttons[1], item.getSubMenu().getItem(1));
228 setupMenuButton(holder.buttons[2]);
230 setupImageButton(holder.buttons[2], item.getSubMenu().getItem(2));
233 convertView.setFocusable(false);
234 convertView.setEnabled(false);
237 case FOUR_BUTTON_MENU_ITEM: {
238 FourButtonMenuItemViewHolder holder = null;
239 if (convertView == null) {
240 holder = new FourButtonMenuItemViewHolder();
241 convertView = mInflater.inflate(R.layout.four_button_menu_item, parent, false);
243 (TintedImageButton) convertView.findViewById(R.id.button_one);
245 (TintedImageButton) convertView.findViewById(R.id.button_two);
247 (TintedImageButton) convertView.findViewById(R.id.button_three);
249 (TintedImageButton) convertView.findViewById(R.id.button_four);
250 convertView.setTag(holder);
251 convertView.setTag(R.id.menu_item_enter_anim_id,
252 buildIconItemEnterAnimator(holder.buttons, hasMenuButton));
254 holder = (FourButtonMenuItemViewHolder) convertView.getTag();
256 setupImageButton(holder.buttons[0], item.getSubMenu().getItem(0));
257 setupImageButton(holder.buttons[1], item.getSubMenu().getItem(1));
258 setupImageButton(holder.buttons[2], item.getSubMenu().getItem(2));
260 setupMenuButton(holder.buttons[3]);
262 setupImageButton(holder.buttons[3], item.getSubMenu().getItem(3));
264 convertView.setFocusable(false);
265 convertView.setEnabled(false);
268 case TITLE_BUTTON_MENU_ITEM:
270 case MENU_BUTTON_MENU_ITEM: {
271 TitleButtonMenuItemViewHolder holder = null;
272 if (convertView == null) {
273 holder = new TitleButtonMenuItemViewHolder();
274 convertView = mInflater.inflate(R.layout.title_button_menu_item, parent, false);
275 holder.title = (TextView) convertView.findViewById(R.id.title);
276 holder.button = (TintedImageButton) convertView.findViewById(R.id.button);
278 View animatedView = hasMenuButton ? holder.title : convertView;
280 convertView.setTag(holder);
281 convertView.setTag(R.id.menu_item_enter_anim_id,
282 buildStandardItemEnterAnimator(animatedView, position));
284 holder = (TitleButtonMenuItemViewHolder) convertView.getTag();
286 final MenuItem titleItem = item.hasSubMenu() ? item.getSubMenu().getItem(0) : item;
287 holder.title.setText(titleItem.getTitle());
288 holder.title.setEnabled(titleItem.isEnabled());
289 holder.title.setFocusable(titleItem.isEnabled());
290 holder.title.setOnClickListener(new OnClickListener() {
292 public void onClick(View v) {
293 mAppMenu.onItemClick(titleItem);
298 holder.button.setVisibility(View.VISIBLE);
299 setupMenuButton(holder.button);
300 } else if (item.getSubMenu().getItem(1).getIcon() != null) {
301 holder.button.setVisibility(View.VISIBLE);
302 setupImageButton(holder.button, item.getSubMenu().getItem(1));
304 holder.button.setVisibility(View.GONE);
306 convertView.setFocusable(false);
307 convertView.setEnabled(false);
311 assert false : "Unexpected MenuItem type";
316 private void setupImageButton(TintedImageButton button, final MenuItem item) {
317 // Store and recover the level of image as button.setimageDrawable
318 // resets drawable to default level.
319 int currentLevel = item.getIcon().getLevel();
320 button.setImageDrawable(item.getIcon());
321 item.getIcon().setLevel(currentLevel);
322 if (item.isChecked()) {
323 button.setTint(button.getResources().getColorStateList(R.color.blue_mode_tint));
325 button.setEnabled(item.isEnabled());
326 button.setFocusable(item.isEnabled());
327 button.setContentDescription(item.getTitleCondensed());
329 button.setOnClickListener(new OnClickListener() {
331 public void onClick(View v) {
332 mAppMenu.onItemClick(item);
337 private void setupMenuButton(TintedImageButton button) {
338 button.setImageResource(R.drawable.btn_menu);
339 button.setTint(button.getResources().getColorStateList(R.color.blue_mode_tint));
340 button.setContentDescription(button.getResources().getString(R.string.menu_dismiss_btn));
341 button.setEnabled(true);
342 button.setFocusable(true);
343 button.setOnClickListener(new OnClickListener() {
345 public void onClick(View v) {
350 // Set the button layout to make it properly line up with any underlying menu button
351 ApiCompatibilityUtils.setPaddingRelative(
352 button, mMenuButtonStartPaddingPx, 0, 0, 0);
353 button.getLayoutParams().width = (int) (MENU_BUTTON_WIDTH_DP * mDpToPx);
354 ((LinearLayout.LayoutParams) button.getLayoutParams()).weight = 0;
355 button.setScaleType(ScaleType.CENTER);
359 * This builds an {@link Animator} for the enter animation of a standard menu item. This means
360 * it will animate the alpha from 0 to 1 and translate the view from -10dp to 0dp on the y axis.
362 * @param view The menu item {@link View} to be animated.
363 * @param position The position in the menu. This impacts the start delay of the animation.
364 * @return The {@link Animator}.
366 private Animator buildStandardItemEnterAnimator(final View view, int position) {
367 final float offsetYPx = ENTER_STANDARD_ITEM_OFFSET_Y_DP * mDpToPx;
368 final int startDelay = ENTER_ITEM_BASE_DELAY_MS + ENTER_ITEM_ADDL_DELAY_MS * position;
370 AnimatorSet animation = new AnimatorSet();
371 animation.playTogether(
372 ObjectAnimator.ofFloat(view, View.ALPHA, 0.f, 1.f),
373 ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, offsetYPx, 0.f));
374 animation.setDuration(ENTER_ITEM_DURATION_MS);
375 animation.setStartDelay(startDelay);
376 animation.setInterpolator(BakedBezierInterpolator.FADE_IN_CURVE);
378 animation.addListener(new AnimatorListenerAdapter() {
380 public void onAnimationStart(Animator animation) {
388 * This builds an {@link Animator} for the enter animation of icon row menu items. This means
389 * it will animate the alpha from 0 to 1 and translate the views from 10dp to 0dp on the x axis.
391 * @param views The list if icons in the menu item that should be animated.
392 * @param skipLastItem Whether or not the last item should be animated or not.
393 * @return The {@link Animator}.
395 private Animator buildIconItemEnterAnimator(final ImageView[] views, boolean skipLastItem) {
396 final boolean rtl = LocalizationUtils.isLayoutRtl();
397 final float offsetXPx = ENTER_STANDARD_ITEM_OFFSET_X_DP * mDpToPx * (rtl ? -1.f : 1.f);
398 final int maxViewsToAnimate = views.length - (skipLastItem ? 1 : 0);
400 AnimatorSet animation = new AnimatorSet();
401 AnimatorSet.Builder builder = null;
402 for (int i = 0; i < maxViewsToAnimate; i++) {
403 final int startDelay = ENTER_ITEM_ADDL_DELAY_MS * i;
405 Animator alpha = ObjectAnimator.ofFloat(views[i], View.ALPHA, 0.f, 1.f);
406 Animator translate = ObjectAnimator.ofFloat(views[i], View.TRANSLATION_X, offsetXPx, 0);
407 alpha.setStartDelay(startDelay);
408 translate.setStartDelay(startDelay);
409 alpha.setDuration(ENTER_ITEM_DURATION_MS);
410 translate.setDuration(ENTER_ITEM_DURATION_MS);
412 if (builder == null) {
413 builder = animation.play(alpha);
417 builder.with(translate);
419 animation.setStartDelay(ENTER_ITEM_BASE_DELAY_MS);
420 animation.setInterpolator(BakedBezierInterpolator.FADE_IN_CURVE);
422 animation.addListener(new AnimatorListenerAdapter() {
424 public void onAnimationStart(Animator animation) {
425 for (int i = 0; i < maxViewsToAnimate; i++) {
426 views[i].setAlpha(0.f);
433 static class StandardMenuItemViewHolder {
434 public TextView text;
435 public AppMenuItemIcon image;
438 static class TwoButtonMenuItemViewHolder {
439 public TintedImageButton[] buttons = new TintedImageButton[2];
442 static class ThreeButtonMenuItemViewHolder {
443 public TintedImageButton[] buttons = new TintedImageButton[3];
446 static class FourButtonMenuItemViewHolder {
447 public TintedImageButton[] buttons = new TintedImageButton[4];
450 static class TitleButtonMenuItemViewHolder {
451 public TextView title;
452 public TintedImageButton button;