blob: 0b1e0e5bcf6d8d860281870ab18af76b06bbf8c8 [file] [log] [blame]
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +00001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.widget;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.AnimatorSet;
22import android.animation.ObjectAnimator;
23import android.content.Context;
24import android.graphics.Color;
25import android.graphics.Point;
26import android.graphics.Rect;
Abodunrinwa Toki517adad2015-04-07 22:13:51 +010027import android.graphics.Region;
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +000028import android.graphics.drawable.ColorDrawable;
Abodunrinwa Toki517adad2015-04-07 22:13:51 +010029import android.util.Size;
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +000030import android.view.Gravity;
31import android.view.LayoutInflater;
32import android.view.Menu;
33import android.view.MenuItem;
34import android.view.View;
35import android.view.View.MeasureSpec;
36import android.view.ViewGroup;
Abodunrinwa Toki517adad2015-04-07 22:13:51 +010037import android.view.ViewTreeObserver;
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +000038import android.view.Window;
Abodunrinwa Toki517adad2015-04-07 22:13:51 +010039import android.view.WindowManager;
40import android.view.animation.Animation;
41import android.view.animation.AnimationSet;
42import android.view.animation.Transformation;
43import android.widget.AdapterView;
44import android.widget.ArrayAdapter;
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +000045import android.widget.Button;
46import android.widget.ImageButton;
47import android.widget.LinearLayout;
Abodunrinwa Toki517adad2015-04-07 22:13:51 +010048import android.widget.ListView;
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +000049import android.widget.PopupWindow;
Abodunrinwa Toki517adad2015-04-07 22:13:51 +010050import android.widget.TextView;
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +000051
52import java.util.ArrayList;
53import java.util.LinkedList;
54import java.util.List;
55
Abodunrinwa Toki517adad2015-04-07 22:13:51 +010056import com.android.internal.R;
57import com.android.internal.util.Preconditions;
58
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +000059/**
60 * A floating toolbar for showing contextual menu items.
61 * This view shows as many menu item buttons as can fit in the horizontal toolbar and the
62 * the remaining menu items in a vertical overflow view when the overflow button is clicked.
63 * The horizontal toolbar morphs into the vertical overflow view.
64 */
65public final class FloatingToolbar {
66
Abodunrinwa Toki517adad2015-04-07 22:13:51 +010067 // This class is responsible for the public API of the floating toolbar.
68 // It delegates rendering operations to the FloatingToolbarPopup.
69
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +000070 private static final MenuItem.OnMenuItemClickListener NO_OP_MENUITEM_CLICK_LISTENER =
71 new MenuItem.OnMenuItemClickListener() {
72 @Override
73 public boolean onMenuItemClick(MenuItem item) {
74 return false;
75 }
76 };
77
78 private final Context mContext;
79 private final FloatingToolbarPopup mPopup;
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +000080
81 private final Rect mContentRect = new Rect();
82 private final Point mCoordinates = new Point();
83
84 private Menu mMenu;
85 private List<CharSequence> mShowingTitles = new ArrayList<CharSequence>();
86 private MenuItem.OnMenuItemClickListener mMenuItemClickListener = NO_OP_MENUITEM_CLICK_LISTENER;
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +000087
88 private int mSuggestedWidth;
Abodunrinwa Toki517adad2015-04-07 22:13:51 +010089 private boolean mWidthChanged = true;
90 private int mOverflowDirection;
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +000091
92 /**
93 * Initializes a floating toolbar.
94 */
95 public FloatingToolbar(Context context, Window window) {
96 mContext = Preconditions.checkNotNull(context);
Abodunrinwa Toki517adad2015-04-07 22:13:51 +010097 mPopup = new FloatingToolbarPopup(window.getDecorView());
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +000098 }
99
100 /**
101 * Sets the menu to be shown in this floating toolbar.
102 * NOTE: Call {@link #updateLayout()} or {@link #show()} to effect visual changes to the
103 * toolbar.
104 */
105 public FloatingToolbar setMenu(Menu menu) {
106 mMenu = Preconditions.checkNotNull(menu);
107 return this;
108 }
109
110 /**
111 * Sets the custom listener for invocation of menu items in this floating
112 * toolbar.
113 */
114 public FloatingToolbar setOnMenuItemClickListener(
115 MenuItem.OnMenuItemClickListener menuItemClickListener) {
116 if (menuItemClickListener != null) {
117 mMenuItemClickListener = menuItemClickListener;
118 } else {
119 mMenuItemClickListener = NO_OP_MENUITEM_CLICK_LISTENER;
120 }
121 return this;
122 }
123
124 /**
125 * Sets the content rectangle. This is the area of the interesting content that this toolbar
126 * should avoid obstructing.
127 * NOTE: Call {@link #updateLayout()} or {@link #show()} to effect visual changes to the
128 * toolbar.
129 */
130 public FloatingToolbar setContentRect(Rect rect) {
131 mContentRect.set(Preconditions.checkNotNull(rect));
132 return this;
133 }
134
135 /**
136 * Sets the suggested width of this floating toolbar.
137 * The actual width will be about this size but there are no guarantees that it will be exactly
138 * the suggested width.
139 * NOTE: Call {@link #updateLayout()} or {@link #show()} to effect visual changes to the
140 * toolbar.
141 */
142 public FloatingToolbar setSuggestedWidth(int suggestedWidth) {
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100143 // Check if there's been a substantial width spec change.
144 int difference = Math.abs(suggestedWidth - mSuggestedWidth);
145 mWidthChanged = difference > (mSuggestedWidth * 0.2);
146
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +0000147 mSuggestedWidth = suggestedWidth;
148 return this;
149 }
150
151 /**
152 * Shows this floating toolbar.
153 */
154 public FloatingToolbar show() {
155 List<MenuItem> menuItems = getVisibleAndEnabledMenuItems(mMenu);
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100156 if (!isCurrentlyShowing(menuItems) || mWidthChanged) {
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +0000157 mPopup.dismiss();
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100158 mPopup.layoutMenuItems(menuItems, mMenuItemClickListener, mSuggestedWidth);
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +0000159 mShowingTitles = getMenuItemTitles(menuItems);
160 }
161 refreshCoordinates();
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100162 mPopup.setOverflowDirection(mOverflowDirection);
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +0000163 mPopup.updateCoordinates(mCoordinates.x, mCoordinates.y);
164 if (!mPopup.isShowing()) {
165 mPopup.show(mCoordinates.x, mCoordinates.y);
166 }
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100167 mWidthChanged = false;
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +0000168 return this;
169 }
170
171 /**
172 * Updates this floating toolbar to reflect recent position and view updates.
173 * NOTE: This method is a no-op if the toolbar isn't showing.
174 */
175 public FloatingToolbar updateLayout() {
176 if (mPopup.isShowing()) {
177 // show() performs all the logic we need here.
178 show();
179 }
180 return this;
181 }
182
183 /**
184 * Dismisses this floating toolbar.
185 */
186 public void dismiss() {
187 mPopup.dismiss();
188 }
189
190 /**
191 * Returns {@code true} if this popup is currently showing. {@code false} otherwise.
192 */
193 public boolean isShowing() {
194 return mPopup.isShowing();
195 }
196
197 /**
198 * Refreshes {@link #mCoordinates} with values based on {@link #mContentRect}.
199 */
200 private void refreshCoordinates() {
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100201 int x = mContentRect.centerX() - mPopup.getWidth() / 2;
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +0000202 int y;
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100203 if (mContentRect.top > mPopup.getHeight()) {
204 y = mContentRect.top - mPopup.getHeight();
205 mOverflowDirection = FloatingToolbarPopup.OVERFLOW_DIRECTION_UP;
Abodunrinwa Tokif8e14fd2015-04-14 18:59:40 +0100206 } else if (mContentRect.top > mPopup.getToolbarHeightWithVerticalMargin()) {
207 y = mContentRect.top - mPopup.getToolbarHeightWithVerticalMargin();
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100208 mOverflowDirection = FloatingToolbarPopup.OVERFLOW_DIRECTION_DOWN;
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +0000209 } else {
210 y = mContentRect.bottom;
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100211 mOverflowDirection = FloatingToolbarPopup.OVERFLOW_DIRECTION_DOWN;
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +0000212 }
213 mCoordinates.set(x, y);
214 }
215
216 /**
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100217 * Returns true if this floating toolbar is currently showing the specified menu items.
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +0000218 */
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100219 private boolean isCurrentlyShowing(List<MenuItem> menuItems) {
220 return mShowingTitles.equals(getMenuItemTitles(menuItems));
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +0000221 }
222
223 /**
224 * Returns the visible and enabled menu items in the specified menu.
225 * This method is recursive.
226 */
227 private List<MenuItem> getVisibleAndEnabledMenuItems(Menu menu) {
228 List<MenuItem> menuItems = new ArrayList<MenuItem>();
229 for (int i = 0; (menu != null) && (i < menu.size()); i++) {
230 MenuItem menuItem = menu.getItem(i);
231 if (menuItem.isVisible() && menuItem.isEnabled()) {
232 Menu subMenu = menuItem.getSubMenu();
233 if (subMenu != null) {
234 menuItems.addAll(getVisibleAndEnabledMenuItems(subMenu));
235 } else {
236 menuItems.add(menuItem);
237 }
238 }
239 }
240 return menuItems;
241 }
242
243 private List<CharSequence> getMenuItemTitles(List<MenuItem> menuItems) {
244 List<CharSequence> titles = new ArrayList<CharSequence>();
245 for (MenuItem menuItem : menuItems) {
246 titles.add(menuItem.getTitle());
247 }
248 return titles;
249 }
250
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +0000251
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100252 /**
253 * A popup window used by the floating toolbar.
254 *
255 * This class is responsible for the rendering/animation of the floating toolbar.
256 * It can hold one of 2 panels (i.e. main panel and overflow panel) at a time.
257 * It delegates specific panel functionality to the appropriate panel.
258 */
259 private static final class FloatingToolbarPopup {
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +0000260
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100261 public static final int OVERFLOW_DIRECTION_UP = 0;
262 public static final int OVERFLOW_DIRECTION_DOWN = 1;
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +0000263
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100264 private final View mParent;
265 private final PopupWindow mPopupWindow;
266 private final ViewGroup mContentContainer;
Abodunrinwa Tokif8e14fd2015-04-14 18:59:40 +0100267 private final int mMarginHorizontal;
268 private final int mMarginVertical;
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +0000269
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100270 private final Animation.AnimationListener mOnOverflowOpened =
271 new Animation.AnimationListener() {
272 @Override
273 public void onAnimationStart(Animation animation) {}
274
275 @Override
276 public void onAnimationEnd(Animation animation) {
277 // This animation should never be run if the overflow panel has not been
278 // initialized.
279 Preconditions.checkNotNull(mOverflowPanel);
280 mContentContainer.removeAllViews();
281 mContentContainer.addView(mOverflowPanel.getView());
282 mOverflowPanel.fadeIn(true);
283 setContentAreaAsTouchableSurface();
284 }
285
286 @Override
287 public void onAnimationRepeat(Animation animation) {}
288 };
289 private final Animation.AnimationListener mOnOverflowClosed =
290 new Animation.AnimationListener() {
291 @Override
292 public void onAnimationStart(Animation animation) {}
293
294 @Override
295 public void onAnimationEnd(Animation animation) {
296 // This animation should never be run if the main panel has not been
297 // initialized.
298 Preconditions.checkNotNull(mMainPanel);
299 mContentContainer.removeAllViews();
300 mContentContainer.addView(mMainPanel.getView());
301 mMainPanel.fadeIn(true);
302 setContentAreaAsTouchableSurface();
303 }
304
305 @Override
306 public void onAnimationRepeat(Animation animation) {
307 }
308 };
309 private final AnimatorSet mGrowFadeInFromBottomAnimation;
310 private final AnimatorSet mShrinkFadeOutFromBottomAnimation;
311
312 private final Runnable mOpenOverflow = new Runnable() {
313 @Override
314 public void run() {
315 openOverflow();
316 }
317 };
318 private final Runnable mCloseOverflow = new Runnable() {
319 @Override
320 public void run() {
321 closeOverflow();
322 }
323 };
324
325 private final Region mTouchableRegion = new Region();
326
327 private boolean mDismissAnimating;
328
329 private FloatingToolbarOverflowPanel mOverflowPanel;
330 private FloatingToolbarMainPanel mMainPanel;
331 private int mOverflowDirection;
332
333 /**
334 * Initializes a new floating toolbar popup.
335 *
336 * @param parent A parent view to get the {@link android.view.View#getWindowToken()} token
337 * from.
338 */
339 public FloatingToolbarPopup(View parent) {
340 mParent = Preconditions.checkNotNull(parent);
341 mContentContainer = createContentContainer(parent.getContext());
342 mPopupWindow = createPopupWindow(mContentContainer);
343 mGrowFadeInFromBottomAnimation = createGrowFadeInFromBottom(mContentContainer);
344 mShrinkFadeOutFromBottomAnimation = createShrinkFadeOutFromBottomAnimation(
345 mContentContainer,
346 new AnimatorListenerAdapter() {
347 @Override
348 public void onAnimationEnd(Animator animation) {
349 mPopupWindow.dismiss();
350 mDismissAnimating = false;
351 setMainPanelAsContent();
352 }
353 });
354 // Make the touchable area of this popup be the area specified by mTouchableRegion.
355 mPopupWindow.getContentView()
356 .getRootView()
357 .getViewTreeObserver()
358 .addOnComputeInternalInsetsListener(
359 new ViewTreeObserver.OnComputeInternalInsetsListener() {
360 public void onComputeInternalInsets(
361 ViewTreeObserver.InternalInsetsInfo info) {
362 info.contentInsets.setEmpty();
363 info.visibleInsets.setEmpty();
364 info.touchableRegion.set(mTouchableRegion);
365 info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo
366 .TOUCHABLE_INSETS_REGION);
367 }
368 });
Abodunrinwa Tokif8e14fd2015-04-14 18:59:40 +0100369 mMarginHorizontal = parent.getResources()
370 .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin);
371 mMarginVertical = parent.getResources()
372 .getDimensionPixelSize(R.dimen.floating_toolbar_vertical_margin);
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100373 }
374
375 /**
376 * Lays out buttons for the specified menu items.
377 */
378 public void layoutMenuItems(List<MenuItem> menuItems,
379 MenuItem.OnMenuItemClickListener menuItemClickListener, int suggestedWidth) {
380 mContentContainer.removeAllViews();
381 if (mMainPanel == null) {
382 mMainPanel = new FloatingToolbarMainPanel(mParent.getContext(), mOpenOverflow);
383 }
384 List<MenuItem> overflowMenuItems =
385 mMainPanel.layoutMenuItems(menuItems, suggestedWidth);
386 mMainPanel.setOnMenuItemClickListener(menuItemClickListener);
387 if (!overflowMenuItems.isEmpty()) {
388 if (mOverflowPanel == null) {
389 mOverflowPanel =
390 new FloatingToolbarOverflowPanel(mParent.getContext(), mCloseOverflow);
391 }
392 mOverflowPanel.setMenuItems(overflowMenuItems);
393 mOverflowPanel.setOnMenuItemClickListener(menuItemClickListener);
394 }
395 updatePopupSize();
396 }
397
398 /**
399 * Shows this popup at the specified coordinates.
400 * The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
401 */
402 public void show(int x, int y) {
403 if (isShowing()) {
404 return;
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +0000405 }
406
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100407 stopDismissAnimation();
408 preparePopupContent();
409 mPopupWindow.showAtLocation(mParent, Gravity.NO_GRAVITY, x, y);
410 growFadeInFromBottom();
411 }
412
413 /**
414 * Gets rid of this popup. If the popup isn't currently showing, this will be a no-op.
415 */
416 public void dismiss() {
417 if (!isShowing()) {
418 return;
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +0000419 }
420
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100421 mDismissAnimating = true;
422 shrinkFadeOutFromBottom();
423 setZeroTouchableSurface();
424 }
425
426 /**
427 * Returns {@code true} if this popup is currently showing. {@code false} otherwise.
428 */
429 public boolean isShowing() {
430 return mPopupWindow.isShowing() && !mDismissAnimating;
431 }
432
433 /**
434 * Updates the coordinates of this popup.
435 * The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
436 */
437 public void updateCoordinates(int x, int y) {
438 if (mDismissAnimating) {
439 // Already being dismissed. Ignore.
440 return;
441 }
442
443 preparePopupContent();
444 mPopupWindow.update(x, y, getWidth(), getHeight());
445 }
446
447 /**
448 * Sets the direction in which the overflow will open. i.e. up or down.
449 *
450 * @param overflowDirection Either {@link #OVERFLOW_DIRECTION_UP}
451 * or {@link #OVERFLOW_DIRECTION_DOWN}.
452 */
453 public void setOverflowDirection(int overflowDirection) {
454 mOverflowDirection = overflowDirection;
455 if (mOverflowPanel != null) {
456 mOverflowPanel.setOverflowDirection(mOverflowDirection);
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +0000457 }
458 }
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100459
460 /**
461 * Returns the width of this popup.
462 */
463 public int getWidth() {
464 return mPopupWindow.getWidth();
465 }
466
467 /**
468 * Returns the height of this popup.
469 */
470 public int getHeight() {
471 return mPopupWindow.getHeight();
472 }
473
474 /**
475 * Returns the context this popup is running in.
476 */
477 public Context getContext() {
478 return mContentContainer.getContext();
479 }
480
Abodunrinwa Tokif8e14fd2015-04-14 18:59:40 +0100481 int getToolbarHeightWithVerticalMargin() {
482 return getEstimatedToolbarHeight(mParent.getContext()) + mMarginVertical * 2;
483 }
484
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100485 /**
486 * Performs the "grow and fade in from the bottom" animation on the floating popup.
487 */
488 private void growFadeInFromBottom() {
489 mGrowFadeInFromBottomAnimation.start();
490 }
491
492 /**
493 * Performs the "shrink and fade out from bottom" animation on the floating popup.
494 */
495 private void shrinkFadeOutFromBottom() {
496 mShrinkFadeOutFromBottomAnimation.start();
497 }
498
499 private void stopDismissAnimation() {
500 mDismissAnimating = false;
501 mShrinkFadeOutFromBottomAnimation.cancel();
502 }
503
504 /**
505 * Opens the floating toolbar overflow.
506 * This method should not be called if menu items have not been laid out with
507 * {@link #layoutMenuItems(List, MenuItem.OnMenuItemClickListener, int)}.
508 *
509 * @throws IllegalStateException if called when menu items have not been laid out.
510 */
511 private void openOverflow() {
512 Preconditions.checkNotNull(mMainPanel);
513 Preconditions.checkNotNull(mOverflowPanel);
514
515 mMainPanel.fadeOut(true);
516 Size overflowPanelSize = mOverflowPanel.measure();
Abodunrinwa Tokif8e14fd2015-04-14 18:59:40 +0100517 final int targetWidth = overflowPanelSize.getWidth();
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100518 final int targetHeight = overflowPanelSize.getHeight();
519 final boolean morphUpwards = (mOverflowDirection == OVERFLOW_DIRECTION_UP);
520 final int startWidth = mContentContainer.getWidth();
521 final int startHeight = mContentContainer.getHeight();
522 final float startY = mContentContainer.getY();
523 final float right = mContentContainer.getX() + mContentContainer.getWidth();
524 Animation widthAnimation = new Animation() {
525 @Override
526 protected void applyTransformation(float interpolatedTime, Transformation t) {
527 ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
528 int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth));
529 params.width = startWidth + deltaWidth;
530 mContentContainer.setLayoutParams(params);
531 mContentContainer.setX(right - mContentContainer.getWidth());
532 }
533 };
534 Animation heightAnimation = new Animation() {
535 @Override
536 protected void applyTransformation(float interpolatedTime, Transformation t) {
537 ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
538 int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight));
539 params.height = startHeight + deltaHeight;
540 mContentContainer.setLayoutParams(params);
541 if (morphUpwards) {
542 float y = startY - (mContentContainer.getHeight() - startHeight);
543 mContentContainer.setY(y);
544 }
545 }
546 };
547 widthAnimation.setDuration(240);
548 heightAnimation.setDuration(180);
549 heightAnimation.setStartOffset(60);
550 AnimationSet animation = new AnimationSet(true);
551 animation.setAnimationListener(mOnOverflowOpened);
552 animation.addAnimation(widthAnimation);
553 animation.addAnimation(heightAnimation);
554 mContentContainer.startAnimation(animation);
555 }
556
557 /**
558 * Opens the floating toolbar overflow.
559 * This method should not be called if menu items have not been laid out with
560 * {@link #layoutMenuItems(java.util.List, MenuItem.OnMenuItemClickListener, int)}.
561 *
562 * @throws IllegalStateException
563 */
564 private void closeOverflow() {
565 Preconditions.checkNotNull(mMainPanel);
566 Preconditions.checkNotNull(mOverflowPanel);
567
568 mOverflowPanel.fadeOut(true);
569 Size mainPanelSize = mMainPanel.measure();
570 final int targetWidth = mainPanelSize.getWidth();
571 final int targetHeight = mainPanelSize.getHeight();
572 final int startWidth = mContentContainer.getWidth();
573 final int startHeight = mContentContainer.getHeight();
574 final float right = mContentContainer.getX() + mContentContainer.getWidth();
575 final float bottom = mContentContainer.getY() + mContentContainer.getHeight();
576 final boolean morphedUpwards = (mOverflowDirection == OVERFLOW_DIRECTION_UP);
577 Animation widthAnimation = new Animation() {
578 @Override
579 protected void applyTransformation(float interpolatedTime, Transformation t) {
580 ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
581 int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth));
582 params.width = startWidth + deltaWidth;
583 mContentContainer.setLayoutParams(params);
584 mContentContainer.setX(right - mContentContainer.getWidth());
585 }
586 };
587 Animation heightAnimation = new Animation() {
588 @Override
589 protected void applyTransformation(float interpolatedTime, Transformation t) {
590 ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
591 int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight));
592 params.height = startHeight + deltaHeight;
593 mContentContainer.setLayoutParams(params);
594 if (morphedUpwards) {
595 mContentContainer.setY(bottom - mContentContainer.getHeight());
596 }
597 }
598 };
599 widthAnimation.setDuration(150);
600 widthAnimation.setStartOffset(150);
601 heightAnimation.setDuration(210);
602 AnimationSet animation = new AnimationSet(true);
603 animation.setAnimationListener(mOnOverflowClosed);
604 animation.addAnimation(widthAnimation);
605 animation.addAnimation(heightAnimation);
606 mContentContainer.startAnimation(animation);
607 }
608
609 /**
610 * Prepares the content container for show and update calls.
611 */
612 private void preparePopupContent() {
613 // Do not call this method if main view panel has not been initialized.
614 Preconditions.checkNotNull(mMainPanel);
615
616 // If we're yet to show the popup, set the container visibility to zero.
617 // The "show" animation will make this visible.
618 if (!mPopupWindow.isShowing()) {
619 mContentContainer.setAlpha(0);
620 }
621
622 // Make sure panels are visible.
623 mMainPanel.fadeIn(false);
624 if (mOverflowPanel != null) {
625 mOverflowPanel.fadeIn(false);
626 }
627
628 // Make sure a panel is set as the content.
629 if (mContentContainer.getChildCount() == 0) {
630 mContentContainer.addView(mMainPanel.getView());
631 }
632
633 // Make sure the main panel is at the correct position.
634 if (mContentContainer.getChildAt(0) == mMainPanel.getView()) {
Abodunrinwa Tokif8e14fd2015-04-14 18:59:40 +0100635 float x = mPopupWindow.getWidth()
636 - (mMainPanel.getView().getMeasuredWidth() + mMarginHorizontal);
637 mContentContainer.setX(x);
638
639 float y = mMarginVertical;
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100640 if (mOverflowDirection == OVERFLOW_DIRECTION_UP) {
Abodunrinwa Tokif8e14fd2015-04-14 18:59:40 +0100641 y = getHeight()
642 - (mMainPanel.getView().getMeasuredHeight() + mMarginVertical);
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100643 }
644 mContentContainer.setY(y);
645 }
646
647 setContentAreaAsTouchableSurface();
648 }
649
650 /**
651 * Sets the current content to be the main view panel.
652 */
653 private void setMainPanelAsContent() {
654 mContentContainer.removeAllViews();
655 Size mainPanelSize = mMainPanel.measure();
656 ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
657 params.width = mainPanelSize.getWidth();
658 params.height = mainPanelSize.getHeight();
659 mContentContainer.setLayoutParams(params);
660 mContentContainer.addView(mMainPanel.getView());
661 }
662
663 private void updatePopupSize() {
664 int width = 0;
665 int height = 0;
666 if (mMainPanel != null) {
667 Size mainPanelSize = mMainPanel.measure();
668 width = mainPanelSize.getWidth();
669 height = mainPanelSize.getHeight();
670 }
671 if (mOverflowPanel != null) {
672 Size overflowPanelSize = mOverflowPanel.measure();
673 width = Math.max(width, overflowPanelSize.getWidth());
674 height = Math.max(height, overflowPanelSize.getHeight());
675 }
Abodunrinwa Tokif8e14fd2015-04-14 18:59:40 +0100676 mPopupWindow.setWidth(width + mMarginHorizontal * 2);
677 mPopupWindow.setHeight(height + mMarginVertical * 2);
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100678 }
679
680 /**
681 * Sets the touchable region of this popup to be zero. This means that all touch events on
682 * this popup will go through to the surface behind it.
683 */
684 private void setZeroTouchableSurface() {
685 mTouchableRegion.setEmpty();
686 }
687
688 /**
689 * Sets the touchable region of this popup to be the area occupied by its content.
690 */
691 private void setContentAreaAsTouchableSurface() {
692 if (!mPopupWindow.isShowing()) {
693 mContentContainer.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
694 }
695 int width = mContentContainer.getMeasuredWidth();
696 int height = mContentContainer.getMeasuredHeight();
697 mTouchableRegion.set(
698 (int) mContentContainer.getX(),
699 (int) mContentContainer.getY(),
700 (int) mContentContainer.getX() + width,
701 (int) mContentContainer.getY() + height);
702 }
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +0000703 }
704
705 /**
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100706 * A widget that holds the primary menu items in the floating toolbar.
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +0000707 */
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100708 private static final class FloatingToolbarMainPanel {
709
710 private final Context mContext;
711 private final ViewGroup mContentView;
712 private final View.OnClickListener mMenuItemButtonOnClickListener =
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +0000713 new View.OnClickListener() {
714 @Override
715 public void onClick(View v) {
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100716 if (v.getTag() instanceof MenuItem) {
717 if (mOnMenuItemClickListener != null) {
718 mOnMenuItemClickListener.onMenuItemClick((MenuItem) v.getTag());
719 }
720 }
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +0000721 }
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100722 };
723 private final ViewFader viewFader;
724 private final Runnable mOpenOverflow;
725
726 private View mOpenOverflowButton;
727 private MenuItem.OnMenuItemClickListener mOnMenuItemClickListener;
728
729 /**
730 * Initializes a floating toolbar popup main view panel.
731 *
732 * @param context
733 * @param openOverflow The code that opens the toolbar popup overflow.
734 */
735 public FloatingToolbarMainPanel(Context context, Runnable openOverflow) {
736 mContext = Preconditions.checkNotNull(context);
737 mContentView = new LinearLayout(context);
738 viewFader = new ViewFader(mContentView);
739 mOpenOverflow = Preconditions.checkNotNull(openOverflow);
740 }
741
742 /**
743 * Fits as many menu items in the main panel and returns a list of the menu items that
744 * were not fit in.
745 *
746 * @return The menu items that are not included in this main panel.
747 */
748 public List<MenuItem> layoutMenuItems(List<MenuItem> menuItems, int suggestedWidth) {
749 final int toolbarWidth = getAdjustedToolbarWidth(mContext, suggestedWidth)
750 // Reserve space for the "open overflow" button.
751 - getEstimatedOpenOverflowButtonWidth(mContext);
752
753 int availableWidth = toolbarWidth;
754 final LinkedList<MenuItem> remainingMenuItems = new LinkedList<MenuItem>(menuItems);
755
756 mContentView.removeAllViews();
757
758 boolean isFirstItem = true;
759 while (!remainingMenuItems.isEmpty()) {
760 final MenuItem menuItem = remainingMenuItems.peek();
761 Button menuItemButton = createMenuItemButton(mContext, menuItem);
762
Abodunrinwa Tokif8e14fd2015-04-14 18:59:40 +0100763 // Adding additional start padding for the first button to even out button spacing.
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100764 if (isFirstItem) {
Abodunrinwa Tokif8e14fd2015-04-14 18:59:40 +0100765 menuItemButton.setPaddingRelative(
766 (int) (1.5 * menuItemButton.getPaddingStart()),
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100767 menuItemButton.getPaddingTop(),
Abodunrinwa Tokif8e14fd2015-04-14 18:59:40 +0100768 menuItemButton.getPaddingEnd(),
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100769 menuItemButton.getPaddingBottom());
770 isFirstItem = false;
771 }
772
Abodunrinwa Tokif8e14fd2015-04-14 18:59:40 +0100773 // Adding additional end padding for the last button to even out button spacing.
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100774 if (remainingMenuItems.size() == 1) {
Abodunrinwa Tokif8e14fd2015-04-14 18:59:40 +0100775 menuItemButton.setPaddingRelative(
776 menuItemButton.getPaddingStart(),
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100777 menuItemButton.getPaddingTop(),
Abodunrinwa Tokif8e14fd2015-04-14 18:59:40 +0100778 (int) (1.5 * menuItemButton.getPaddingEnd()),
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100779 menuItemButton.getPaddingBottom());
780 }
781
782 menuItemButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
783 int menuItemButtonWidth = Math.min(menuItemButton.getMeasuredWidth(), toolbarWidth);
784 if (menuItemButtonWidth <= availableWidth) {
785 menuItemButton.setTag(menuItem);
786 menuItemButton.setOnClickListener(mMenuItemButtonOnClickListener);
787 mContentView.addView(menuItemButton);
788 ViewGroup.LayoutParams params = menuItemButton.getLayoutParams();
789 params.width = menuItemButtonWidth;
790 menuItemButton.setLayoutParams(params);
791 availableWidth -= menuItemButtonWidth;
792 remainingMenuItems.pop();
793 } else {
794 if (mOpenOverflowButton == null) {
795 mOpenOverflowButton = (ImageButton) LayoutInflater.from(mContext)
796 .inflate(R.layout.floating_popup_open_overflow_button, null);
797 mOpenOverflowButton.setOnClickListener(new View.OnClickListener() {
798 @Override
799 public void onClick(View v) {
800 if (mOpenOverflowButton != null) {
801 mOpenOverflow.run();
802 }
803 }
804 });
805 }
806 mContentView.addView(mOpenOverflowButton);
807 break;
808 }
809 }
810 return remainingMenuItems;
811 }
812
813 public void setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener listener) {
814 mOnMenuItemClickListener = listener;
815 }
816
817 public View getView() {
818 return mContentView;
819 }
820
821 public void fadeIn(boolean animate) {
822 viewFader.fadeIn(animate);
823 }
824
825 public void fadeOut(boolean animate) {
826 viewFader.fadeOut(animate);
827 }
828
829 /**
830 * Returns how big this panel's view should be.
831 * This method should only be called when the view has not been attached to a parent
832 * otherwise it will throw an illegal state.
833 */
834 public Size measure() throws IllegalStateException {
835 Preconditions.checkState(mContentView.getParent() == null);
836 mContentView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
837 return new Size(mContentView.getMeasuredWidth(), mContentView.getMeasuredHeight());
838 }
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +0000839 }
840
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100841
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +0000842 /**
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100843 * A widget that holds the overflow items in the floating toolbar.
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +0000844 */
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100845 private static final class FloatingToolbarOverflowPanel {
846
847 private final LinearLayout mContentView;
848 private final ViewGroup mBackButtonContainer;
849 private final View mBackButton;
850 private final ListView mListView;
Abodunrinwa Tokif8e14fd2015-04-14 18:59:40 +0100851 private final TextView mListViewItemWidthCalculator;
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100852 private final ViewFader mViewFader;
853 private final Runnable mCloseOverflow;
854
855 private MenuItem.OnMenuItemClickListener mOnMenuItemClickListener;
Abodunrinwa Tokif8e14fd2015-04-14 18:59:40 +0100856 private int mOverflowWidth = 0;
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100857
858 /**
859 * Initializes a floating toolbar popup overflow view panel.
860 *
861 * @param context
862 * @param closeOverflow The code that closes the toolbar popup's overflow.
863 */
864 public FloatingToolbarOverflowPanel(Context context, Runnable closeOverflow) {
865 mCloseOverflow = Preconditions.checkNotNull(closeOverflow);
866
867 mContentView = new LinearLayout(context);
868 mContentView.setOrientation(LinearLayout.VERTICAL);
869 mViewFader = new ViewFader(mContentView);
870
871 mBackButton = LayoutInflater.from(context)
872 .inflate(R.layout.floating_popup_close_overflow_button, null);
873 mBackButton.setOnClickListener(new View.OnClickListener() {
874 @Override
875 public void onClick(View v) {
876 mCloseOverflow.run();
877 }
878 });
879 mBackButtonContainer = new LinearLayout(context);
880 mBackButtonContainer.addView(mBackButton);
881
Abodunrinwa Tokif8e14fd2015-04-14 18:59:40 +0100882 mListView = createOverflowListView();
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100883 mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
884 @Override
885 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
886 MenuItem menuItem = (MenuItem) mListView.getAdapter().getItem(position);
887 if (mOnMenuItemClickListener != null) {
888 mOnMenuItemClickListener.onMenuItemClick(menuItem);
889 }
890 }
891 });
892
893 mContentView.addView(mListView);
894 mContentView.addView(mBackButtonContainer);
Abodunrinwa Tokif8e14fd2015-04-14 18:59:40 +0100895
896 mListViewItemWidthCalculator = createOverflowMenuItemButton(context);
897 mListViewItemWidthCalculator.setLayoutParams(new ViewGroup.LayoutParams(
898 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100899 }
900
901 /**
902 * Sets the menu items to be displayed in the overflow.
903 */
904 public void setMenuItems(List<MenuItem> menuItems) {
905 ArrayAdapter overflowListViewAdapter = (ArrayAdapter) mListView.getAdapter();
906 overflowListViewAdapter.clear();
907 overflowListViewAdapter.addAll(menuItems);
908 setListViewHeight();
Abodunrinwa Tokif8e14fd2015-04-14 18:59:40 +0100909 setOverflowWidth();
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100910 }
911
912 public void setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener listener) {
913 mOnMenuItemClickListener = listener;
914 }
915
916 /**
917 * Notifies the overflow of the current direction in which the overflow will be opened.
918 *
919 * @param overflowDirection {@link FloatingToolbarPopup#OVERFLOW_DIRECTION_UP}
920 * or {@link FloatingToolbarPopup#OVERFLOW_DIRECTION_DOWN}.
921 */
922 public void setOverflowDirection(int overflowDirection) {
923 mContentView.removeView(mBackButtonContainer);
924 int index = (overflowDirection == FloatingToolbarPopup.OVERFLOW_DIRECTION_UP)? 1 : 0;
925 mContentView.addView(mBackButtonContainer, index);
926 }
927
928 /**
929 * Returns the content view of the overflow.
930 */
931 public View getView() {
932 return mContentView;
933 }
934
935 public void fadeIn(boolean animate) {
936 mViewFader.fadeIn(animate);
937 }
938
939 public void fadeOut(boolean animate) {
940 mViewFader.fadeOut(animate);
941 }
942
943 /**
944 * Returns how big this panel's view should be.
945 * This method should only be called when the view has not been attached to a parent.
946 *
947 * @throws IllegalStateException
948 */
949 public Size measure() {
950 Preconditions.checkState(mContentView.getParent() == null);
951 mContentView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
952 return new Size(mContentView.getMeasuredWidth(), mContentView.getMeasuredHeight());
953 }
954
955 private void setListViewHeight() {
956 int itemHeight = getEstimatedToolbarHeight(mContentView.getContext());
957 int height = mListView.getAdapter().getCount() * itemHeight;
958 int maxHeight = mContentView.getContext().getResources().
959 getDimensionPixelSize(R.dimen.floating_toolbar_minimum_overflow_height);
960 ViewGroup.LayoutParams params = mListView.getLayoutParams();
961 params.height = Math.min(height, maxHeight);
962 mListView.setLayoutParams(params);
963 }
964
Abodunrinwa Tokif8e14fd2015-04-14 18:59:40 +0100965 private int setOverflowWidth() {
966 for (int i = 0; i < mListView.getAdapter().getCount(); i++) {
967 MenuItem menuItem = (MenuItem) mListView.getAdapter().getItem(i);
968 Preconditions.checkNotNull(menuItem);
969 mListViewItemWidthCalculator.setText(menuItem.getTitle());
970 mListViewItemWidthCalculator.measure(
971 MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
972 mOverflowWidth = Math.max(
973 mListViewItemWidthCalculator.getMeasuredWidth(), mOverflowWidth);
974 }
975 return mOverflowWidth;
976 }
977
978 private ListView createOverflowListView() {
979 final Context context = mContentView.getContext();
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100980 final ListView overflowListView = new ListView(context);
981 overflowListView.setLayoutParams(new ViewGroup.LayoutParams(
982 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
983 overflowListView.setDivider(null);
984 overflowListView.setDividerHeight(0);
985 final ArrayAdapter overflowListViewAdapter =
986 new ArrayAdapter<MenuItem>(context, 0) {
987 @Override
988 public View getView(int position, View convertView, ViewGroup parent) {
989 TextView menuButton;
990 if (convertView != null) {
991 menuButton = (TextView) convertView;
992 } else {
993 menuButton = createOverflowMenuItemButton(context);
994 }
995 MenuItem menuItem = getItem(position);
996 menuButton.setText(menuItem.getTitle());
997 menuButton.setContentDescription(menuItem.getTitle());
Abodunrinwa Tokif8e14fd2015-04-14 18:59:40 +0100998 menuButton.setMinimumWidth(mOverflowWidth);
Abodunrinwa Toki517adad2015-04-07 22:13:51 +0100999 return menuButton;
1000 }
1001 };
1002 overflowListView.setAdapter(overflowListViewAdapter);
1003 return overflowListView;
1004 }
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +00001005 }
1006
Abodunrinwa Toki517adad2015-04-07 22:13:51 +01001007
1008 /**
1009 * A helper for fading in or out a view.
1010 */
1011 private static final class ViewFader {
1012
1013 private static final int FADE_OUT_DURATION = 250;
1014 private static final int FADE_IN_DURATION = 150;
1015
1016 private final View mView;
1017 private final ObjectAnimator mFadeOutAnimation;
1018 private final ObjectAnimator mFadeInAnimation;
1019
1020 private ViewFader(View view) {
1021 mView = Preconditions.checkNotNull(view);
1022 mFadeOutAnimation = ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0)
1023 .setDuration(FADE_OUT_DURATION);
1024 mFadeInAnimation = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1)
1025 .setDuration(FADE_IN_DURATION);
1026 }
1027
1028 public void fadeIn(boolean animate) {
1029 if (animate) {
1030 mFadeInAnimation.start();
1031 } else {
1032 mView.setAlpha(1);
1033 }
1034 }
1035
1036 public void fadeOut(boolean animate) {
1037 if (animate) {
1038 mFadeOutAnimation.start();
1039 } else {
1040 mView.setAlpha(0);
1041 }
1042 }
1043 }
1044
1045
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +00001046 /**
1047 * Creates and returns a menu button for the specified menu item.
1048 */
1049 private static Button createMenuItemButton(Context context, MenuItem menuItem) {
1050 Button menuItemButton = (Button) LayoutInflater.from(context)
1051 .inflate(R.layout.floating_popup_menu_button, null);
1052 menuItemButton.setText(menuItem.getTitle());
1053 menuItemButton.setContentDescription(menuItem.getTitle());
1054 return menuItemButton;
1055 }
1056
Abodunrinwa Toki517adad2015-04-07 22:13:51 +01001057 /**
1058 * Creates and returns a styled floating toolbar overflow list view item.
1059 */
1060 private static TextView createOverflowMenuItemButton(Context context) {
1061 return (TextView) LayoutInflater.from(context)
1062 .inflate(R.layout.floating_popup_overflow_list_item, null);
1063 }
1064
1065 private static ViewGroup createContentContainer(Context context) {
1066 return (ViewGroup) LayoutInflater.from(context)
1067 .inflate(R.layout.floating_popup_container, null);
1068 }
1069
1070 private static PopupWindow createPopupWindow(View content) {
1071 ViewGroup popupContentHolder = new LinearLayout(content.getContext());
1072 PopupWindow popupWindow = new PopupWindow(popupContentHolder);
1073 popupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
1074 popupWindow.setAnimationStyle(0);
1075 popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
1076 content.setLayoutParams(new ViewGroup.LayoutParams(
1077 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
1078 popupContentHolder.addView(content);
1079 return popupWindow;
1080 }
1081
1082 /**
1083 * Creates a "grow and fade in from the bottom" animation for the specified view.
1084 *
1085 * @param view The view to animate
1086 */
1087 private static AnimatorSet createGrowFadeInFromBottom(View view) {
1088 AnimatorSet growFadeInFromBottomAnimation = new AnimatorSet();
1089 growFadeInFromBottomAnimation.playTogether(
1090 ObjectAnimator.ofFloat(view, View.SCALE_X, 0.5f, 1).setDuration(125),
1091 ObjectAnimator.ofFloat(view, View.SCALE_Y, 0.5f, 1).setDuration(125),
1092 ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1).setDuration(75));
1093 growFadeInFromBottomAnimation.setStartDelay(50);
1094 return growFadeInFromBottomAnimation;
1095 }
1096
1097 /**
1098 * Creates a "shrink and fade out from bottom" animation for the specified view.
1099 *
1100 * @param view The view to animate
1101 * @param listener The animation listener
1102 */
1103 private static AnimatorSet createShrinkFadeOutFromBottomAnimation(
1104 View view, Animator.AnimatorListener listener) {
1105 AnimatorSet shrinkFadeOutFromBottomAnimation = new AnimatorSet();
1106 shrinkFadeOutFromBottomAnimation.playTogether(
1107 ObjectAnimator.ofFloat(view, View.SCALE_Y, 1, 0.5f).setDuration(125),
1108 ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0).setDuration(75));
1109 shrinkFadeOutFromBottomAnimation.setStartDelay(150);
1110 shrinkFadeOutFromBottomAnimation.addListener(listener);
1111 return shrinkFadeOutFromBottomAnimation;
1112 }
1113
Abodunrinwa Toki517adad2015-04-07 22:13:51 +01001114 private static int getEstimatedToolbarHeight(Context context) {
1115 return context.getResources().getDimensionPixelSize(R.dimen.floating_toolbar_height);
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +00001116 }
1117
1118 private static int getEstimatedOpenOverflowButtonWidth(Context context) {
1119 return context.getResources()
1120 .getDimensionPixelSize(R.dimen.floating_toolbar_menu_button_minimum_width);
1121 }
1122
1123 private static int getAdjustedToolbarWidth(Context context, int width) {
1124 if (width <= 0 || width > getScreenWidth(context)) {
1125 width = context.getResources()
1126 .getDimensionPixelSize(R.dimen.floating_toolbar_default_width);
1127 }
1128 return width;
1129 }
1130
1131 /**
1132 * Returns the device's screen width.
1133 */
Abodunrinwa Toki517adad2015-04-07 22:13:51 +01001134 private static int getScreenWidth(Context context) {
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +00001135 return context.getResources().getDisplayMetrics().widthPixels;
1136 }
1137
1138 /**
1139 * Returns the device's screen height.
1140 */
Abodunrinwa Toki517adad2015-04-07 22:13:51 +01001141 private static int getScreenHeight(Context context) {
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +00001142 return context.getResources().getDisplayMetrics().heightPixels;
1143 }
1144
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +00001145 /**
Abodunrinwa Toki517adad2015-04-07 22:13:51 +01001146 * Returns value, restricted to the range min->max (inclusive).
1147 * If maximum is less than minimum, the result is undefined.
1148 *
1149 * @param value The value to clamp.
1150 * @param minimum The minimum value in the range.
1151 * @param maximum The maximum value in the range. Must not be less than minimum.
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +00001152 */
Abodunrinwa Toki517adad2015-04-07 22:13:51 +01001153 private static int clamp(int value, int minimum, int maximum) {
1154 return Math.max(minimum, Math.min(value, maximum));
Abodunrinwa Toki0c7ed282015-03-27 15:02:03 +00001155 }
1156}