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.ImageButton;
19 import android.widget.ImageView;
20 import android.widget.ImageView.ScaleType;
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.ui.base.LocalizationUtils;
27 import org.chromium.ui.interpolators.BakedBezierInterpolator;
29 import java.util.List;
32 * ListAdapter to customize the view of items in the list.
34 class AppMenuAdapter extends BaseAdapter {
35 private static final int VIEW_TYPE_COUNT = 5;
38 * Regular Android menu item that contains a title and an icon if icon is specified.
40 private static final int STANDARD_MENU_ITEM = 0;
42 * Menu item that has two buttons, the first one is a title and the second one is an icon.
43 * It is different from the regular menu item because it contains two separate buttons.
45 private static final int TITLE_BUTTON_MENU_ITEM = 1;
47 * Menu item that has three buttons. Every one of these buttons is displayed as an icon.
49 private static final int THREE_BUTTON_MENU_ITEM = 2;
51 * Menu item that has four buttons. Every one of these buttons is displayed as an icon.
53 private static final int FOUR_BUTTON_MENU_ITEM = 3;
55 * Menu item that has two buttons, the first one is a title and the second is a menu icon.
56 * This is similar to {@link #TITLE_BUTTON_MENU_ITEM} but has some slight layout differences.
58 private static final int MENU_BUTTON_MENU_ITEM = 4;
60 /** MenuItem Animation Constants */
61 private static final int ENTER_ITEM_DURATION_MS = 350;
62 private static final int ENTER_ITEM_BASE_DELAY_MS = 80;
63 private static final int ENTER_ITEM_ADDL_DELAY_MS = 30;
64 private static final float ENTER_STANDARD_ITEM_OFFSET_Y_DP = -10.f;
65 private static final float ENTER_STANDARD_ITEM_OFFSET_X_DP = 10.f;
67 /** Menu Button Layout Constants */
68 private static final float MENU_BUTTON_WIDTH_DP = 59.f;
69 private static final float MENU_BUTTON_START_PADDING_DP = 21.f;
71 private final AppMenu mAppMenu;
72 private final LayoutInflater mInflater;
73 private final List<MenuItem> mMenuItems;
74 private final int mNumMenuItems;
75 private final boolean mShowMenuButton;
76 private final float mDpToPx;
78 public AppMenuAdapter(AppMenu appMenu, List<MenuItem> menuItems, LayoutInflater inflater,
79 boolean showMenuButton) {
81 mMenuItems = menuItems;
83 mNumMenuItems = menuItems.size();
84 mShowMenuButton = showMenuButton;
85 mDpToPx = inflater.getContext().getResources().getDisplayMetrics().density;
89 public int getCount() {
94 public int getViewTypeCount() {
95 return VIEW_TYPE_COUNT;
99 public int getItemViewType(int position) {
100 MenuItem item = getItem(position);
101 boolean hasMenuButton = mShowMenuButton && position == 0;
102 int viewCount = item.hasSubMenu() ? item.getSubMenu().size() : 1;
103 if (hasMenuButton) viewCount++;
105 if (viewCount == 4) {
106 return FOUR_BUTTON_MENU_ITEM;
107 } else if (viewCount == 3) {
108 return THREE_BUTTON_MENU_ITEM;
109 } else if (viewCount == 2) {
110 return hasMenuButton ? MENU_BUTTON_MENU_ITEM : TITLE_BUTTON_MENU_ITEM;
112 return STANDARD_MENU_ITEM;
116 public long getItemId(int position) {
117 return getItem(position).getItemId();
121 public MenuItem getItem(int position) {
122 if (position == ListView.INVALID_POSITION) return null;
123 assert position >= 0;
124 assert position < mMenuItems.size();
125 return mMenuItems.get(position);
129 public View getView(int position, View convertView, ViewGroup parent) {
130 final boolean hasMenuButton = mShowMenuButton && position == 0;
131 final MenuItem item = getItem(position);
132 switch (getItemViewType(position)) {
133 case STANDARD_MENU_ITEM: {
134 StandardMenuItemViewHolder holder = null;
135 if (convertView == null) {
136 holder = new StandardMenuItemViewHolder();
137 convertView = mInflater.inflate(R.layout.menu_item, parent, false);
138 holder.text = (TextView) convertView.findViewById(R.id.menu_item_text);
139 holder.image = (AppMenuItemIcon) convertView.findViewById(R.id.menu_item_icon);
140 convertView.setTag(holder);
141 convertView.setTag(R.id.menu_item_enter_anim_id,
142 buildStandardItemEnterAnimator(convertView, position));
144 holder = (StandardMenuItemViewHolder) convertView.getTag();
147 convertView.setOnClickListener(new OnClickListener() {
149 public void onClick(View v) {
150 mAppMenu.onItemClick(item);
154 Drawable icon = item.getIcon();
155 holder.image.setImageDrawable(icon);
156 holder.image.setVisibility(icon == null ? View.GONE : View.VISIBLE);
157 holder.image.setChecked(item.isChecked());
159 holder.text.setText(item.getTitle());
160 boolean isEnabled = item.isEnabled();
161 // Set the text color (using a color state list).
162 holder.text.setEnabled(isEnabled);
163 // This will ensure that the item is not highlighted when selected.
164 convertView.setEnabled(isEnabled);
167 case THREE_BUTTON_MENU_ITEM: {
168 ThreeButtonMenuItemViewHolder holder = null;
169 if (convertView == null) {
170 holder = new ThreeButtonMenuItemViewHolder();
171 convertView = mInflater.inflate(R.layout.three_button_menu_item, parent, false);
172 holder.buttons[0] = (ImageButton) convertView.findViewById(R.id.button_one);
173 holder.buttons[1] = (ImageButton) convertView.findViewById(R.id.button_two);
174 holder.buttons[2] = (ImageButton) convertView.findViewById(R.id.button_three);
175 convertView.setTag(holder);
176 convertView.setTag(R.id.menu_item_enter_anim_id,
177 buildIconItemEnterAnimator(holder.buttons, hasMenuButton));
179 holder = (ThreeButtonMenuItemViewHolder) convertView.getTag();
181 setupImageButton(holder.buttons[0], item.getSubMenu().getItem(0));
182 setupImageButton(holder.buttons[1], item.getSubMenu().getItem(1));
184 setupMenuButton(holder.buttons[3]);
186 setupImageButton(holder.buttons[2], item.getSubMenu().getItem(2));
189 convertView.setFocusable(false);
190 convertView.setEnabled(false);
193 case FOUR_BUTTON_MENU_ITEM: {
194 FourButtonMenuItemViewHolder holder = null;
195 if (convertView == null) {
196 holder = new FourButtonMenuItemViewHolder();
197 convertView = mInflater.inflate(R.layout.four_button_menu_item, parent, false);
198 holder.buttons[0] = (ImageButton) convertView.findViewById(R.id.button_one);
199 holder.buttons[1] = (ImageButton) convertView.findViewById(R.id.button_two);
200 holder.buttons[2] = (ImageButton) convertView.findViewById(R.id.button_three);
201 holder.buttons[3] = (ImageButton) convertView.findViewById(R.id.button_four);
202 convertView.setTag(holder);
203 convertView.setTag(R.id.menu_item_enter_anim_id,
204 buildIconItemEnterAnimator(holder.buttons, hasMenuButton));
206 holder = (FourButtonMenuItemViewHolder) convertView.getTag();
208 setupImageButton(holder.buttons[0], item.getSubMenu().getItem(0));
209 setupImageButton(holder.buttons[1], item.getSubMenu().getItem(1));
210 setupImageButton(holder.buttons[2], item.getSubMenu().getItem(2));
212 setupMenuButton(holder.buttons[3]);
214 setupImageButton(holder.buttons[3], item.getSubMenu().getItem(3));
216 convertView.setFocusable(false);
217 convertView.setEnabled(false);
220 case TITLE_BUTTON_MENU_ITEM:
222 case MENU_BUTTON_MENU_ITEM: {
223 TitleButtonMenuItemViewHolder holder = null;
224 if (convertView == null) {
225 holder = new TitleButtonMenuItemViewHolder();
226 convertView = mInflater.inflate(R.layout.title_button_menu_item, parent, false);
227 holder.title = (TextView) convertView.findViewById(R.id.title);
228 holder.button = (ImageButton) convertView.findViewById(R.id.button);
230 View animatedView = hasMenuButton ? holder.title : convertView;
232 convertView.setTag(holder);
233 convertView.setTag(R.id.menu_item_enter_anim_id,
234 buildStandardItemEnterAnimator(animatedView, position));
236 holder = (TitleButtonMenuItemViewHolder) convertView.getTag();
238 final MenuItem titleItem = item.hasSubMenu() ? item.getSubMenu().getItem(0) : item;
239 holder.title.setText(titleItem.getTitle());
240 holder.title.setEnabled(titleItem.isEnabled());
241 holder.title.setFocusable(titleItem.isEnabled());
242 holder.title.setOnClickListener(new OnClickListener() {
244 public void onClick(View v) {
245 mAppMenu.onItemClick(titleItem);
250 holder.button.setVisibility(View.VISIBLE);
251 setupMenuButton(holder.button);
252 } else if (item.getSubMenu().getItem(1).getIcon() != null) {
253 holder.button.setVisibility(View.VISIBLE);
254 setupImageButton(holder.button, item.getSubMenu().getItem(1));
256 holder.button.setVisibility(View.GONE);
258 convertView.setFocusable(false);
259 convertView.setEnabled(false);
263 assert false : "Unexpected MenuItem type";
268 private void setupImageButton(ImageButton button, final MenuItem item) {
269 // Store and recover the level of image as button.setimageDrawable
270 // resets drawable to default level.
271 int currentLevel = item.getIcon().getLevel();
272 button.setImageDrawable(item.getIcon());
273 item.getIcon().setLevel(currentLevel);
274 button.setContentDescription(item.getTitle());
275 button.setEnabled(item.isEnabled());
276 button.setFocusable(item.isEnabled());
277 button.setOnClickListener(new OnClickListener() {
279 public void onClick(View v) {
280 mAppMenu.onItemClick(item);
285 private void setupMenuButton(ImageButton button) {
286 button.setImageResource(R.drawable.btn_menu_pressed);
287 button.setContentDescription(button.getResources().getString(R.string.menu_dismiss_btn));
288 button.setEnabled(true);
289 button.setFocusable(true);
290 button.setOnClickListener(new OnClickListener() {
292 public void onClick(View v) {
297 // Set the button layout to make it properly line up with any underlying menu button
298 ApiCompatibilityUtils.setPaddingRelative(
299 button, (int) (MENU_BUTTON_START_PADDING_DP * mDpToPx), 0, 0, 0);
300 button.getLayoutParams().width = (int) (MENU_BUTTON_WIDTH_DP * mDpToPx);
301 button.setScaleType(ScaleType.CENTER);
305 * This builds an {@link Animator} for the enter animation of a standard menu item. This means
306 * it will animate the alpha from 0 to 1 and translate the view from -10dp to 0dp on the y axis.
308 * @param view The menu item {@link View} to be animated.
309 * @param position The position in the menu. This impacts the start delay of the animation.
310 * @return The {@link Animator}.
312 private Animator buildStandardItemEnterAnimator(final View view, int position) {
313 final float offsetYPx = ENTER_STANDARD_ITEM_OFFSET_Y_DP * mDpToPx;
314 final int startDelay = ENTER_ITEM_BASE_DELAY_MS + ENTER_ITEM_ADDL_DELAY_MS * position;
316 AnimatorSet animation = new AnimatorSet();
317 animation.playTogether(
318 ObjectAnimator.ofFloat(view, View.ALPHA, 0.f, 1.f),
319 ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, offsetYPx, 0.f));
320 animation.setDuration(ENTER_ITEM_DURATION_MS);
321 animation.setStartDelay(startDelay);
322 animation.setInterpolator(BakedBezierInterpolator.FADE_IN_CURVE);
324 animation.addListener(new AnimatorListenerAdapter() {
326 public void onAnimationStart(Animator animation) {
334 * This builds an {@link Animator} for the enter animation of icon row menu items. This means
335 * it will animate the alpha from 0 to 1 and translate the views from 10dp to 0dp on the x axis.
337 * @param views The list if icons in the menu item that should be animated.
338 * @param skipLastItem Whether or not the last item should be animated or not.
339 * @return The {@link Animator}.
341 private Animator buildIconItemEnterAnimator(final ImageView[] views, boolean skipLastItem) {
342 final boolean rtl = LocalizationUtils.isLayoutRtl();
343 final float offsetXPx = ENTER_STANDARD_ITEM_OFFSET_X_DP * mDpToPx * (rtl ? -1.f : 1.f);
344 final int maxViewsToAnimate = views.length - (skipLastItem ? 1 : 0);
346 AnimatorSet animation = new AnimatorSet();
347 AnimatorSet.Builder builder = null;
348 for (int i = 0; i < maxViewsToAnimate; i++) {
349 final int startDelay = ENTER_ITEM_ADDL_DELAY_MS * i;
351 Animator alpha = ObjectAnimator.ofFloat(views[i], View.ALPHA, 0.f, 1.f);
352 Animator translate = ObjectAnimator.ofFloat(views[i], View.TRANSLATION_X, offsetXPx, 0);
353 alpha.setStartDelay(startDelay);
354 translate.setStartDelay(startDelay);
355 alpha.setDuration(ENTER_ITEM_DURATION_MS);
356 translate.setDuration(ENTER_ITEM_DURATION_MS);
358 if (builder == null) {
359 builder = animation.play(alpha);
363 builder.with(translate);
365 animation.setStartDelay(ENTER_ITEM_BASE_DELAY_MS);
366 animation.setInterpolator(BakedBezierInterpolator.FADE_IN_CURVE);
368 animation.addListener(new AnimatorListenerAdapter() {
370 public void onAnimationStart(Animator animation) {
371 for (int i = 0; i < maxViewsToAnimate; i++) {
372 views[i].setAlpha(0.f);
379 static class StandardMenuItemViewHolder {
380 public TextView text;
381 public AppMenuItemIcon image;
384 static class ThreeButtonMenuItemViewHolder {
385 public ImageButton[] buttons = new ImageButton[3];
388 static class FourButtonMenuItemViewHolder {
389 public ImageButton[] buttons = new ImageButton[4];
392 static class TitleButtonMenuItemViewHolder {
393 public TextView title;
394 public ImageButton button;