Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 1 | /* |
| 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 | |
| 17 | package com.android.internal.widget; |
| 18 | |
| 19 | import android.animation.Animator; |
| 20 | import android.animation.AnimatorListenerAdapter; |
| 21 | import android.animation.AnimatorSet; |
| 22 | import android.animation.ObjectAnimator; |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 23 | import android.animation.ValueAnimator; |
Abodunrinwa Toki | 29cb768 | 2018-04-11 21:24:20 +0100 | [diff] [blame] | 24 | import android.annotation.Nullable; |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 25 | import android.content.Context; |
Abodunrinwa Toki | 079f33b | 2015-06-23 20:36:52 -0700 | [diff] [blame] | 26 | import android.content.res.TypedArray; |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 27 | import android.graphics.Color; |
| 28 | import android.graphics.Point; |
| 29 | import android.graphics.Rect; |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 30 | import android.graphics.Region; |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 31 | import android.graphics.drawable.AnimatedVectorDrawable; |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 32 | import android.graphics.drawable.ColorDrawable; |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 33 | import android.graphics.drawable.Drawable; |
Abodunrinwa Toki | b21562c | 2015-05-20 22:25:16 +0100 | [diff] [blame] | 34 | import android.text.TextUtils; |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 35 | import android.util.Size; |
Abodunrinwa Toki | 5fedfb8 | 2017-02-06 19:34:00 +0000 | [diff] [blame] | 36 | import android.util.TypedValue; |
Abodunrinwa Toki | 079f33b | 2015-06-23 20:36:52 -0700 | [diff] [blame] | 37 | import android.view.ContextThemeWrapper; |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 38 | import android.view.Gravity; |
| 39 | import android.view.LayoutInflater; |
| 40 | import android.view.Menu; |
| 41 | import android.view.MenuItem; |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 42 | import android.view.MotionEvent; |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 43 | import android.view.View; |
| 44 | import android.view.View.MeasureSpec; |
Abodunrinwa Toki | 30f8ef4 | 2016-02-01 13:54:10 +0000 | [diff] [blame] | 45 | import android.view.View.OnLayoutChangeListener; |
Abodunrinwa Toki | f0f4d73 | 2016-03-23 15:56:43 +0000 | [diff] [blame] | 46 | import android.view.ViewConfiguration; |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 47 | import android.view.ViewGroup; |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 48 | import android.view.ViewTreeObserver; |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 49 | import android.view.Window; |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 50 | import android.view.WindowManager; |
| 51 | import android.view.animation.Animation; |
| 52 | import android.view.animation.AnimationSet; |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 53 | import android.view.animation.AnimationUtils; |
| 54 | import android.view.animation.Interpolator; |
Siyamed Sinir | 484c2e2 | 2017-06-07 16:26:19 -0700 | [diff] [blame] | 55 | import android.view.animation.Transformation; |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 56 | import android.widget.ArrayAdapter; |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 57 | import android.widget.ImageButton; |
Abodunrinwa Toki | b21562c | 2015-05-20 22:25:16 +0100 | [diff] [blame] | 58 | import android.widget.ImageView; |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 59 | import android.widget.LinearLayout; |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 60 | import android.widget.ListView; |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 61 | import android.widget.PopupWindow; |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 62 | import android.widget.TextView; |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 63 | |
Siyamed Sinir | 484c2e2 | 2017-06-07 16:26:19 -0700 | [diff] [blame] | 64 | import com.android.internal.R; |
| 65 | import com.android.internal.util.Preconditions; |
| 66 | |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 67 | import java.util.ArrayList; |
Abodunrinwa Toki | 9c881f2 | 2017-10-16 21:05:41 +0100 | [diff] [blame] | 68 | import java.util.Comparator; |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 69 | import java.util.LinkedList; |
| 70 | import java.util.List; |
Abodunrinwa Toki | 46850fc | 2017-05-22 15:20:18 +0100 | [diff] [blame] | 71 | import java.util.Objects; |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 72 | |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 73 | /** |
| 74 | * A floating toolbar for showing contextual menu items. |
| 75 | * This view shows as many menu item buttons as can fit in the horizontal toolbar and the |
| 76 | * the remaining menu items in a vertical overflow view when the overflow button is clicked. |
| 77 | * The horizontal toolbar morphs into the vertical overflow view. |
| 78 | */ |
| 79 | public final class FloatingToolbar { |
| 80 | |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 81 | // This class is responsible for the public API of the floating toolbar. |
| 82 | // It delegates rendering operations to the FloatingToolbarPopup. |
| 83 | |
Abodunrinwa Toki | 8a5e1ae | 2015-09-28 21:59:04 +0100 | [diff] [blame] | 84 | public static final String FLOATING_TOOLBAR_TAG = "floating_toolbar"; |
| 85 | |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 86 | private static final MenuItem.OnMenuItemClickListener NO_OP_MENUITEM_CLICK_LISTENER = |
Abodunrinwa Toki | 46850fc | 2017-05-22 15:20:18 +0100 | [diff] [blame] | 87 | item -> false; |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 88 | |
| 89 | private final Context mContext; |
Abodunrinwa Toki | 30f8ef4 | 2016-02-01 13:54:10 +0000 | [diff] [blame] | 90 | private final Window mWindow; |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 91 | private final FloatingToolbarPopup mPopup; |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 92 | |
| 93 | private final Rect mContentRect = new Rect(); |
Abodunrinwa Toki | c107b0e | 2015-06-25 21:33:51 -0700 | [diff] [blame] | 94 | private final Rect mPreviousContentRect = new Rect(); |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 95 | |
| 96 | private Menu mMenu; |
Abodunrinwa Toki | 46850fc | 2017-05-22 15:20:18 +0100 | [diff] [blame] | 97 | private List<MenuItem> mShowingMenuItems = new ArrayList<>(); |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 98 | private MenuItem.OnMenuItemClickListener mMenuItemClickListener = NO_OP_MENUITEM_CLICK_LISTENER; |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 99 | |
| 100 | private int mSuggestedWidth; |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 101 | private boolean mWidthChanged = true; |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 102 | |
Abodunrinwa Toki | 30f8ef4 | 2016-02-01 13:54:10 +0000 | [diff] [blame] | 103 | private final OnLayoutChangeListener mOrientationChangeHandler = new OnLayoutChangeListener() { |
| 104 | |
| 105 | private final Rect mNewRect = new Rect(); |
| 106 | private final Rect mOldRect = new Rect(); |
| 107 | |
Clara Bayarri | 6bc1224 | 2015-06-16 18:04:55 +0100 | [diff] [blame] | 108 | @Override |
Abodunrinwa Toki | 30f8ef4 | 2016-02-01 13:54:10 +0000 | [diff] [blame] | 109 | public void onLayoutChange( |
| 110 | View view, |
| 111 | int newLeft, int newRight, int newTop, int newBottom, |
| 112 | int oldLeft, int oldRight, int oldTop, int oldBottom) { |
| 113 | mNewRect.set(newLeft, newRight, newTop, newBottom); |
| 114 | mOldRect.set(oldLeft, oldRight, oldTop, oldBottom); |
| 115 | if (mPopup.isShowing() && !mNewRect.equals(mOldRect)) { |
Clara Bayarri | 6bc1224 | 2015-06-16 18:04:55 +0100 | [diff] [blame] | 116 | mWidthChanged = true; |
| 117 | updateLayout(); |
| 118 | } |
| 119 | } |
Clara Bayarri | 6bc1224 | 2015-06-16 18:04:55 +0100 | [diff] [blame] | 120 | }; |
| 121 | |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 122 | /** |
Abodunrinwa Toki | 9c881f2 | 2017-10-16 21:05:41 +0100 | [diff] [blame] | 123 | * Sorts the list of menu items to conform to certain requirements. |
| 124 | */ |
| 125 | private final Comparator<MenuItem> mMenuItemComparator = (menuItem1, menuItem2) -> { |
| 126 | // Ensure the assist menu item is always the first item: |
| 127 | if (menuItem1.getItemId() == android.R.id.textAssist) { |
| 128 | return menuItem2.getItemId() == android.R.id.textAssist ? 0 : -1; |
| 129 | } |
| 130 | if (menuItem2.getItemId() == android.R.id.textAssist) { |
| 131 | return 1; |
| 132 | } |
| 133 | |
| 134 | // Order by SHOW_AS_ACTION type: |
| 135 | if (menuItem1.requiresActionButton()) { |
| 136 | return menuItem2.requiresActionButton() ? 0 : -1; |
| 137 | } |
| 138 | if (menuItem2.requiresActionButton()) { |
| 139 | return 1; |
| 140 | } |
| 141 | if (menuItem1.requiresOverflow()) { |
| 142 | return menuItem2.requiresOverflow() ? 0 : 1; |
| 143 | } |
| 144 | if (menuItem2.requiresOverflow()) { |
| 145 | return -1; |
| 146 | } |
| 147 | |
| 148 | // Order by order value: |
| 149 | return menuItem1.getOrder() - menuItem2.getOrder(); |
| 150 | }; |
| 151 | |
| 152 | /** |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 153 | * Initializes a floating toolbar. |
| 154 | */ |
Tarandeep Singh | c9c83a9 | 2017-08-29 14:39:22 -0700 | [diff] [blame] | 155 | public FloatingToolbar(Window window) { |
| 156 | // TODO(b/65172902): Pass context in constructor when DecorView (and other callers) |
| 157 | // supports multi-display. |
| 158 | mContext = applyDefaultTheme(window.getContext()); |
Abodunrinwa Toki | 30f8ef4 | 2016-02-01 13:54:10 +0000 | [diff] [blame] | 159 | mWindow = Preconditions.checkNotNull(window); |
Abodunrinwa Toki | 079f33b | 2015-06-23 20:36:52 -0700 | [diff] [blame] | 160 | mPopup = new FloatingToolbarPopup(mContext, window.getDecorView()); |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 161 | } |
| 162 | |
| 163 | /** |
| 164 | * Sets the menu to be shown in this floating toolbar. |
| 165 | * NOTE: Call {@link #updateLayout()} or {@link #show()} to effect visual changes to the |
| 166 | * toolbar. |
| 167 | */ |
| 168 | public FloatingToolbar setMenu(Menu menu) { |
| 169 | mMenu = Preconditions.checkNotNull(menu); |
| 170 | return this; |
| 171 | } |
| 172 | |
| 173 | /** |
Abodunrinwa Toki | 7270d07 | 2015-04-17 20:31:34 +0100 | [diff] [blame] | 174 | * Sets the custom listener for invocation of menu items in this floating toolbar. |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 175 | */ |
| 176 | public FloatingToolbar setOnMenuItemClickListener( |
| 177 | MenuItem.OnMenuItemClickListener menuItemClickListener) { |
| 178 | if (menuItemClickListener != null) { |
| 179 | mMenuItemClickListener = menuItemClickListener; |
| 180 | } else { |
| 181 | mMenuItemClickListener = NO_OP_MENUITEM_CLICK_LISTENER; |
| 182 | } |
| 183 | return this; |
| 184 | } |
| 185 | |
| 186 | /** |
| 187 | * Sets the content rectangle. This is the area of the interesting content that this toolbar |
| 188 | * should avoid obstructing. |
| 189 | * NOTE: Call {@link #updateLayout()} or {@link #show()} to effect visual changes to the |
| 190 | * toolbar. |
| 191 | */ |
| 192 | public FloatingToolbar setContentRect(Rect rect) { |
| 193 | mContentRect.set(Preconditions.checkNotNull(rect)); |
| 194 | return this; |
| 195 | } |
| 196 | |
| 197 | /** |
| 198 | * Sets the suggested width of this floating toolbar. |
| 199 | * The actual width will be about this size but there are no guarantees that it will be exactly |
| 200 | * the suggested width. |
| 201 | * NOTE: Call {@link #updateLayout()} or {@link #show()} to effect visual changes to the |
| 202 | * toolbar. |
| 203 | */ |
| 204 | public FloatingToolbar setSuggestedWidth(int suggestedWidth) { |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 205 | // Check if there's been a substantial width spec change. |
| 206 | int difference = Math.abs(suggestedWidth - mSuggestedWidth); |
| 207 | mWidthChanged = difference > (mSuggestedWidth * 0.2); |
| 208 | |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 209 | mSuggestedWidth = suggestedWidth; |
| 210 | return this; |
| 211 | } |
| 212 | |
| 213 | /** |
| 214 | * Shows this floating toolbar. |
| 215 | */ |
| 216 | public FloatingToolbar show() { |
Abodunrinwa Toki | 30f8ef4 | 2016-02-01 13:54:10 +0000 | [diff] [blame] | 217 | registerOrientationHandler(); |
| 218 | doShow(); |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 219 | return this; |
| 220 | } |
| 221 | |
| 222 | /** |
| 223 | * Updates this floating toolbar to reflect recent position and view updates. |
| 224 | * NOTE: This method is a no-op if the toolbar isn't showing. |
| 225 | */ |
| 226 | public FloatingToolbar updateLayout() { |
| 227 | if (mPopup.isShowing()) { |
Abodunrinwa Toki | 30f8ef4 | 2016-02-01 13:54:10 +0000 | [diff] [blame] | 228 | doShow(); |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 229 | } |
| 230 | return this; |
| 231 | } |
| 232 | |
| 233 | /** |
| 234 | * Dismisses this floating toolbar. |
| 235 | */ |
| 236 | public void dismiss() { |
Abodunrinwa Toki | 30f8ef4 | 2016-02-01 13:54:10 +0000 | [diff] [blame] | 237 | unregisterOrientationHandler(); |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 238 | mPopup.dismiss(); |
| 239 | } |
| 240 | |
| 241 | /** |
Abodunrinwa Toki | 7270d07 | 2015-04-17 20:31:34 +0100 | [diff] [blame] | 242 | * Hides this floating toolbar. This is a no-op if the toolbar is not showing. |
| 243 | * Use {@link #isHidden()} to distinguish between a hidden and a dismissed toolbar. |
| 244 | */ |
| 245 | public void hide() { |
| 246 | mPopup.hide(); |
| 247 | } |
| 248 | |
| 249 | /** |
| 250 | * Returns {@code true} if this toolbar is currently showing. {@code false} otherwise. |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 251 | */ |
| 252 | public boolean isShowing() { |
| 253 | return mPopup.isShowing(); |
| 254 | } |
| 255 | |
| 256 | /** |
Abodunrinwa Toki | 7270d07 | 2015-04-17 20:31:34 +0100 | [diff] [blame] | 257 | * Returns {@code true} if this toolbar is currently hidden. {@code false} otherwise. |
| 258 | */ |
| 259 | public boolean isHidden() { |
| 260 | return mPopup.isHidden(); |
| 261 | } |
| 262 | |
Abodunrinwa Toki | 29cb768 | 2018-04-11 21:24:20 +0100 | [diff] [blame] | 263 | /** |
| 264 | * If this is set to true, the action mode view will dismiss itself on touch events outside of |
| 265 | * its window. If the toolbar is already showing, it will be re-shown so that this setting takes |
| 266 | * effect immediately. |
| 267 | * |
| 268 | * @param outsideTouchable whether or not this action mode is "outside touchable" |
| 269 | * @param onDismiss optional. Sets a callback for when this action mode popup dismisses itself |
| 270 | */ |
| 271 | public void setOutsideTouchable( |
| 272 | boolean outsideTouchable, @Nullable PopupWindow.OnDismissListener onDismiss) { |
| 273 | if (mPopup.setOutsideTouchable(outsideTouchable, onDismiss) && isShowing()) { |
| 274 | dismiss(); |
| 275 | doShow(); |
| 276 | } |
| 277 | } |
| 278 | |
Abodunrinwa Toki | 30f8ef4 | 2016-02-01 13:54:10 +0000 | [diff] [blame] | 279 | private void doShow() { |
| 280 | List<MenuItem> menuItems = getVisibleAndEnabledMenuItems(mMenu); |
Abodunrinwa Toki | 9c881f2 | 2017-10-16 21:05:41 +0100 | [diff] [blame] | 281 | menuItems.sort(mMenuItemComparator); |
Abodunrinwa Toki | 30f8ef4 | 2016-02-01 13:54:10 +0000 | [diff] [blame] | 282 | if (!isCurrentlyShowing(menuItems) || mWidthChanged) { |
| 283 | mPopup.dismiss(); |
| 284 | mPopup.layoutMenuItems(menuItems, mMenuItemClickListener, mSuggestedWidth); |
Abodunrinwa Toki | 46850fc | 2017-05-22 15:20:18 +0100 | [diff] [blame] | 285 | mShowingMenuItems = menuItems; |
Abodunrinwa Toki | 30f8ef4 | 2016-02-01 13:54:10 +0000 | [diff] [blame] | 286 | } |
| 287 | if (!mPopup.isShowing()) { |
| 288 | mPopup.show(mContentRect); |
| 289 | } else if (!mPreviousContentRect.equals(mContentRect)) { |
| 290 | mPopup.updateCoordinates(mContentRect); |
| 291 | } |
| 292 | mWidthChanged = false; |
| 293 | mPreviousContentRect.set(mContentRect); |
| 294 | } |
| 295 | |
Abodunrinwa Toki | 7270d07 | 2015-04-17 20:31:34 +0100 | [diff] [blame] | 296 | /** |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 297 | * Returns true if this floating toolbar is currently showing the specified menu items. |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 298 | */ |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 299 | private boolean isCurrentlyShowing(List<MenuItem> menuItems) { |
Abodunrinwa Toki | 46850fc | 2017-05-22 15:20:18 +0100 | [diff] [blame] | 300 | if (mShowingMenuItems == null || menuItems.size() != mShowingMenuItems.size()) { |
| 301 | return false; |
| 302 | } |
| 303 | |
| 304 | final int size = menuItems.size(); |
| 305 | for (int i = 0; i < size; i++) { |
| 306 | final MenuItem menuItem = menuItems.get(i); |
| 307 | final MenuItem showingItem = mShowingMenuItems.get(i); |
| 308 | if (menuItem.getItemId() != showingItem.getItemId() |
| 309 | || !TextUtils.equals(menuItem.getTitle(), showingItem.getTitle()) |
| 310 | || !Objects.equals(menuItem.getIcon(), showingItem.getIcon()) |
| 311 | || menuItem.getGroupId() != showingItem.getGroupId()) { |
| 312 | return false; |
| 313 | } |
| 314 | } |
| 315 | |
| 316 | return true; |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 317 | } |
| 318 | |
| 319 | /** |
| 320 | * Returns the visible and enabled menu items in the specified menu. |
| 321 | * This method is recursive. |
| 322 | */ |
| 323 | private List<MenuItem> getVisibleAndEnabledMenuItems(Menu menu) { |
Abodunrinwa Toki | 46850fc | 2017-05-22 15:20:18 +0100 | [diff] [blame] | 324 | List<MenuItem> menuItems = new ArrayList<>(); |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 325 | for (int i = 0; (menu != null) && (i < menu.size()); i++) { |
| 326 | MenuItem menuItem = menu.getItem(i); |
| 327 | if (menuItem.isVisible() && menuItem.isEnabled()) { |
| 328 | Menu subMenu = menuItem.getSubMenu(); |
| 329 | if (subMenu != null) { |
| 330 | menuItems.addAll(getVisibleAndEnabledMenuItems(subMenu)); |
| 331 | } else { |
| 332 | menuItems.add(menuItem); |
| 333 | } |
| 334 | } |
| 335 | } |
| 336 | return menuItems; |
| 337 | } |
| 338 | |
Abodunrinwa Toki | 30f8ef4 | 2016-02-01 13:54:10 +0000 | [diff] [blame] | 339 | private void registerOrientationHandler() { |
| 340 | unregisterOrientationHandler(); |
| 341 | mWindow.getDecorView().addOnLayoutChangeListener(mOrientationChangeHandler); |
| 342 | } |
| 343 | |
| 344 | private void unregisterOrientationHandler() { |
| 345 | mWindow.getDecorView().removeOnLayoutChangeListener(mOrientationChangeHandler); |
| 346 | } |
| 347 | |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 348 | |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 349 | /** |
| 350 | * A popup window used by the floating toolbar. |
| 351 | * |
| 352 | * This class is responsible for the rendering/animation of the floating toolbar. |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 353 | * It holds 2 panels (i.e. main panel and overflow panel) and an overflow button |
| 354 | * to transition between panels. |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 355 | */ |
| 356 | private static final class FloatingToolbarPopup { |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 357 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 358 | /* Minimum and maximum number of items allowed in the overflow. */ |
| 359 | private static final int MIN_OVERFLOW_SIZE = 2; |
| 360 | private static final int MAX_OVERFLOW_SIZE = 4; |
| 361 | |
Abodunrinwa Toki | 079f33b | 2015-06-23 20:36:52 -0700 | [diff] [blame] | 362 | private final Context mContext; |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 363 | private final View mParent; // Parent for the popup window. |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 364 | private final PopupWindow mPopupWindow; |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 365 | |
| 366 | /* Margins between the popup window and it's content. */ |
Abodunrinwa Toki | f8e14fd | 2015-04-14 18:59:40 +0100 | [diff] [blame] | 367 | private final int mMarginHorizontal; |
| 368 | private final int mMarginVertical; |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 369 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 370 | /* View components */ |
| 371 | private final ViewGroup mContentContainer; // holds all contents. |
| 372 | private final ViewGroup mMainPanel; // holds menu items that are initially displayed. |
Abodunrinwa Toki | cc9d617 | 2016-02-03 18:32:42 +0000 | [diff] [blame] | 373 | private final OverflowPanel mOverflowPanel; // holds menu items hidden in the overflow. |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 374 | private final ImageButton mOverflowButton; // opens/closes the overflow. |
| 375 | /* overflow button drawables. */ |
| 376 | private final Drawable mArrow; |
| 377 | private final Drawable mOverflow; |
| 378 | private final AnimatedVectorDrawable mToArrow; |
| 379 | private final AnimatedVectorDrawable mToOverflow; |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 380 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 381 | private final OverflowPanelViewHelper mOverflowPanelViewHelper; |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 382 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 383 | /* Animation interpolators. */ |
| 384 | private final Interpolator mLogAccelerateInterpolator; |
| 385 | private final Interpolator mFastOutSlowInInterpolator; |
| 386 | private final Interpolator mLinearOutSlowInInterpolator; |
| 387 | private final Interpolator mFastOutLinearInInterpolator; |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 388 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 389 | /* Animations. */ |
| 390 | private final AnimatorSet mShowAnimation; |
Abodunrinwa Toki | 7270d07 | 2015-04-17 20:31:34 +0100 | [diff] [blame] | 391 | private final AnimatorSet mDismissAnimation; |
| 392 | private final AnimatorSet mHideAnimation; |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 393 | private final AnimationSet mOpenOverflowAnimation; |
| 394 | private final AnimationSet mCloseOverflowAnimation; |
| 395 | private final Animation.AnimationListener mOverflowAnimationListener; |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 396 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 397 | private final Rect mViewPortOnScreen = new Rect(); // portion of screen we can draw in. |
| 398 | private final Point mCoordsOnWindow = new Point(); // popup window coordinates. |
| 399 | /* Temporary data holders. Reset values before using. */ |
Yohei Yukawa | 4b26997 | 2015-07-15 19:01:32 -0700 | [diff] [blame] | 400 | private final int[] mTmpCoords = new int[2]; |
Abodunrinwa Toki | ffebf68 | 2015-05-12 21:54:08 +0100 | [diff] [blame] | 401 | |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 402 | private final Region mTouchableRegion = new Region(); |
Abodunrinwa Toki | d5358ff | 2015-04-22 07:08:29 +0100 | [diff] [blame] | 403 | private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = |
Abodunrinwa Toki | 46850fc | 2017-05-22 15:20:18 +0100 | [diff] [blame] | 404 | info -> { |
| 405 | info.contentInsets.setEmpty(); |
| 406 | info.visibleInsets.setEmpty(); |
| 407 | info.touchableRegion.set(mTouchableRegion); |
| 408 | info.setTouchableInsets( |
| 409 | ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); |
Abodunrinwa Toki | d5358ff | 2015-04-22 07:08:29 +0100 | [diff] [blame] | 410 | }; |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 411 | |
Abodunrinwa Toki | 852e9ac | 2017-02-22 00:20:15 +0000 | [diff] [blame] | 412 | private final int mLineHeight; |
| 413 | private final int mIconTextSpacing; |
| 414 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 415 | /** |
| 416 | * @see OverflowPanelViewHelper#preparePopupContent(). |
| 417 | */ |
| 418 | private final Runnable mPreparePopupContentRTLHelper = new Runnable() { |
| 419 | @Override |
| 420 | public void run() { |
| 421 | setPanelsStatesAtRestingPosition(); |
| 422 | setContentAreaAsTouchableSurface(); |
| 423 | mContentContainer.setAlpha(1); |
| 424 | } |
| 425 | }; |
| 426 | |
Abodunrinwa Toki | 7270d07 | 2015-04-17 20:31:34 +0100 | [diff] [blame] | 427 | private boolean mDismissed = true; // tracks whether this popup is dismissed or dismissing. |
| 428 | private boolean mHidden; // tracks whether this popup is hidden or hiding. |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 429 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 430 | /* Calculated sizes for panels and overflow button. */ |
| 431 | private final Size mOverflowButtonSize; |
| 432 | private Size mOverflowPanelSize; // Should be null when there is no overflow. |
| 433 | private Size mMainPanelSize; |
| 434 | |
| 435 | /* Item click listeners */ |
| 436 | private MenuItem.OnMenuItemClickListener mOnMenuItemClickListener; |
| 437 | private final View.OnClickListener mMenuItemButtonOnClickListener = |
| 438 | new View.OnClickListener() { |
| 439 | @Override |
| 440 | public void onClick(View v) { |
| 441 | if (v.getTag() instanceof MenuItem) { |
| 442 | if (mOnMenuItemClickListener != null) { |
| 443 | mOnMenuItemClickListener.onMenuItemClick((MenuItem) v.getTag()); |
| 444 | } |
| 445 | } |
| 446 | } |
| 447 | }; |
| 448 | |
| 449 | private boolean mOpenOverflowUpwards; // Whether the overflow opens upwards or downwards. |
| 450 | private boolean mIsOverflowOpen; |
| 451 | |
| 452 | private int mTransitionDurationScale; // Used to scale the toolbar transition duration. |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 453 | |
| 454 | /** |
| 455 | * Initializes a new floating toolbar popup. |
| 456 | * |
| 457 | * @param parent A parent view to get the {@link android.view.View#getWindowToken()} token |
| 458 | * from. |
| 459 | */ |
Abodunrinwa Toki | 079f33b | 2015-06-23 20:36:52 -0700 | [diff] [blame] | 460 | public FloatingToolbarPopup(Context context, View parent) { |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 461 | mParent = Preconditions.checkNotNull(parent); |
Abodunrinwa Toki | 079f33b | 2015-06-23 20:36:52 -0700 | [diff] [blame] | 462 | mContext = Preconditions.checkNotNull(context); |
| 463 | mContentContainer = createContentContainer(context); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 464 | mPopupWindow = createPopupWindow(mContentContainer); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 465 | mMarginHorizontal = parent.getResources() |
| 466 | .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin); |
| 467 | mMarginVertical = parent.getResources() |
| 468 | .getDimensionPixelSize(R.dimen.floating_toolbar_vertical_margin); |
Abodunrinwa Toki | 852e9ac | 2017-02-22 00:20:15 +0000 | [diff] [blame] | 469 | mLineHeight = context.getResources() |
| 470 | .getDimensionPixelSize(R.dimen.floating_toolbar_height); |
| 471 | mIconTextSpacing = context.getResources() |
Abodunrinwa Toki | 49ec542 | 2018-04-25 14:07:17 +0100 | [diff] [blame] | 472 | .getDimensionPixelSize(R.dimen.floating_toolbar_icon_text_spacing); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 473 | |
| 474 | // Interpolators |
| 475 | mLogAccelerateInterpolator = new LogAccelerateInterpolator(); |
| 476 | mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator( |
| 477 | mContext, android.R.interpolator.fast_out_slow_in); |
| 478 | mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator( |
| 479 | mContext, android.R.interpolator.linear_out_slow_in); |
| 480 | mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator( |
| 481 | mContext, android.R.interpolator.fast_out_linear_in); |
| 482 | |
| 483 | // Drawables. Needed for views. |
| 484 | mArrow = mContext.getResources() |
| 485 | .getDrawable(R.drawable.ft_avd_tooverflow, mContext.getTheme()); |
| 486 | mArrow.setAutoMirrored(true); |
| 487 | mOverflow = mContext.getResources() |
| 488 | .getDrawable(R.drawable.ft_avd_toarrow, mContext.getTheme()); |
| 489 | mOverflow.setAutoMirrored(true); |
| 490 | mToArrow = (AnimatedVectorDrawable) mContext.getResources() |
| 491 | .getDrawable(R.drawable.ft_avd_toarrow_animation, mContext.getTheme()); |
| 492 | mToArrow.setAutoMirrored(true); |
| 493 | mToOverflow = (AnimatedVectorDrawable) mContext.getResources() |
| 494 | .getDrawable(R.drawable.ft_avd_tooverflow_animation, mContext.getTheme()); |
| 495 | mToOverflow.setAutoMirrored(true); |
| 496 | |
| 497 | // Views |
| 498 | mOverflowButton = createOverflowButton(); |
| 499 | mOverflowButtonSize = measure(mOverflowButton); |
| 500 | mMainPanel = createMainPanel(); |
Abodunrinwa Toki | 49ec542 | 2018-04-25 14:07:17 +0100 | [diff] [blame] | 501 | mOverflowPanelViewHelper = new OverflowPanelViewHelper(mContext, mIconTextSpacing); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 502 | mOverflowPanel = createOverflowPanel(); |
| 503 | |
| 504 | // Animation. Need views. |
| 505 | mOverflowAnimationListener = createOverflowAnimationListener(); |
| 506 | mOpenOverflowAnimation = new AnimationSet(true); |
| 507 | mOpenOverflowAnimation.setAnimationListener(mOverflowAnimationListener); |
| 508 | mCloseOverflowAnimation = new AnimationSet(true); |
| 509 | mCloseOverflowAnimation.setAnimationListener(mOverflowAnimationListener); |
| 510 | mShowAnimation = createEnterAnimation(mContentContainer); |
Abodunrinwa Toki | 7aa6d0a | 2015-06-03 21:33:15 +0100 | [diff] [blame] | 511 | mDismissAnimation = createExitAnimation( |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 512 | mContentContainer, |
Abodunrinwa Toki | 4ce050b | 2015-05-19 17:36:55 +0100 | [diff] [blame] | 513 | 150, // startDelay |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 514 | new AnimatorListenerAdapter() { |
| 515 | @Override |
| 516 | public void onAnimationEnd(Animator animation) { |
| 517 | mPopupWindow.dismiss(); |
Abodunrinwa Toki | b904437 | 2015-04-19 18:55:42 +0100 | [diff] [blame] | 518 | mContentContainer.removeAllViews(); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 519 | } |
| 520 | }); |
Abodunrinwa Toki | 7aa6d0a | 2015-06-03 21:33:15 +0100 | [diff] [blame] | 521 | mHideAnimation = createExitAnimation( |
Abodunrinwa Toki | 7270d07 | 2015-04-17 20:31:34 +0100 | [diff] [blame] | 522 | mContentContainer, |
Abodunrinwa Toki | 4ce050b | 2015-05-19 17:36:55 +0100 | [diff] [blame] | 523 | 0, // startDelay |
Abodunrinwa Toki | 7270d07 | 2015-04-17 20:31:34 +0100 | [diff] [blame] | 524 | new AnimatorListenerAdapter() { |
| 525 | @Override |
| 526 | public void onAnimationEnd(Animator animation) { |
| 527 | mPopupWindow.dismiss(); |
| 528 | } |
| 529 | }); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 530 | } |
| 531 | |
| 532 | /** |
Abodunrinwa Toki | 29cb768 | 2018-04-11 21:24:20 +0100 | [diff] [blame] | 533 | * Makes this toolbar "outside touchable" and sets the onDismissListener. |
| 534 | * This will take effect the next time the toolbar is re-shown. |
| 535 | * |
| 536 | * @param outsideTouchable if true, the popup will be made "outside touchable" and |
| 537 | * "non focusable". The reverse will happen if false. |
| 538 | * @param onDismiss |
| 539 | * |
| 540 | * @return true if the "outsideTouchable" setting was modified. Otherwise returns false |
| 541 | * |
| 542 | * @see PopupWindow#setOutsideTouchable(boolean) |
| 543 | * @see PopupWindow#setFocusable(boolean) |
| 544 | * @see PopupWindow.OnDismissListener |
| 545 | */ |
| 546 | public boolean setOutsideTouchable( |
| 547 | boolean outsideTouchable, @Nullable PopupWindow.OnDismissListener onDismiss) { |
| 548 | boolean ret = false; |
| 549 | if (mPopupWindow.isOutsideTouchable() ^ outsideTouchable) { |
| 550 | mPopupWindow.setOutsideTouchable(outsideTouchable); |
| 551 | mPopupWindow.setFocusable(!outsideTouchable); |
| 552 | ret = true; |
| 553 | } |
| 554 | mPopupWindow.setOnDismissListener(onDismiss); |
| 555 | return ret; |
| 556 | } |
| 557 | |
| 558 | /** |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 559 | * Lays out buttons for the specified menu items. |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 560 | * Requires a subsequent call to {@link #show()} to show the items. |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 561 | */ |
Abodunrinwa Toki | 6c5ac8e | 2015-06-01 17:35:34 +0100 | [diff] [blame] | 562 | public void layoutMenuItems( |
| 563 | List<MenuItem> menuItems, |
| 564 | MenuItem.OnMenuItemClickListener menuItemClickListener, |
| 565 | int suggestedWidth) { |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 566 | mOnMenuItemClickListener = menuItemClickListener; |
| 567 | cancelOverflowAnimations(); |
| 568 | clearPanels(); |
| 569 | menuItems = layoutMainPanelItems(menuItems, getAdjustedToolbarWidth(suggestedWidth)); |
| 570 | if (!menuItems.isEmpty()) { |
| 571 | // Add remaining items to the overflow. |
| 572 | layoutOverflowPanelItems(menuItems); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 573 | } |
| 574 | updatePopupSize(); |
| 575 | } |
| 576 | |
| 577 | /** |
| 578 | * Shows this popup at the specified coordinates. |
| 579 | * The specified coordinates may be adjusted to make sure the popup is entirely on-screen. |
| 580 | */ |
Yohei Yukawa | bafc908 | 2015-07-14 05:59:05 -0700 | [diff] [blame] | 581 | public void show(Rect contentRectOnScreen) { |
| 582 | Preconditions.checkNotNull(contentRectOnScreen); |
Abodunrinwa Toki | ffebf68 | 2015-05-12 21:54:08 +0100 | [diff] [blame] | 583 | |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 584 | if (isShowing()) { |
| 585 | return; |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 586 | } |
| 587 | |
Abodunrinwa Toki | 7270d07 | 2015-04-17 20:31:34 +0100 | [diff] [blame] | 588 | mHidden = false; |
| 589 | mDismissed = false; |
Abodunrinwa Toki | 0ce3e08 | 2015-04-21 20:33:21 +0100 | [diff] [blame] | 590 | cancelDismissAndHideAnimations(); |
| 591 | cancelOverflowAnimations(); |
Abodunrinwa Toki | ffebf68 | 2015-05-12 21:54:08 +0100 | [diff] [blame] | 592 | |
Yohei Yukawa | bafc908 | 2015-07-14 05:59:05 -0700 | [diff] [blame] | 593 | refreshCoordinatesAndOverflowDirection(contentRectOnScreen); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 594 | preparePopupContent(); |
Yohei Yukawa | 4b26997 | 2015-07-15 19:01:32 -0700 | [diff] [blame] | 595 | // We need to specify the position in window coordinates. |
Yohei Yukawa | bafc908 | 2015-07-14 05:59:05 -0700 | [diff] [blame] | 596 | // TODO: Consider to use PopupWindow.setLayoutInScreenEnabled(true) so that we can |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 597 | // specify the popup position in screen coordinates. |
| 598 | mPopupWindow.showAtLocation( |
| 599 | mParent, Gravity.NO_GRAVITY, mCoordsOnWindow.x, mCoordsOnWindow.y); |
Abodunrinwa Toki | d5358ff | 2015-04-22 07:08:29 +0100 | [diff] [blame] | 600 | setTouchableSurfaceInsetsComputer(); |
Abodunrinwa Toki | 7270d07 | 2015-04-17 20:31:34 +0100 | [diff] [blame] | 601 | runShowAnimation(); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 602 | } |
| 603 | |
| 604 | /** |
| 605 | * Gets rid of this popup. If the popup isn't currently showing, this will be a no-op. |
| 606 | */ |
| 607 | public void dismiss() { |
Abodunrinwa Toki | 0ce3e08 | 2015-04-21 20:33:21 +0100 | [diff] [blame] | 608 | if (mDismissed) { |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 609 | return; |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 610 | } |
| 611 | |
Abodunrinwa Toki | 7270d07 | 2015-04-17 20:31:34 +0100 | [diff] [blame] | 612 | mHidden = false; |
| 613 | mDismissed = true; |
Abodunrinwa Toki | 0ce3e08 | 2015-04-21 20:33:21 +0100 | [diff] [blame] | 614 | mHideAnimation.cancel(); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 615 | |
Abodunrinwa Toki | 7270d07 | 2015-04-17 20:31:34 +0100 | [diff] [blame] | 616 | runDismissAnimation(); |
| 617 | setZeroTouchableSurface(); |
| 618 | } |
| 619 | |
| 620 | /** |
| 621 | * Hides this popup. This is a no-op if this popup is not showing. |
| 622 | * Use {@link #isHidden()} to distinguish between a hidden and a dismissed popup. |
| 623 | */ |
| 624 | public void hide() { |
| 625 | if (!isShowing()) { |
| 626 | return; |
| 627 | } |
| 628 | |
| 629 | mHidden = true; |
| 630 | runHideAnimation(); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 631 | setZeroTouchableSurface(); |
| 632 | } |
| 633 | |
| 634 | /** |
| 635 | * Returns {@code true} if this popup is currently showing. {@code false} otherwise. |
| 636 | */ |
| 637 | public boolean isShowing() { |
Abodunrinwa Toki | c23ac32 | 2015-04-25 00:28:56 +0100 | [diff] [blame] | 638 | return !mDismissed && !mHidden; |
Abodunrinwa Toki | 7270d07 | 2015-04-17 20:31:34 +0100 | [diff] [blame] | 639 | } |
| 640 | |
| 641 | /** |
| 642 | * Returns {@code true} if this popup is currently hidden. {@code false} otherwise. |
| 643 | */ |
| 644 | public boolean isHidden() { |
| 645 | return mHidden; |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 646 | } |
| 647 | |
| 648 | /** |
| 649 | * Updates the coordinates of this popup. |
| 650 | * The specified coordinates may be adjusted to make sure the popup is entirely on-screen. |
Abodunrinwa Toki | 7270d07 | 2015-04-17 20:31:34 +0100 | [diff] [blame] | 651 | * This is a no-op if this popup is not showing. |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 652 | */ |
Yohei Yukawa | bafc908 | 2015-07-14 05:59:05 -0700 | [diff] [blame] | 653 | public void updateCoordinates(Rect contentRectOnScreen) { |
| 654 | Preconditions.checkNotNull(contentRectOnScreen); |
Abodunrinwa Toki | ffebf68 | 2015-05-12 21:54:08 +0100 | [diff] [blame] | 655 | |
Abodunrinwa Toki | c23ac32 | 2015-04-25 00:28:56 +0100 | [diff] [blame] | 656 | if (!isShowing() || !mPopupWindow.isShowing()) { |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 657 | return; |
| 658 | } |
| 659 | |
Abodunrinwa Toki | 0ce3e08 | 2015-04-21 20:33:21 +0100 | [diff] [blame] | 660 | cancelOverflowAnimations(); |
Yohei Yukawa | bafc908 | 2015-07-14 05:59:05 -0700 | [diff] [blame] | 661 | refreshCoordinatesAndOverflowDirection(contentRectOnScreen); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 662 | preparePopupContent(); |
Yohei Yukawa | 4b26997 | 2015-07-15 19:01:32 -0700 | [diff] [blame] | 663 | // We need to specify the position in window coordinates. |
Yohei Yukawa | bafc908 | 2015-07-14 05:59:05 -0700 | [diff] [blame] | 664 | // TODO: Consider to use PopupWindow.setLayoutInScreenEnabled(true) so that we can |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 665 | // specify the popup position in screen coordinates. |
| 666 | mPopupWindow.update( |
| 667 | mCoordsOnWindow.x, mCoordsOnWindow.y, |
| 668 | mPopupWindow.getWidth(), mPopupWindow.getHeight()); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 669 | } |
| 670 | |
Yohei Yukawa | bafc908 | 2015-07-14 05:59:05 -0700 | [diff] [blame] | 671 | private void refreshCoordinatesAndOverflowDirection(Rect contentRectOnScreen) { |
Abodunrinwa Toki | 6d2b75c | 2015-06-12 15:35:51 +0100 | [diff] [blame] | 672 | refreshViewPort(); |
| 673 | |
Abodunrinwa Toki | 51a8af6 | 2016-04-28 19:59:57 +0100 | [diff] [blame] | 674 | // Initialize x ensuring that the toolbar isn't rendered behind the nav bar in |
| 675 | // landscape. |
| 676 | final int x = Math.min( |
| 677 | contentRectOnScreen.centerX() - mPopupWindow.getWidth() / 2, |
| 678 | mViewPortOnScreen.right - mPopupWindow.getWidth()); |
Abodunrinwa Toki | e3eb183 | 2015-05-27 20:31:01 +0100 | [diff] [blame] | 679 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 680 | final int y; |
Abodunrinwa Toki | 9ae95df | 2015-06-19 03:04:50 +0100 | [diff] [blame] | 681 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 682 | final int availableHeightAboveContent = |
| 683 | contentRectOnScreen.top - mViewPortOnScreen.top; |
| 684 | final int availableHeightBelowContent = |
| 685 | mViewPortOnScreen.bottom - contentRectOnScreen.bottom; |
Abodunrinwa Toki | 9ae95df | 2015-06-19 03:04:50 +0100 | [diff] [blame] | 686 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 687 | final int margin = 2 * mMarginVertical; |
Abodunrinwa Toki | 852e9ac | 2017-02-22 00:20:15 +0000 | [diff] [blame] | 688 | final int toolbarHeightWithVerticalMargin = mLineHeight + margin; |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 689 | |
| 690 | if (!hasOverflow()) { |
| 691 | if (availableHeightAboveContent >= toolbarHeightWithVerticalMargin) { |
Abodunrinwa Toki | 6d2b75c | 2015-06-12 15:35:51 +0100 | [diff] [blame] | 692 | // There is enough space at the top of the content. |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 693 | y = contentRectOnScreen.top - toolbarHeightWithVerticalMargin; |
| 694 | } else if (availableHeightBelowContent >= toolbarHeightWithVerticalMargin) { |
Abodunrinwa Toki | 6d2b75c | 2015-06-12 15:35:51 +0100 | [diff] [blame] | 695 | // There is enough space at the bottom of the content. |
Yohei Yukawa | bafc908 | 2015-07-14 05:59:05 -0700 | [diff] [blame] | 696 | y = contentRectOnScreen.bottom; |
Abodunrinwa Toki | 852e9ac | 2017-02-22 00:20:15 +0000 | [diff] [blame] | 697 | } else if (availableHeightBelowContent >= mLineHeight) { |
Abodunrinwa Toki | 9ae95df | 2015-06-19 03:04:50 +0100 | [diff] [blame] | 698 | // Just enough space to fit the toolbar with no vertical margins. |
Yohei Yukawa | bafc908 | 2015-07-14 05:59:05 -0700 | [diff] [blame] | 699 | y = contentRectOnScreen.bottom - mMarginVertical; |
Abodunrinwa Toki | 6d2b75c | 2015-06-12 15:35:51 +0100 | [diff] [blame] | 700 | } else { |
| 701 | // Not enough space. Prefer to position as high as possible. |
| 702 | y = Math.max( |
Yohei Yukawa | bafc908 | 2015-07-14 05:59:05 -0700 | [diff] [blame] | 703 | mViewPortOnScreen.top, |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 704 | contentRectOnScreen.top - toolbarHeightWithVerticalMargin); |
Abodunrinwa Toki | 6d2b75c | 2015-06-12 15:35:51 +0100 | [diff] [blame] | 705 | } |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 706 | } else { |
| 707 | // Has an overflow. |
| 708 | final int minimumOverflowHeightWithMargin = |
| 709 | calculateOverflowHeight(MIN_OVERFLOW_SIZE) + margin; |
| 710 | final int availableHeightThroughContentDown = mViewPortOnScreen.bottom - |
| 711 | contentRectOnScreen.top + toolbarHeightWithVerticalMargin; |
| 712 | final int availableHeightThroughContentUp = contentRectOnScreen.bottom - |
| 713 | mViewPortOnScreen.top + toolbarHeightWithVerticalMargin; |
Abodunrinwa Toki | 9ae95df | 2015-06-19 03:04:50 +0100 | [diff] [blame] | 714 | |
| 715 | if (availableHeightAboveContent >= minimumOverflowHeightWithMargin) { |
Abodunrinwa Toki | 6d2b75c | 2015-06-12 15:35:51 +0100 | [diff] [blame] | 716 | // There is enough space at the top of the content rect for the overflow. |
| 717 | // Position above and open upwards. |
Abodunrinwa Toki | 9ae95df | 2015-06-19 03:04:50 +0100 | [diff] [blame] | 718 | updateOverflowHeight(availableHeightAboveContent - margin); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 719 | y = contentRectOnScreen.top - mPopupWindow.getHeight(); |
| 720 | mOpenOverflowUpwards = true; |
| 721 | } else if (availableHeightAboveContent >= toolbarHeightWithVerticalMargin |
Abodunrinwa Toki | 9ae95df | 2015-06-19 03:04:50 +0100 | [diff] [blame] | 722 | && availableHeightThroughContentDown >= minimumOverflowHeightWithMargin) { |
Abodunrinwa Toki | 6d2b75c | 2015-06-12 15:35:51 +0100 | [diff] [blame] | 723 | // There is enough space at the top of the content rect for the main panel |
| 724 | // but not the overflow. |
| 725 | // Position above but open downwards. |
Abodunrinwa Toki | 9ae95df | 2015-06-19 03:04:50 +0100 | [diff] [blame] | 726 | updateOverflowHeight(availableHeightThroughContentDown - margin); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 727 | y = contentRectOnScreen.top - toolbarHeightWithVerticalMargin; |
| 728 | mOpenOverflowUpwards = false; |
Abodunrinwa Toki | 9ae95df | 2015-06-19 03:04:50 +0100 | [diff] [blame] | 729 | } else if (availableHeightBelowContent >= minimumOverflowHeightWithMargin) { |
Abodunrinwa Toki | 6d2b75c | 2015-06-12 15:35:51 +0100 | [diff] [blame] | 730 | // There is enough space at the bottom of the content rect for the overflow. |
| 731 | // Position below and open downwards. |
Abodunrinwa Toki | 9ae95df | 2015-06-19 03:04:50 +0100 | [diff] [blame] | 732 | updateOverflowHeight(availableHeightBelowContent - margin); |
Yohei Yukawa | bafc908 | 2015-07-14 05:59:05 -0700 | [diff] [blame] | 733 | y = contentRectOnScreen.bottom; |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 734 | mOpenOverflowUpwards = false; |
| 735 | } else if (availableHeightBelowContent >= toolbarHeightWithVerticalMargin |
Yohei Yukawa | bafc908 | 2015-07-14 05:59:05 -0700 | [diff] [blame] | 736 | && mViewPortOnScreen.height() >= minimumOverflowHeightWithMargin) { |
Abodunrinwa Toki | 9ae95df | 2015-06-19 03:04:50 +0100 | [diff] [blame] | 737 | // There is enough space at the bottom of the content rect for the main panel |
| 738 | // but not the overflow. |
| 739 | // Position below but open upwards. |
| 740 | updateOverflowHeight(availableHeightThroughContentUp - margin); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 741 | y = contentRectOnScreen.bottom + toolbarHeightWithVerticalMargin - |
| 742 | mPopupWindow.getHeight(); |
| 743 | mOpenOverflowUpwards = true; |
Abodunrinwa Toki | 6d2b75c | 2015-06-12 15:35:51 +0100 | [diff] [blame] | 744 | } else { |
| 745 | // Not enough space. |
Abodunrinwa Toki | 9ae95df | 2015-06-19 03:04:50 +0100 | [diff] [blame] | 746 | // Position at the top of the view port and open downwards. |
Yohei Yukawa | bafc908 | 2015-07-14 05:59:05 -0700 | [diff] [blame] | 747 | updateOverflowHeight(mViewPortOnScreen.height() - margin); |
| 748 | y = mViewPortOnScreen.top; |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 749 | mOpenOverflowUpwards = false; |
Abodunrinwa Toki | 6d2b75c | 2015-06-12 15:35:51 +0100 | [diff] [blame] | 750 | } |
Abodunrinwa Toki | ffebf68 | 2015-05-12 21:54:08 +0100 | [diff] [blame] | 751 | } |
Abodunrinwa Toki | 6d2b75c | 2015-06-12 15:35:51 +0100 | [diff] [blame] | 752 | |
Yohei Yukawa | 4b26997 | 2015-07-15 19:01:32 -0700 | [diff] [blame] | 753 | // We later specify the location of PopupWindow relative to the attached window. |
| 754 | // The idea here is that 1) we can get the location of a View in both window coordinates |
| 755 | // and screen coordiantes, where the offset between them should be equal to the window |
| 756 | // origin, and 2) we can use an arbitrary for this calculation while calculating the |
| 757 | // location of the rootview is supposed to be least expensive. |
| 758 | // TODO: Consider to use PopupWindow.setLayoutInScreenEnabled(true) so that we can avoid |
| 759 | // the following calculation. |
| 760 | mParent.getRootView().getLocationOnScreen(mTmpCoords); |
| 761 | int rootViewLeftOnScreen = mTmpCoords[0]; |
| 762 | int rootViewTopOnScreen = mTmpCoords[1]; |
| 763 | mParent.getRootView().getLocationInWindow(mTmpCoords); |
| 764 | int rootViewLeftOnWindow = mTmpCoords[0]; |
| 765 | int rootViewTopOnWindow = mTmpCoords[1]; |
| 766 | int windowLeftOnScreen = rootViewLeftOnScreen - rootViewLeftOnWindow; |
| 767 | int windowTopOnScreen = rootViewTopOnScreen - rootViewTopOnWindow; |
Abodunrinwa Toki | 51a8af6 | 2016-04-28 19:59:57 +0100 | [diff] [blame] | 768 | mCoordsOnWindow.set( |
| 769 | Math.max(0, x - windowLeftOnScreen), Math.max(0, y - windowTopOnScreen)); |
Abodunrinwa Toki | ffebf68 | 2015-05-12 21:54:08 +0100 | [diff] [blame] | 770 | } |
| 771 | |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 772 | /** |
Abodunrinwa Toki | 7270d07 | 2015-04-17 20:31:34 +0100 | [diff] [blame] | 773 | * Performs the "show" animation on the floating popup. |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 774 | */ |
Abodunrinwa Toki | 7270d07 | 2015-04-17 20:31:34 +0100 | [diff] [blame] | 775 | private void runShowAnimation() { |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 776 | mShowAnimation.start(); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 777 | } |
| 778 | |
| 779 | /** |
Abodunrinwa Toki | 7270d07 | 2015-04-17 20:31:34 +0100 | [diff] [blame] | 780 | * Performs the "dismiss" animation on the floating popup. |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 781 | */ |
Abodunrinwa Toki | 7270d07 | 2015-04-17 20:31:34 +0100 | [diff] [blame] | 782 | private void runDismissAnimation() { |
| 783 | mDismissAnimation.start(); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 784 | } |
| 785 | |
Abodunrinwa Toki | 7270d07 | 2015-04-17 20:31:34 +0100 | [diff] [blame] | 786 | /** |
| 787 | * Performs the "hide" animation on the floating popup. |
| 788 | */ |
| 789 | private void runHideAnimation() { |
| 790 | mHideAnimation.start(); |
| 791 | } |
| 792 | |
Abodunrinwa Toki | 0ce3e08 | 2015-04-21 20:33:21 +0100 | [diff] [blame] | 793 | private void cancelDismissAndHideAnimations() { |
Abodunrinwa Toki | 7270d07 | 2015-04-17 20:31:34 +0100 | [diff] [blame] | 794 | mDismissAnimation.cancel(); |
| 795 | mHideAnimation.cancel(); |
Abodunrinwa Toki | 0ce3e08 | 2015-04-21 20:33:21 +0100 | [diff] [blame] | 796 | } |
| 797 | |
| 798 | private void cancelOverflowAnimations() { |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 799 | mContentContainer.clearAnimation(); |
| 800 | mMainPanel.animate().cancel(); |
| 801 | mOverflowPanel.animate().cancel(); |
| 802 | mToArrow.stop(); |
| 803 | mToOverflow.stop(); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 804 | } |
| 805 | |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 806 | private void openOverflow() { |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 807 | final int targetWidth = mOverflowPanelSize.getWidth(); |
| 808 | final int targetHeight = mOverflowPanelSize.getHeight(); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 809 | final int startWidth = mContentContainer.getWidth(); |
| 810 | final int startHeight = mContentContainer.getHeight(); |
| 811 | final float startY = mContentContainer.getY(); |
Abodunrinwa Toki | 6c5ac8e | 2015-06-01 17:35:34 +0100 | [diff] [blame] | 812 | final float left = mContentContainer.getX(); |
| 813 | final float right = left + mContentContainer.getWidth(); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 814 | Animation widthAnimation = new Animation() { |
| 815 | @Override |
| 816 | protected void applyTransformation(float interpolatedTime, Transformation t) { |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 817 | int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth)); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 818 | setWidth(mContentContainer, startWidth + deltaWidth); |
Abodunrinwa Toki | 49482f8 | 2016-07-01 19:57:50 +0100 | [diff] [blame] | 819 | if (isInRTLMode()) { |
Abodunrinwa Toki | 6c5ac8e | 2015-06-01 17:35:34 +0100 | [diff] [blame] | 820 | mContentContainer.setX(left); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 821 | |
| 822 | // Lock the panels in place. |
| 823 | mMainPanel.setX(0); |
| 824 | mOverflowPanel.setX(0); |
Abodunrinwa Toki | 6c5ac8e | 2015-06-01 17:35:34 +0100 | [diff] [blame] | 825 | } else { |
| 826 | mContentContainer.setX(right - mContentContainer.getWidth()); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 827 | |
| 828 | // Offset the panels' positions so they look like they're locked in place |
| 829 | // on the screen. |
| 830 | mMainPanel.setX(mContentContainer.getWidth() - startWidth); |
| 831 | mOverflowPanel.setX(mContentContainer.getWidth() - targetWidth); |
Abodunrinwa Toki | 6c5ac8e | 2015-06-01 17:35:34 +0100 | [diff] [blame] | 832 | } |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 833 | } |
| 834 | }; |
| 835 | Animation heightAnimation = new Animation() { |
| 836 | @Override |
| 837 | protected void applyTransformation(float interpolatedTime, Transformation t) { |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 838 | int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight)); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 839 | setHeight(mContentContainer, startHeight + deltaHeight); |
| 840 | if (mOpenOverflowUpwards) { |
| 841 | mContentContainer.setY( |
| 842 | startY - (mContentContainer.getHeight() - startHeight)); |
| 843 | positionContentYCoordinatesIfOpeningOverflowUpwards(); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 844 | } |
| 845 | } |
| 846 | }; |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 847 | final float overflowButtonStartX = mOverflowButton.getX(); |
Abodunrinwa Toki | 49482f8 | 2016-07-01 19:57:50 +0100 | [diff] [blame] | 848 | final float overflowButtonTargetX = isInRTLMode() ? |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 849 | overflowButtonStartX + targetWidth - mOverflowButton.getWidth() : |
| 850 | overflowButtonStartX - targetWidth + mOverflowButton.getWidth(); |
| 851 | Animation overflowButtonAnimation = new Animation() { |
| 852 | @Override |
| 853 | protected void applyTransformation(float interpolatedTime, Transformation t) { |
| 854 | float overflowButtonX = overflowButtonStartX |
| 855 | + interpolatedTime * (overflowButtonTargetX - overflowButtonStartX); |
Abodunrinwa Toki | 49482f8 | 2016-07-01 19:57:50 +0100 | [diff] [blame] | 856 | float deltaContainerWidth = isInRTLMode() ? |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 857 | 0 : |
| 858 | mContentContainer.getWidth() - startWidth; |
| 859 | float actualOverflowButtonX = overflowButtonX + deltaContainerWidth; |
| 860 | mOverflowButton.setX(actualOverflowButtonX); |
| 861 | } |
| 862 | }; |
| 863 | widthAnimation.setInterpolator(mLogAccelerateInterpolator); |
| 864 | widthAnimation.setDuration(getAdjustedDuration(250)); |
| 865 | heightAnimation.setInterpolator(mFastOutSlowInInterpolator); |
| 866 | heightAnimation.setDuration(getAdjustedDuration(250)); |
| 867 | overflowButtonAnimation.setInterpolator(mFastOutSlowInInterpolator); |
| 868 | overflowButtonAnimation.setDuration(getAdjustedDuration(250)); |
Abodunrinwa Toki | b904437 | 2015-04-19 18:55:42 +0100 | [diff] [blame] | 869 | mOpenOverflowAnimation.getAnimations().clear(); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 870 | mOpenOverflowAnimation.getAnimations().clear(); |
Abodunrinwa Toki | b904437 | 2015-04-19 18:55:42 +0100 | [diff] [blame] | 871 | mOpenOverflowAnimation.addAnimation(widthAnimation); |
| 872 | mOpenOverflowAnimation.addAnimation(heightAnimation); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 873 | mOpenOverflowAnimation.addAnimation(overflowButtonAnimation); |
Abodunrinwa Toki | b904437 | 2015-04-19 18:55:42 +0100 | [diff] [blame] | 874 | mContentContainer.startAnimation(mOpenOverflowAnimation); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 875 | mIsOverflowOpen = true; |
| 876 | mMainPanel.animate() |
| 877 | .alpha(0).withLayer() |
| 878 | .setInterpolator(mLinearOutSlowInInterpolator) |
| 879 | .setDuration(250) |
| 880 | .start(); |
| 881 | mOverflowPanel.setAlpha(1); // fadeIn in 0ms. |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 882 | } |
| 883 | |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 884 | private void closeOverflow() { |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 885 | final int targetWidth = mMainPanelSize.getWidth(); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 886 | final int startWidth = mContentContainer.getWidth(); |
Abodunrinwa Toki | 6c5ac8e | 2015-06-01 17:35:34 +0100 | [diff] [blame] | 887 | final float left = mContentContainer.getX(); |
| 888 | final float right = left + mContentContainer.getWidth(); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 889 | Animation widthAnimation = new Animation() { |
| 890 | @Override |
| 891 | protected void applyTransformation(float interpolatedTime, Transformation t) { |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 892 | int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth)); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 893 | setWidth(mContentContainer, startWidth + deltaWidth); |
Abodunrinwa Toki | 49482f8 | 2016-07-01 19:57:50 +0100 | [diff] [blame] | 894 | if (isInRTLMode()) { |
Abodunrinwa Toki | 6c5ac8e | 2015-06-01 17:35:34 +0100 | [diff] [blame] | 895 | mContentContainer.setX(left); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 896 | |
| 897 | // Lock the panels in place. |
| 898 | mMainPanel.setX(0); |
| 899 | mOverflowPanel.setX(0); |
Abodunrinwa Toki | 6c5ac8e | 2015-06-01 17:35:34 +0100 | [diff] [blame] | 900 | } else { |
| 901 | mContentContainer.setX(right - mContentContainer.getWidth()); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 902 | |
| 903 | // Offset the panels' positions so they look like they're locked in place |
| 904 | // on the screen. |
| 905 | mMainPanel.setX(mContentContainer.getWidth() - targetWidth); |
| 906 | mOverflowPanel.setX(mContentContainer.getWidth() - startWidth); |
Abodunrinwa Toki | 6c5ac8e | 2015-06-01 17:35:34 +0100 | [diff] [blame] | 907 | } |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 908 | } |
| 909 | }; |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 910 | final int targetHeight = mMainPanelSize.getHeight(); |
| 911 | final int startHeight = mContentContainer.getHeight(); |
| 912 | final float bottom = mContentContainer.getY() + mContentContainer.getHeight(); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 913 | Animation heightAnimation = new Animation() { |
| 914 | @Override |
| 915 | protected void applyTransformation(float interpolatedTime, Transformation t) { |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 916 | int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight)); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 917 | setHeight(mContentContainer, startHeight + deltaHeight); |
| 918 | if (mOpenOverflowUpwards) { |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 919 | mContentContainer.setY(bottom - mContentContainer.getHeight()); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 920 | positionContentYCoordinatesIfOpeningOverflowUpwards(); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 921 | } |
| 922 | } |
| 923 | }; |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 924 | final float overflowButtonStartX = mOverflowButton.getX(); |
Abodunrinwa Toki | 49482f8 | 2016-07-01 19:57:50 +0100 | [diff] [blame] | 925 | final float overflowButtonTargetX = isInRTLMode() ? |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 926 | overflowButtonStartX - startWidth + mOverflowButton.getWidth() : |
| 927 | overflowButtonStartX + startWidth - mOverflowButton.getWidth(); |
| 928 | Animation overflowButtonAnimation = new Animation() { |
| 929 | @Override |
| 930 | protected void applyTransformation(float interpolatedTime, Transformation t) { |
| 931 | float overflowButtonX = overflowButtonStartX |
| 932 | + interpolatedTime * (overflowButtonTargetX - overflowButtonStartX); |
Abodunrinwa Toki | 49482f8 | 2016-07-01 19:57:50 +0100 | [diff] [blame] | 933 | float deltaContainerWidth = isInRTLMode() ? |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 934 | 0 : |
| 935 | mContentContainer.getWidth() - startWidth; |
| 936 | float actualOverflowButtonX = overflowButtonX + deltaContainerWidth; |
| 937 | mOverflowButton.setX(actualOverflowButtonX); |
| 938 | } |
| 939 | }; |
| 940 | widthAnimation.setInterpolator(mFastOutSlowInInterpolator); |
| 941 | widthAnimation.setDuration(getAdjustedDuration(250)); |
| 942 | heightAnimation.setInterpolator(mLogAccelerateInterpolator); |
| 943 | heightAnimation.setDuration(getAdjustedDuration(250)); |
| 944 | overflowButtonAnimation.setInterpolator(mFastOutSlowInInterpolator); |
| 945 | overflowButtonAnimation.setDuration(getAdjustedDuration(250)); |
Abodunrinwa Toki | b904437 | 2015-04-19 18:55:42 +0100 | [diff] [blame] | 946 | mCloseOverflowAnimation.getAnimations().clear(); |
Abodunrinwa Toki | b904437 | 2015-04-19 18:55:42 +0100 | [diff] [blame] | 947 | mCloseOverflowAnimation.addAnimation(widthAnimation); |
| 948 | mCloseOverflowAnimation.addAnimation(heightAnimation); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 949 | mCloseOverflowAnimation.addAnimation(overflowButtonAnimation); |
Abodunrinwa Toki | b904437 | 2015-04-19 18:55:42 +0100 | [diff] [blame] | 950 | mContentContainer.startAnimation(mCloseOverflowAnimation); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 951 | mIsOverflowOpen = false; |
| 952 | mMainPanel.animate() |
| 953 | .alpha(1).withLayer() |
| 954 | .setInterpolator(mFastOutLinearInInterpolator) |
| 955 | .setDuration(100) |
| 956 | .start(); |
| 957 | mOverflowPanel.animate() |
| 958 | .alpha(0).withLayer() |
| 959 | .setInterpolator(mLinearOutSlowInInterpolator) |
| 960 | .setDuration(150) |
| 961 | .start(); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 962 | } |
| 963 | |
Abodunrinwa Toki | f0f4d73 | 2016-03-23 15:56:43 +0000 | [diff] [blame] | 964 | /** |
| 965 | * Defines the position of the floating toolbar popup panels when transition animation has |
| 966 | * stopped. |
| 967 | */ |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 968 | private void setPanelsStatesAtRestingPosition() { |
| 969 | mOverflowButton.setEnabled(true); |
Abodunrinwa Toki | cc9d617 | 2016-02-03 18:32:42 +0000 | [diff] [blame] | 970 | mOverflowPanel.awakenScrollBars(); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 971 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 972 | if (mIsOverflowOpen) { |
| 973 | // Set open state. |
| 974 | final Size containerSize = mOverflowPanelSize; |
| 975 | setSize(mContentContainer, containerSize); |
| 976 | mMainPanel.setAlpha(0); |
Abodunrinwa Toki | 0cf6489 | 2016-03-17 15:29:36 +0000 | [diff] [blame] | 977 | mMainPanel.setVisibility(View.INVISIBLE); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 978 | mOverflowPanel.setAlpha(1); |
Abodunrinwa Toki | 0cf6489 | 2016-03-17 15:29:36 +0000 | [diff] [blame] | 979 | mOverflowPanel.setVisibility(View.VISIBLE); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 980 | mOverflowButton.setImageDrawable(mArrow); |
Phil Weaver | 1d4e183 | 2016-05-16 09:41:14 -0700 | [diff] [blame] | 981 | mOverflowButton.setContentDescription(mContext.getString( |
| 982 | R.string.floating_toolbar_close_overflow_description)); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 983 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 984 | // Update x-coordinates depending on RTL state. |
Abodunrinwa Toki | 49482f8 | 2016-07-01 19:57:50 +0100 | [diff] [blame] | 985 | if (isInRTLMode()) { |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 986 | mContentContainer.setX(mMarginHorizontal); // align left |
| 987 | mMainPanel.setX(0); // align left |
| 988 | mOverflowButton.setX( // align right |
| 989 | containerSize.getWidth() - mOverflowButtonSize.getWidth()); |
| 990 | mOverflowPanel.setX(0); // align left |
| 991 | } else { |
| 992 | mContentContainer.setX( // align right |
Abodunrinwa Toki | 0780f3a | 2016-05-03 16:25:41 +0100 | [diff] [blame] | 993 | mPopupWindow.getWidth() - |
| 994 | containerSize.getWidth() - mMarginHorizontal); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 995 | mMainPanel.setX(-mContentContainer.getX()); // align right |
| 996 | mOverflowButton.setX(0); // align left |
| 997 | mOverflowPanel.setX(0); // align left |
| 998 | } |
Abodunrinwa Toki | ffebf68 | 2015-05-12 21:54:08 +0100 | [diff] [blame] | 999 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1000 | // Update y-coordinates depending on overflow's open direction. |
| 1001 | if (mOpenOverflowUpwards) { |
| 1002 | mContentContainer.setY(mMarginVertical); // align top |
| 1003 | mMainPanel.setY( // align bottom |
| 1004 | containerSize.getHeight() - mContentContainer.getHeight()); |
| 1005 | mOverflowButton.setY( // align bottom |
| 1006 | containerSize.getHeight() - mOverflowButtonSize.getHeight()); |
| 1007 | mOverflowPanel.setY(0); // align top |
| 1008 | } else { |
| 1009 | // opens downwards. |
| 1010 | mContentContainer.setY(mMarginVertical); // align top |
| 1011 | mMainPanel.setY(0); // align top |
| 1012 | mOverflowButton.setY(0); // align top |
| 1013 | mOverflowPanel.setY(mOverflowButtonSize.getHeight()); // align bottom |
| 1014 | } |
Abodunrinwa Toki | 6cb5cc1 | 2015-06-03 11:28:12 +0100 | [diff] [blame] | 1015 | } else { |
Abodunrinwa Toki | 6be228c | 2016-01-27 22:11:30 +0000 | [diff] [blame] | 1016 | // Overflow not open. Set closed state. |
| 1017 | final Size containerSize = mMainPanelSize; |
| 1018 | setSize(mContentContainer, containerSize); |
| 1019 | mMainPanel.setAlpha(1); |
Abodunrinwa Toki | 0cf6489 | 2016-03-17 15:29:36 +0000 | [diff] [blame] | 1020 | mMainPanel.setVisibility(View.VISIBLE); |
Abodunrinwa Toki | 6be228c | 2016-01-27 22:11:30 +0000 | [diff] [blame] | 1021 | mOverflowPanel.setAlpha(0); |
Abodunrinwa Toki | 0cf6489 | 2016-03-17 15:29:36 +0000 | [diff] [blame] | 1022 | mOverflowPanel.setVisibility(View.INVISIBLE); |
Abodunrinwa Toki | 6be228c | 2016-01-27 22:11:30 +0000 | [diff] [blame] | 1023 | mOverflowButton.setImageDrawable(mOverflow); |
Phil Weaver | 1d4e183 | 2016-05-16 09:41:14 -0700 | [diff] [blame] | 1024 | mOverflowButton.setContentDescription(mContext.getString( |
| 1025 | R.string.floating_toolbar_open_overflow_description)); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1026 | |
Abodunrinwa Toki | 6be228c | 2016-01-27 22:11:30 +0000 | [diff] [blame] | 1027 | if (hasOverflow()) { |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1028 | // Update x-coordinates depending on RTL state. |
Abodunrinwa Toki | 49482f8 | 2016-07-01 19:57:50 +0100 | [diff] [blame] | 1029 | if (isInRTLMode()) { |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1030 | mContentContainer.setX(mMarginHorizontal); // align left |
| 1031 | mMainPanel.setX(0); // align left |
| 1032 | mOverflowButton.setX(0); // align left |
| 1033 | mOverflowPanel.setX(0); // align left |
| 1034 | } else { |
Abodunrinwa Toki | 0780f3a | 2016-05-03 16:25:41 +0100 | [diff] [blame] | 1035 | mContentContainer.setX( // align right |
| 1036 | mPopupWindow.getWidth() - |
| 1037 | containerSize.getWidth() - mMarginHorizontal); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1038 | mMainPanel.setX(0); // align left |
| 1039 | mOverflowButton.setX( // align right |
| 1040 | containerSize.getWidth() - mOverflowButtonSize.getWidth()); |
| 1041 | mOverflowPanel.setX( // align right |
| 1042 | containerSize.getWidth() - mOverflowPanelSize.getWidth()); |
| 1043 | } |
| 1044 | |
| 1045 | // Update y-coordinates depending on overflow's open direction. |
| 1046 | if (mOpenOverflowUpwards) { |
| 1047 | mContentContainer.setY( // align bottom |
| 1048 | mMarginVertical + |
| 1049 | mOverflowPanelSize.getHeight() - containerSize.getHeight()); |
| 1050 | mMainPanel.setY(0); // align top |
| 1051 | mOverflowButton.setY(0); // align top |
| 1052 | mOverflowPanel.setY( // align bottom |
| 1053 | containerSize.getHeight() - mOverflowPanelSize.getHeight()); |
| 1054 | } else { |
| 1055 | // opens downwards. |
| 1056 | mContentContainer.setY(mMarginVertical); // align top |
| 1057 | mMainPanel.setY(0); // align top |
| 1058 | mOverflowButton.setY(0); // align top |
| 1059 | mOverflowPanel.setY(mOverflowButtonSize.getHeight()); // align bottom |
| 1060 | } |
| 1061 | } else { |
Abodunrinwa Toki | 6be228c | 2016-01-27 22:11:30 +0000 | [diff] [blame] | 1062 | // No overflow. |
| 1063 | mContentContainer.setX(mMarginHorizontal); // align left |
| 1064 | mContentContainer.setY(mMarginVertical); // align top |
| 1065 | mMainPanel.setX(0); // align left |
| 1066 | mMainPanel.setY(0); // align top |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1067 | } |
Abodunrinwa Toki | 6cb5cc1 | 2015-06-03 11:28:12 +0100 | [diff] [blame] | 1068 | } |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1069 | } |
| 1070 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1071 | private void updateOverflowHeight(int suggestedHeight) { |
| 1072 | if (hasOverflow()) { |
| 1073 | final int maxItemSize = (suggestedHeight - mOverflowButtonSize.getHeight()) / |
Abodunrinwa Toki | 852e9ac | 2017-02-22 00:20:15 +0000 | [diff] [blame] | 1074 | mLineHeight; |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1075 | final int newHeight = calculateOverflowHeight(maxItemSize); |
| 1076 | if (mOverflowPanelSize.getHeight() != newHeight) { |
| 1077 | mOverflowPanelSize = new Size(mOverflowPanelSize.getWidth(), newHeight); |
| 1078 | } |
| 1079 | setSize(mOverflowPanel, mOverflowPanelSize); |
| 1080 | if (mIsOverflowOpen) { |
| 1081 | setSize(mContentContainer, mOverflowPanelSize); |
| 1082 | if (mOpenOverflowUpwards) { |
| 1083 | final int deltaHeight = mOverflowPanelSize.getHeight() - newHeight; |
| 1084 | mContentContainer.setY(mContentContainer.getY() + deltaHeight); |
| 1085 | mOverflowButton.setY(mOverflowButton.getY() - deltaHeight); |
| 1086 | } |
| 1087 | } else { |
| 1088 | setSize(mContentContainer, mMainPanelSize); |
| 1089 | } |
Abodunrinwa Toki | ffebf68 | 2015-05-12 21:54:08 +0100 | [diff] [blame] | 1090 | updatePopupSize(); |
Abodunrinwa Toki | ffebf68 | 2015-05-12 21:54:08 +0100 | [diff] [blame] | 1091 | } |
| 1092 | } |
| 1093 | |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1094 | private void updatePopupSize() { |
| 1095 | int width = 0; |
| 1096 | int height = 0; |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1097 | if (mMainPanelSize != null) { |
| 1098 | width = Math.max(width, mMainPanelSize.getWidth()); |
| 1099 | height = Math.max(height, mMainPanelSize.getHeight()); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1100 | } |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1101 | if (mOverflowPanelSize != null) { |
| 1102 | width = Math.max(width, mOverflowPanelSize.getWidth()); |
| 1103 | height = Math.max(height, mOverflowPanelSize.getHeight()); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1104 | } |
Abodunrinwa Toki | f8e14fd | 2015-04-14 18:59:40 +0100 | [diff] [blame] | 1105 | mPopupWindow.setWidth(width + mMarginHorizontal * 2); |
| 1106 | mPopupWindow.setHeight(height + mMarginVertical * 2); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1107 | maybeComputeTransitionDurationScale(); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1108 | } |
| 1109 | |
Abodunrinwa Toki | e3eb183 | 2015-05-27 20:31:01 +0100 | [diff] [blame] | 1110 | private void refreshViewPort() { |
Yohei Yukawa | bafc908 | 2015-07-14 05:59:05 -0700 | [diff] [blame] | 1111 | mParent.getWindowVisibleDisplayFrame(mViewPortOnScreen); |
Abodunrinwa Toki | e3eb183 | 2015-05-27 20:31:01 +0100 | [diff] [blame] | 1112 | } |
| 1113 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1114 | private int getAdjustedToolbarWidth(int suggestedWidth) { |
Abodunrinwa Toki | e3eb183 | 2015-05-27 20:31:01 +0100 | [diff] [blame] | 1115 | int width = suggestedWidth; |
| 1116 | refreshViewPort(); |
Yohei Yukawa | bafc908 | 2015-07-14 05:59:05 -0700 | [diff] [blame] | 1117 | int maximumWidth = mViewPortOnScreen.width() - 2 * mParent.getResources() |
Abodunrinwa Toki | e3eb183 | 2015-05-27 20:31:01 +0100 | [diff] [blame] | 1118 | .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin); |
| 1119 | if (width <= 0) { |
| 1120 | width = mParent.getResources() |
| 1121 | .getDimensionPixelSize(R.dimen.floating_toolbar_preferred_width); |
| 1122 | } |
| 1123 | return Math.min(width, maximumWidth); |
| 1124 | } |
| 1125 | |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1126 | /** |
| 1127 | * Sets the touchable region of this popup to be zero. This means that all touch events on |
| 1128 | * this popup will go through to the surface behind it. |
| 1129 | */ |
| 1130 | private void setZeroTouchableSurface() { |
| 1131 | mTouchableRegion.setEmpty(); |
| 1132 | } |
| 1133 | |
| 1134 | /** |
| 1135 | * Sets the touchable region of this popup to be the area occupied by its content. |
| 1136 | */ |
| 1137 | private void setContentAreaAsTouchableSurface() { |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1138 | Preconditions.checkNotNull(mMainPanelSize); |
| 1139 | final int width; |
| 1140 | final int height; |
| 1141 | if (mIsOverflowOpen) { |
| 1142 | Preconditions.checkNotNull(mOverflowPanelSize); |
| 1143 | width = mOverflowPanelSize.getWidth(); |
| 1144 | height = mOverflowPanelSize.getHeight(); |
| 1145 | } else { |
| 1146 | width = mMainPanelSize.getWidth(); |
| 1147 | height = mMainPanelSize.getHeight(); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1148 | } |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1149 | mTouchableRegion.set( |
| 1150 | (int) mContentContainer.getX(), |
| 1151 | (int) mContentContainer.getY(), |
| 1152 | (int) mContentContainer.getX() + width, |
| 1153 | (int) mContentContainer.getY() + height); |
| 1154 | } |
Abodunrinwa Toki | d5358ff | 2015-04-22 07:08:29 +0100 | [diff] [blame] | 1155 | |
| 1156 | /** |
| 1157 | * Make the touchable area of this popup be the area specified by mTouchableRegion. |
| 1158 | * This should be called after the popup window has been dismissed (dismiss/hide) |
| 1159 | * and is probably being re-shown with a new content root view. |
| 1160 | */ |
| 1161 | private void setTouchableSurfaceInsetsComputer() { |
| 1162 | ViewTreeObserver viewTreeObserver = mPopupWindow.getContentView() |
| 1163 | .getRootView() |
| 1164 | .getViewTreeObserver(); |
| 1165 | viewTreeObserver.removeOnComputeInternalInsetsListener(mInsetsComputer); |
| 1166 | viewTreeObserver.addOnComputeInternalInsetsListener(mInsetsComputer); |
| 1167 | } |
Abodunrinwa Toki | 6cb5cc1 | 2015-06-03 11:28:12 +0100 | [diff] [blame] | 1168 | |
Abodunrinwa Toki | 49482f8 | 2016-07-01 19:57:50 +0100 | [diff] [blame] | 1169 | private boolean isInRTLMode() { |
| 1170 | return mContext.getApplicationInfo().hasRtlSupport() |
| 1171 | && mContext.getResources().getConfiguration().getLayoutDirection() |
| 1172 | == View.LAYOUT_DIRECTION_RTL; |
Abodunrinwa Toki | 6cb5cc1 | 2015-06-03 11:28:12 +0100 | [diff] [blame] | 1173 | } |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 1174 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1175 | private boolean hasOverflow() { |
| 1176 | return mOverflowPanelSize != null; |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1177 | } |
| 1178 | |
| 1179 | /** |
| 1180 | * Fits as many menu items in the main panel and returns a list of the menu items that |
| 1181 | * were not fit in. |
| 1182 | * |
| 1183 | * @return The menu items that are not included in this main panel. |
| 1184 | */ |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1185 | public List<MenuItem> layoutMainPanelItems( |
| 1186 | List<MenuItem> menuItems, final int toolbarWidth) { |
Abodunrinwa Toki | ffebf68 | 2015-05-12 21:54:08 +0100 | [diff] [blame] | 1187 | Preconditions.checkNotNull(menuItems); |
| 1188 | |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1189 | int availableWidth = toolbarWidth; |
Siyamed Sinir | 484c2e2 | 2017-06-07 16:26:19 -0700 | [diff] [blame] | 1190 | |
| 1191 | final LinkedList<MenuItem> remainingMenuItems = new LinkedList<>(); |
| 1192 | // add the overflow menu items to the end of the remainingMenuItems list. |
| 1193 | final LinkedList<MenuItem> overflowMenuItems = new LinkedList(); |
| 1194 | for (MenuItem menuItem : menuItems) { |
Abodunrinwa Toki | 9c881f2 | 2017-10-16 21:05:41 +0100 | [diff] [blame] | 1195 | if (menuItem.getItemId() != android.R.id.textAssist |
| 1196 | && menuItem.requiresOverflow()) { |
Siyamed Sinir | 484c2e2 | 2017-06-07 16:26:19 -0700 | [diff] [blame] | 1197 | overflowMenuItems.add(menuItem); |
| 1198 | } else { |
| 1199 | remainingMenuItems.add(menuItem); |
| 1200 | } |
| 1201 | } |
| 1202 | remainingMenuItems.addAll(overflowMenuItems); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1203 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1204 | mMainPanel.removeAllViews(); |
Abodunrinwa Toki | 6be228c | 2016-01-27 22:11:30 +0000 | [diff] [blame] | 1205 | mMainPanel.setPaddingRelative(0, 0, 0, 0); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1206 | |
Abodunrinwa Toki | 5fedfb8 | 2017-02-06 19:34:00 +0000 | [diff] [blame] | 1207 | int lastGroupId = -1; |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1208 | boolean isFirstItem = true; |
| 1209 | while (!remainingMenuItems.isEmpty()) { |
| 1210 | final MenuItem menuItem = remainingMenuItems.peek(); |
Siyamed Sinir | 484c2e2 | 2017-06-07 16:26:19 -0700 | [diff] [blame] | 1211 | |
| 1212 | // if this is the first item, regardless of requiresOverflow(), it should be |
| 1213 | // displayed on the main panel. Otherwise all items including this one will be |
| 1214 | // overflow items, and should be displayed in overflow panel. |
| 1215 | if(!isFirstItem && menuItem.requiresOverflow()) { |
| 1216 | break; |
| 1217 | } |
| 1218 | |
Abodunrinwa Toki | 9c881f2 | 2017-10-16 21:05:41 +0100 | [diff] [blame] | 1219 | final boolean showIcon = isFirstItem && menuItem.getItemId() == R.id.textAssist; |
| 1220 | final View menuItemButton = createMenuItemButton( |
| 1221 | mContext, menuItem, mIconTextSpacing, showIcon); |
Mihai Popa | 66d41f8 | 2018-04-17 16:21:10 +0100 | [diff] [blame] | 1222 | if (!showIcon && menuItemButton instanceof LinearLayout) { |
| 1223 | ((LinearLayout) menuItemButton).setGravity(Gravity.CENTER); |
| 1224 | } |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1225 | |
Abodunrinwa Toki | f8e14fd | 2015-04-14 18:59:40 +0100 | [diff] [blame] | 1226 | // Adding additional start padding for the first button to even out button spacing. |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1227 | if (isFirstItem) { |
Abodunrinwa Toki | f8e14fd | 2015-04-14 18:59:40 +0100 | [diff] [blame] | 1228 | menuItemButton.setPaddingRelative( |
| 1229 | (int) (1.5 * menuItemButton.getPaddingStart()), |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1230 | menuItemButton.getPaddingTop(), |
Abodunrinwa Toki | f8e14fd | 2015-04-14 18:59:40 +0100 | [diff] [blame] | 1231 | menuItemButton.getPaddingEnd(), |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1232 | menuItemButton.getPaddingBottom()); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1233 | } |
| 1234 | |
Abodunrinwa Toki | f8e14fd | 2015-04-14 18:59:40 +0100 | [diff] [blame] | 1235 | // Adding additional end padding for the last button to even out button spacing. |
Abodunrinwa Toki | 5fedfb8 | 2017-02-06 19:34:00 +0000 | [diff] [blame] | 1236 | boolean isLastItem = remainingMenuItems.size() == 1; |
| 1237 | if (isLastItem) { |
Abodunrinwa Toki | f8e14fd | 2015-04-14 18:59:40 +0100 | [diff] [blame] | 1238 | menuItemButton.setPaddingRelative( |
| 1239 | menuItemButton.getPaddingStart(), |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1240 | menuItemButton.getPaddingTop(), |
Abodunrinwa Toki | f8e14fd | 2015-04-14 18:59:40 +0100 | [diff] [blame] | 1241 | (int) (1.5 * menuItemButton.getPaddingEnd()), |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1242 | menuItemButton.getPaddingBottom()); |
| 1243 | } |
| 1244 | |
| 1245 | menuItemButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); |
Abodunrinwa Toki | 9c881f2 | 2017-10-16 21:05:41 +0100 | [diff] [blame] | 1246 | final int menuItemButtonWidth = Math.min( |
| 1247 | menuItemButton.getMeasuredWidth(), toolbarWidth); |
Abodunrinwa Toki | 5fedfb8 | 2017-02-06 19:34:00 +0000 | [diff] [blame] | 1248 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1249 | // Check if we can fit an item while reserving space for the overflowButton. |
Abodunrinwa Toki | 9c881f2 | 2017-10-16 21:05:41 +0100 | [diff] [blame] | 1250 | final boolean canFitWithOverflow = |
Abodunrinwa Toki | 5fedfb8 | 2017-02-06 19:34:00 +0000 | [diff] [blame] | 1251 | menuItemButtonWidth <= |
Mihai Popa | 66d41f8 | 2018-04-17 16:21:10 +0100 | [diff] [blame] | 1252 | availableWidth - mOverflowButtonSize.getWidth(); |
Abodunrinwa Toki | 9c881f2 | 2017-10-16 21:05:41 +0100 | [diff] [blame] | 1253 | final boolean canFitNoOverflow = |
Mihai Popa | 66d41f8 | 2018-04-17 16:21:10 +0100 | [diff] [blame] | 1254 | isLastItem && menuItemButtonWidth <= availableWidth; |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1255 | if (canFitWithOverflow || canFitNoOverflow) { |
Abodunrinwa Toki | b21562c | 2015-05-20 22:25:16 +0100 | [diff] [blame] | 1256 | setButtonTagAndClickListener(menuItemButton, menuItem); |
Vladislav Kaznacheev | 2a00f98 | 2017-02-23 17:33:51 -0800 | [diff] [blame] | 1257 | // Set tooltips for main panel items, but not overflow items (b/35726766). |
| 1258 | menuItemButton.setTooltipText(menuItem.getTooltipText()); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1259 | mMainPanel.addView(menuItemButton); |
Abodunrinwa Toki | 5fedfb8 | 2017-02-06 19:34:00 +0000 | [diff] [blame] | 1260 | final ViewGroup.LayoutParams params = menuItemButton.getLayoutParams(); |
Mihai Popa | 66d41f8 | 2018-04-17 16:21:10 +0100 | [diff] [blame] | 1261 | params.width = menuItemButtonWidth; |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1262 | menuItemButton.setLayoutParams(params); |
Mihai Popa | 66d41f8 | 2018-04-17 16:21:10 +0100 | [diff] [blame] | 1263 | availableWidth -= menuItemButtonWidth; |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1264 | remainingMenuItems.pop(); |
| 1265 | } else { |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1266 | break; |
| 1267 | } |
Abodunrinwa Toki | 5fedfb8 | 2017-02-06 19:34:00 +0000 | [diff] [blame] | 1268 | lastGroupId = menuItem.getGroupId(); |
| 1269 | isFirstItem = false; |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1270 | } |
Siyamed Sinir | 484c2e2 | 2017-06-07 16:26:19 -0700 | [diff] [blame] | 1271 | |
| 1272 | if (!remainingMenuItems.isEmpty()) { |
| 1273 | // Reserve space for overflowButton. |
| 1274 | mMainPanel.setPaddingRelative(0, 0, mOverflowButtonSize.getWidth(), 0); |
| 1275 | } |
| 1276 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1277 | mMainPanelSize = measure(mMainPanel); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1278 | return remainingMenuItems; |
| 1279 | } |
| 1280 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1281 | private void layoutOverflowPanelItems(List<MenuItem> menuItems) { |
| 1282 | ArrayAdapter<MenuItem> overflowPanelAdapter = |
| 1283 | (ArrayAdapter<MenuItem>) mOverflowPanel.getAdapter(); |
| 1284 | overflowPanelAdapter.clear(); |
| 1285 | final int size = menuItems.size(); |
| 1286 | for (int i = 0; i < size; i++) { |
| 1287 | overflowPanelAdapter.add(menuItems.get(i)); |
| 1288 | } |
| 1289 | mOverflowPanel.setAdapter(overflowPanelAdapter); |
| 1290 | if (mOpenOverflowUpwards) { |
| 1291 | mOverflowPanel.setY(0); |
| 1292 | } else { |
| 1293 | mOverflowPanel.setY(mOverflowButtonSize.getHeight()); |
| 1294 | } |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1295 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1296 | int width = Math.max(getOverflowWidth(), mOverflowButtonSize.getWidth()); |
| 1297 | int height = calculateOverflowHeight(MAX_OVERFLOW_SIZE); |
| 1298 | mOverflowPanelSize = new Size(width, height); |
| 1299 | setSize(mOverflowPanel, mOverflowPanelSize); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1300 | } |
| 1301 | |
| 1302 | /** |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1303 | * Resets the content container and appropriately position it's panels. |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1304 | */ |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1305 | private void preparePopupContent() { |
| 1306 | mContentContainer.removeAllViews(); |
| 1307 | |
| 1308 | // Add views in the specified order so they stack up as expected. |
| 1309 | // Order: overflowPanel, mainPanel, overflowButton. |
| 1310 | if (hasOverflow()) { |
| 1311 | mContentContainer.addView(mOverflowPanel); |
| 1312 | } |
| 1313 | mContentContainer.addView(mMainPanel); |
| 1314 | if (hasOverflow()) { |
| 1315 | mContentContainer.addView(mOverflowButton); |
| 1316 | } |
| 1317 | setPanelsStatesAtRestingPosition(); |
| 1318 | setContentAreaAsTouchableSurface(); |
| 1319 | |
| 1320 | // The positioning of contents in RTL is wrong when the view is first rendered. |
| 1321 | // Hide the view and post a runnable to recalculate positions and render the view. |
| 1322 | // TODO: Investigate why this happens and fix. |
Abodunrinwa Toki | 49482f8 | 2016-07-01 19:57:50 +0100 | [diff] [blame] | 1323 | if (isInRTLMode()) { |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1324 | mContentContainer.setAlpha(0); |
| 1325 | mContentContainer.post(mPreparePopupContentRTLHelper); |
| 1326 | } |
| 1327 | } |
| 1328 | |
| 1329 | /** |
| 1330 | * Clears out the panels and their container. Resets their calculated sizes. |
| 1331 | */ |
| 1332 | private void clearPanels() { |
| 1333 | mOverflowPanelSize = null; |
| 1334 | mMainPanelSize = null; |
| 1335 | mIsOverflowOpen = false; |
| 1336 | mMainPanel.removeAllViews(); |
| 1337 | ArrayAdapter<MenuItem> overflowPanelAdapter = |
| 1338 | (ArrayAdapter<MenuItem>) mOverflowPanel.getAdapter(); |
| 1339 | overflowPanelAdapter.clear(); |
| 1340 | mOverflowPanel.setAdapter(overflowPanelAdapter); |
| 1341 | mContentContainer.removeAllViews(); |
| 1342 | } |
| 1343 | |
| 1344 | private void positionContentYCoordinatesIfOpeningOverflowUpwards() { |
| 1345 | if (mOpenOverflowUpwards) { |
| 1346 | mMainPanel.setY(mContentContainer.getHeight() - mMainPanelSize.getHeight()); |
| 1347 | mOverflowButton.setY(mContentContainer.getHeight() - mOverflowButton.getHeight()); |
| 1348 | mOverflowPanel.setY(mContentContainer.getHeight() - mOverflowPanelSize.getHeight()); |
| 1349 | } |
| 1350 | } |
| 1351 | |
| 1352 | private int getOverflowWidth() { |
| 1353 | int overflowWidth = 0; |
| 1354 | final int count = mOverflowPanel.getAdapter().getCount(); |
| 1355 | for (int i = 0; i < count; i++) { |
| 1356 | MenuItem menuItem = (MenuItem) mOverflowPanel.getAdapter().getItem(i); |
| 1357 | overflowWidth = |
| 1358 | Math.max(mOverflowPanelViewHelper.calculateWidth(menuItem), overflowWidth); |
| 1359 | } |
| 1360 | return overflowWidth; |
| 1361 | } |
| 1362 | |
| 1363 | private int calculateOverflowHeight(int maxItemSize) { |
| 1364 | // Maximum of 4 items, minimum of 2 if the overflow has to scroll. |
| 1365 | int actualSize = Math.min( |
| 1366 | MAX_OVERFLOW_SIZE, |
| 1367 | Math.min( |
| 1368 | Math.max(MIN_OVERFLOW_SIZE, maxItemSize), |
| 1369 | mOverflowPanel.getCount())); |
Abodunrinwa Toki | f0f4d73 | 2016-03-23 15:56:43 +0000 | [diff] [blame] | 1370 | int extension = 0; |
| 1371 | if (actualSize < mOverflowPanel.getCount()) { |
| 1372 | // The overflow will require scrolling to get to all the items. |
| 1373 | // Extend the height so that part of the hidden items is displayed. |
Abodunrinwa Toki | 852e9ac | 2017-02-22 00:20:15 +0000 | [diff] [blame] | 1374 | extension = (int) (mLineHeight * 0.5f); |
Abodunrinwa Toki | f0f4d73 | 2016-03-23 15:56:43 +0000 | [diff] [blame] | 1375 | } |
Abodunrinwa Toki | 852e9ac | 2017-02-22 00:20:15 +0000 | [diff] [blame] | 1376 | return actualSize * mLineHeight |
Abodunrinwa Toki | f0f4d73 | 2016-03-23 15:56:43 +0000 | [diff] [blame] | 1377 | + mOverflowButtonSize.getHeight() |
| 1378 | + extension; |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1379 | } |
Abodunrinwa Toki | b21562c | 2015-05-20 22:25:16 +0100 | [diff] [blame] | 1380 | |
| 1381 | private void setButtonTagAndClickListener(View menuItemButton, MenuItem menuItem) { |
Abodunrinwa Toki | 852e9ac | 2017-02-22 00:20:15 +0000 | [diff] [blame] | 1382 | menuItemButton.setTag(menuItem); |
| 1383 | menuItemButton.setOnClickListener(mMenuItemButtonOnClickListener); |
Abodunrinwa Toki | b21562c | 2015-05-20 22:25:16 +0100 | [diff] [blame] | 1384 | } |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1385 | |
| 1386 | /** |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1387 | * NOTE: Use only in android.view.animation.* animations. Do not use in android.animation.* |
| 1388 | * animations. See comment about this in the code. |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1389 | */ |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1390 | private int getAdjustedDuration(int originalDuration) { |
| 1391 | if (mTransitionDurationScale < 150) { |
| 1392 | // For smaller transition, decrease the time. |
| 1393 | return Math.max(originalDuration - 50, 0); |
| 1394 | } else if (mTransitionDurationScale > 300) { |
| 1395 | // For bigger transition, increase the time. |
| 1396 | return originalDuration + 50; |
| 1397 | } |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1398 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1399 | // Scale the animation duration with getDurationScale(). This allows |
| 1400 | // android.view.animation.* animations to scale just like android.animation.* animations |
| 1401 | // when animator duration scale is adjusted in "Developer Options". |
| 1402 | // For this reason, do not use this method for android.animation.* animations. |
| 1403 | return (int) (originalDuration * ValueAnimator.getDurationScale()); |
| 1404 | } |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1405 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1406 | private void maybeComputeTransitionDurationScale() { |
Abodunrinwa Toki | d1cd7fd | 2016-01-27 22:27:31 +0000 | [diff] [blame] | 1407 | if (mMainPanelSize != null && mOverflowPanelSize != null) { |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1408 | int w = mMainPanelSize.getWidth() - mOverflowPanelSize.getWidth(); |
| 1409 | int h = mOverflowPanelSize.getHeight() - mMainPanelSize.getHeight(); |
| 1410 | mTransitionDurationScale = (int) (Math.sqrt(w * w + h * h) / |
| 1411 | mContentContainer.getContext().getResources().getDisplayMetrics().density); |
| 1412 | } |
| 1413 | } |
| 1414 | |
| 1415 | private ViewGroup createMainPanel() { |
| 1416 | ViewGroup mainPanel = new LinearLayout(mContext) { |
| 1417 | @Override |
| 1418 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| 1419 | if (isOverflowAnimating()) { |
| 1420 | // Update widthMeasureSpec to make sure that this view is not clipped |
| 1421 | // as we offset it's coordinates with respect to it's parent. |
| 1422 | widthMeasureSpec = MeasureSpec.makeMeasureSpec( |
| 1423 | mMainPanelSize.getWidth(), |
| 1424 | MeasureSpec.EXACTLY); |
| 1425 | } |
| 1426 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); |
| 1427 | } |
| 1428 | |
| 1429 | @Override |
| 1430 | public boolean onInterceptTouchEvent(MotionEvent ev) { |
| 1431 | // Intercept the touch event while the overflow is animating. |
| 1432 | return isOverflowAnimating(); |
| 1433 | } |
| 1434 | }; |
| 1435 | return mainPanel; |
| 1436 | } |
| 1437 | |
| 1438 | private ImageButton createOverflowButton() { |
| 1439 | final ImageButton overflowButton = (ImageButton) LayoutInflater.from(mContext) |
| 1440 | .inflate(R.layout.floating_popup_overflow_button, null); |
| 1441 | overflowButton.setImageDrawable(mOverflow); |
Abodunrinwa Toki | 46850fc | 2017-05-22 15:20:18 +0100 | [diff] [blame] | 1442 | overflowButton.setOnClickListener(v -> { |
| 1443 | if (mIsOverflowOpen) { |
| 1444 | overflowButton.setImageDrawable(mToOverflow); |
| 1445 | mToOverflow.start(); |
| 1446 | closeOverflow(); |
| 1447 | } else { |
| 1448 | overflowButton.setImageDrawable(mToArrow); |
| 1449 | mToArrow.start(); |
| 1450 | openOverflow(); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1451 | } |
| 1452 | }); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1453 | return overflowButton; |
| 1454 | } |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1455 | |
Abodunrinwa Toki | cc9d617 | 2016-02-03 18:32:42 +0000 | [diff] [blame] | 1456 | private OverflowPanel createOverflowPanel() { |
| 1457 | final OverflowPanel overflowPanel = new OverflowPanel(this); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1458 | overflowPanel.setLayoutParams(new ViewGroup.LayoutParams( |
| 1459 | ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); |
| 1460 | overflowPanel.setDivider(null); |
| 1461 | overflowPanel.setDividerHeight(0); |
| 1462 | |
| 1463 | final ArrayAdapter adapter = |
| 1464 | new ArrayAdapter<MenuItem>(mContext, 0) { |
| 1465 | @Override |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1466 | public View getView(int position, View convertView, ViewGroup parent) { |
| 1467 | return mOverflowPanelViewHelper.getView( |
| 1468 | getItem(position), mOverflowPanelSize.getWidth(), convertView); |
| 1469 | } |
| 1470 | }; |
| 1471 | overflowPanel.setAdapter(adapter); |
| 1472 | |
Abodunrinwa Toki | 46850fc | 2017-05-22 15:20:18 +0100 | [diff] [blame] | 1473 | overflowPanel.setOnItemClickListener((parent, view, position, id) -> { |
| 1474 | MenuItem menuItem = (MenuItem) overflowPanel.getAdapter().getItem(position); |
| 1475 | if (mOnMenuItemClickListener != null) { |
| 1476 | mOnMenuItemClickListener.onMenuItemClick(menuItem); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1477 | } |
| 1478 | }); |
| 1479 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1480 | return overflowPanel; |
| 1481 | } |
Abodunrinwa Toki | f8e14fd | 2015-04-14 18:59:40 +0100 | [diff] [blame] | 1482 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1483 | private boolean isOverflowAnimating() { |
| 1484 | final boolean overflowOpening = mOpenOverflowAnimation.hasStarted() |
| 1485 | && !mOpenOverflowAnimation.hasEnded(); |
| 1486 | final boolean overflowClosing = mCloseOverflowAnimation.hasStarted() |
| 1487 | && !mCloseOverflowAnimation.hasEnded(); |
| 1488 | return overflowOpening || overflowClosing; |
| 1489 | } |
| 1490 | |
| 1491 | private Animation.AnimationListener createOverflowAnimationListener() { |
| 1492 | Animation.AnimationListener listener = new Animation.AnimationListener() { |
| 1493 | @Override |
| 1494 | public void onAnimationStart(Animation animation) { |
| 1495 | // Disable the overflow button while it's animating. |
| 1496 | // It will be re-enabled when the animation stops. |
| 1497 | mOverflowButton.setEnabled(false); |
Abodunrinwa Toki | 0cf6489 | 2016-03-17 15:29:36 +0000 | [diff] [blame] | 1498 | // Ensure both panels have visibility turned on when the overflow animation |
| 1499 | // starts. |
| 1500 | mMainPanel.setVisibility(View.VISIBLE); |
| 1501 | mOverflowPanel.setVisibility(View.VISIBLE); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1502 | } |
| 1503 | |
| 1504 | @Override |
| 1505 | public void onAnimationEnd(Animation animation) { |
| 1506 | // Posting this because it seems like this is called before the animation |
| 1507 | // actually ends. |
Abodunrinwa Toki | 46850fc | 2017-05-22 15:20:18 +0100 | [diff] [blame] | 1508 | mContentContainer.post(() -> { |
| 1509 | setPanelsStatesAtRestingPosition(); |
| 1510 | setContentAreaAsTouchableSurface(); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1511 | }); |
| 1512 | } |
| 1513 | |
| 1514 | @Override |
| 1515 | public void onAnimationRepeat(Animation animation) { |
| 1516 | } |
| 1517 | }; |
| 1518 | return listener; |
| 1519 | } |
| 1520 | |
| 1521 | private static Size measure(View view) { |
| 1522 | Preconditions.checkState(view.getParent() == null); |
| 1523 | view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); |
| 1524 | return new Size(view.getMeasuredWidth(), view.getMeasuredHeight()); |
| 1525 | } |
| 1526 | |
| 1527 | private static void setSize(View view, int width, int height) { |
| 1528 | view.setMinimumWidth(width); |
| 1529 | view.setMinimumHeight(height); |
| 1530 | ViewGroup.LayoutParams params = view.getLayoutParams(); |
| 1531 | params = (params == null) ? new ViewGroup.LayoutParams(0, 0) : params; |
| 1532 | params.width = width; |
| 1533 | params.height = height; |
| 1534 | view.setLayoutParams(params); |
| 1535 | } |
| 1536 | |
| 1537 | private static void setSize(View view, Size size) { |
| 1538 | setSize(view, size.getWidth(), size.getHeight()); |
| 1539 | } |
| 1540 | |
| 1541 | private static void setWidth(View view, int width) { |
| 1542 | ViewGroup.LayoutParams params = view.getLayoutParams(); |
| 1543 | setSize(view, width, params.height); |
| 1544 | } |
| 1545 | |
| 1546 | private static void setHeight(View view, int height) { |
| 1547 | ViewGroup.LayoutParams params = view.getLayoutParams(); |
| 1548 | setSize(view, params.width, height); |
| 1549 | } |
| 1550 | |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1551 | /** |
Abodunrinwa Toki | cc9d617 | 2016-02-03 18:32:42 +0000 | [diff] [blame] | 1552 | * A custom ListView for the overflow panel. |
| 1553 | */ |
| 1554 | private static final class OverflowPanel extends ListView { |
| 1555 | |
| 1556 | private final FloatingToolbarPopup mPopup; |
| 1557 | |
| 1558 | OverflowPanel(FloatingToolbarPopup popup) { |
| 1559 | super(Preconditions.checkNotNull(popup).mContext); |
| 1560 | this.mPopup = popup; |
Abodunrinwa Toki | f0f4d73 | 2016-03-23 15:56:43 +0000 | [diff] [blame] | 1561 | setScrollBarDefaultDelayBeforeFade(ViewConfiguration.getScrollDefaultDelay() * 3); |
Abodunrinwa Toki | 8949faf | 2016-04-06 14:15:26 +0100 | [diff] [blame] | 1562 | setScrollIndicators(View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM); |
Abodunrinwa Toki | cc9d617 | 2016-02-03 18:32:42 +0000 | [diff] [blame] | 1563 | } |
| 1564 | |
| 1565 | @Override |
| 1566 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| 1567 | // Update heightMeasureSpec to make sure that this view is not clipped |
| 1568 | // as we offset it's coordinates with respect to it's parent. |
| 1569 | int height = mPopup.mOverflowPanelSize.getHeight() |
| 1570 | - mPopup.mOverflowButtonSize.getHeight(); |
| 1571 | heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); |
| 1572 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); |
| 1573 | } |
| 1574 | |
| 1575 | @Override |
| 1576 | public boolean dispatchTouchEvent(MotionEvent ev) { |
| 1577 | if (mPopup.isOverflowAnimating()) { |
| 1578 | // Eat the touch event. |
| 1579 | return true; |
| 1580 | } |
| 1581 | return super.dispatchTouchEvent(ev); |
| 1582 | } |
| 1583 | |
| 1584 | @Override |
| 1585 | protected boolean awakenScrollBars() { |
| 1586 | return super.awakenScrollBars(); |
| 1587 | } |
| 1588 | } |
| 1589 | |
| 1590 | /** |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1591 | * A custom interpolator used for various floating toolbar animations. |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1592 | */ |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1593 | private static final class LogAccelerateInterpolator implements Interpolator { |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1594 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1595 | private static final int BASE = 100; |
| 1596 | private static final float LOGS_SCALE = 1f / computeLog(1, BASE); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1597 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1598 | private static float computeLog(float t, int base) { |
| 1599 | return (float) (1 - Math.pow(base, -t)); |
Abodunrinwa Toki | ffebf68 | 2015-05-12 21:54:08 +0100 | [diff] [blame] | 1600 | } |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1601 | |
| 1602 | @Override |
| 1603 | public float getInterpolation(float t) { |
| 1604 | return 1 - computeLog(1 - t, BASE) * LOGS_SCALE; |
| 1605 | } |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1606 | } |
| 1607 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1608 | /** |
| 1609 | * A helper for generating views for the overflow panel. |
| 1610 | */ |
| 1611 | private static final class OverflowPanelViewHelper { |
| 1612 | |
Abodunrinwa Toki | 852e9ac | 2017-02-22 00:20:15 +0000 | [diff] [blame] | 1613 | private final View mCalculator; |
| 1614 | private final int mIconTextSpacing; |
| 1615 | private final int mSidePadding; |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1616 | |
| 1617 | private final Context mContext; |
| 1618 | |
Abodunrinwa Toki | 49ec542 | 2018-04-25 14:07:17 +0100 | [diff] [blame] | 1619 | public OverflowPanelViewHelper(Context context, int iconTextSpacing) { |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1620 | mContext = Preconditions.checkNotNull(context); |
Abodunrinwa Toki | 49ec542 | 2018-04-25 14:07:17 +0100 | [diff] [blame] | 1621 | mIconTextSpacing = iconTextSpacing; |
Abodunrinwa Toki | 852e9ac | 2017-02-22 00:20:15 +0000 | [diff] [blame] | 1622 | mSidePadding = context.getResources() |
| 1623 | .getDimensionPixelSize(R.dimen.floating_toolbar_overflow_side_padding); |
| 1624 | mCalculator = createMenuButton(null); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1625 | } |
| 1626 | |
| 1627 | public View getView(MenuItem menuItem, int minimumWidth, View convertView) { |
Abodunrinwa Toki | f8e14fd | 2015-04-14 18:59:40 +0100 | [diff] [blame] | 1628 | Preconditions.checkNotNull(menuItem); |
Abodunrinwa Toki | 852e9ac | 2017-02-22 00:20:15 +0000 | [diff] [blame] | 1629 | if (convertView != null) { |
Abodunrinwa Toki | 9c881f2 | 2017-10-16 21:05:41 +0100 | [diff] [blame] | 1630 | updateMenuItemButton( |
| 1631 | convertView, menuItem, mIconTextSpacing, shouldShowIcon(menuItem)); |
Abodunrinwa Toki | 852e9ac | 2017-02-22 00:20:15 +0000 | [diff] [blame] | 1632 | } else { |
| 1633 | convertView = createMenuButton(menuItem); |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1634 | } |
Abodunrinwa Toki | 852e9ac | 2017-02-22 00:20:15 +0000 | [diff] [blame] | 1635 | convertView.setMinimumWidth(minimumWidth); |
| 1636 | return convertView; |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1637 | } |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1638 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1639 | public int calculateWidth(MenuItem menuItem) { |
Abodunrinwa Toki | 9c881f2 | 2017-10-16 21:05:41 +0100 | [diff] [blame] | 1640 | updateMenuItemButton( |
| 1641 | mCalculator, menuItem, mIconTextSpacing, shouldShowIcon(menuItem)); |
Abodunrinwa Toki | 852e9ac | 2017-02-22 00:20:15 +0000 | [diff] [blame] | 1642 | mCalculator.measure( |
| 1643 | View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); |
| 1644 | return mCalculator.getMeasuredWidth(); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1645 | } |
Abodunrinwa Toki | b904437 | 2015-04-19 18:55:42 +0100 | [diff] [blame] | 1646 | |
Abodunrinwa Toki | 852e9ac | 2017-02-22 00:20:15 +0000 | [diff] [blame] | 1647 | private View createMenuButton(MenuItem menuItem) { |
Abodunrinwa Toki | 9c881f2 | 2017-10-16 21:05:41 +0100 | [diff] [blame] | 1648 | View button = createMenuItemButton( |
| 1649 | mContext, menuItem, mIconTextSpacing, shouldShowIcon(menuItem)); |
Abodunrinwa Toki | 852e9ac | 2017-02-22 00:20:15 +0000 | [diff] [blame] | 1650 | button.setPadding(mSidePadding, 0, mSidePadding, 0); |
| 1651 | return button; |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1652 | } |
Abodunrinwa Toki | 9c881f2 | 2017-10-16 21:05:41 +0100 | [diff] [blame] | 1653 | |
| 1654 | private boolean shouldShowIcon(MenuItem menuItem) { |
| 1655 | if (menuItem != null) { |
| 1656 | return menuItem.getGroupId() == android.R.id.textAssist; |
| 1657 | } |
| 1658 | return false; |
| 1659 | } |
Abodunrinwa Toki | b904437 | 2015-04-19 18:55:42 +0100 | [diff] [blame] | 1660 | } |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1661 | } |
| 1662 | |
Abodunrinwa Toki | b21562c | 2015-05-20 22:25:16 +0100 | [diff] [blame] | 1663 | /** |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 1664 | * Creates and returns a menu button for the specified menu item. |
| 1665 | */ |
Abodunrinwa Toki | 852e9ac | 2017-02-22 00:20:15 +0000 | [diff] [blame] | 1666 | private static View createMenuItemButton( |
Abodunrinwa Toki | 9c881f2 | 2017-10-16 21:05:41 +0100 | [diff] [blame] | 1667 | Context context, MenuItem menuItem, int iconTextSpacing, boolean showIcon) { |
Abodunrinwa Toki | 852e9ac | 2017-02-22 00:20:15 +0000 | [diff] [blame] | 1668 | final View menuItemButton = LayoutInflater.from(context) |
| 1669 | .inflate(R.layout.floating_popup_menu_button, null); |
| 1670 | if (menuItem != null) { |
Abodunrinwa Toki | 9c881f2 | 2017-10-16 21:05:41 +0100 | [diff] [blame] | 1671 | updateMenuItemButton(menuItemButton, menuItem, iconTextSpacing, showIcon); |
Abodunrinwa Toki | 852e9ac | 2017-02-22 00:20:15 +0000 | [diff] [blame] | 1672 | } |
| 1673 | return menuItemButton; |
| 1674 | } |
| 1675 | |
| 1676 | /** |
| 1677 | * Updates the specified menu item button with the specified menu item data. |
| 1678 | */ |
| 1679 | private static void updateMenuItemButton( |
Abodunrinwa Toki | 9c881f2 | 2017-10-16 21:05:41 +0100 | [diff] [blame] | 1680 | View menuItemButton, MenuItem menuItem, int iconTextSpacing, boolean showIcon) { |
| 1681 | final TextView buttonText = menuItemButton.findViewById( |
Abodunrinwa Toki | 852e9ac | 2017-02-22 00:20:15 +0000 | [diff] [blame] | 1682 | R.id.floating_toolbar_menu_item_text); |
Abodunrinwa Toki | 9c881f2 | 2017-10-16 21:05:41 +0100 | [diff] [blame] | 1683 | buttonText.setEllipsize(null); |
Abodunrinwa Toki | 852e9ac | 2017-02-22 00:20:15 +0000 | [diff] [blame] | 1684 | if (TextUtils.isEmpty(menuItem.getTitle())) { |
| 1685 | buttonText.setVisibility(View.GONE); |
| 1686 | } else { |
| 1687 | buttonText.setVisibility(View.VISIBLE); |
| 1688 | buttonText.setText(menuItem.getTitle()); |
| 1689 | } |
Abodunrinwa Toki | 9c881f2 | 2017-10-16 21:05:41 +0100 | [diff] [blame] | 1690 | final ImageView buttonIcon = menuItemButton.findViewById( |
| 1691 | R.id.floating_toolbar_menu_item_image); |
| 1692 | if (menuItem.getIcon() == null || !showIcon) { |
Abodunrinwa Toki | 852e9ac | 2017-02-22 00:20:15 +0000 | [diff] [blame] | 1693 | buttonIcon.setVisibility(View.GONE); |
| 1694 | if (buttonText != null) { |
| 1695 | buttonText.setPaddingRelative(0, 0, 0, 0); |
Vladislav Kaznacheev | 7039cbc | 2017-01-04 10:15:31 -0800 | [diff] [blame] | 1696 | } |
| 1697 | } else { |
Abodunrinwa Toki | 852e9ac | 2017-02-22 00:20:15 +0000 | [diff] [blame] | 1698 | buttonIcon.setVisibility(View.VISIBLE); |
| 1699 | buttonIcon.setImageDrawable(menuItem.getIcon()); |
| 1700 | if (buttonText != null) { |
| 1701 | buttonText.setPaddingRelative(iconTextSpacing, 0, 0, 0); |
| 1702 | } |
Abodunrinwa Toki | b21562c | 2015-05-20 22:25:16 +0100 | [diff] [blame] | 1703 | } |
Vladislav Kaznacheev | 7039cbc | 2017-01-04 10:15:31 -0800 | [diff] [blame] | 1704 | final CharSequence contentDescription = menuItem.getContentDescription(); |
| 1705 | if (TextUtils.isEmpty(contentDescription)) { |
| 1706 | menuItemButton.setContentDescription(menuItem.getTitle()); |
| 1707 | } else { |
| 1708 | menuItemButton.setContentDescription(contentDescription); |
| 1709 | } |
Abodunrinwa Toki | 0c7ed28 | 2015-03-27 15:02:03 +0000 | [diff] [blame] | 1710 | } |
| 1711 | |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1712 | private static ViewGroup createContentContainer(Context context) { |
Abodunrinwa Toki | 8a5e1ae | 2015-09-28 21:59:04 +0100 | [diff] [blame] | 1713 | ViewGroup contentContainer = (ViewGroup) LayoutInflater.from(context) |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1714 | .inflate(R.layout.floating_popup_container, null); |
Abodunrinwa Toki | 6be228c | 2016-01-27 22:11:30 +0000 | [diff] [blame] | 1715 | contentContainer.setLayoutParams(new ViewGroup.LayoutParams( |
| 1716 | ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); |
Abodunrinwa Toki | 8a5e1ae | 2015-09-28 21:59:04 +0100 | [diff] [blame] | 1717 | contentContainer.setTag(FLOATING_TOOLBAR_TAG); |
Mihai Popa | 17f91d5 | 2018-04-10 16:10:16 +0100 | [diff] [blame] | 1718 | contentContainer.setClipToOutline(true); |
Abodunrinwa Toki | 8a5e1ae | 2015-09-28 21:59:04 +0100 | [diff] [blame] | 1719 | return contentContainer; |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1720 | } |
| 1721 | |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1722 | private static PopupWindow createPopupWindow(ViewGroup content) { |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1723 | ViewGroup popupContentHolder = new LinearLayout(content.getContext()); |
| 1724 | PopupWindow popupWindow = new PopupWindow(popupContentHolder); |
Yohei Yukawa | bafc908 | 2015-07-14 05:59:05 -0700 | [diff] [blame] | 1725 | // TODO: Use .setLayoutInScreenEnabled(true) instead of .setClippingEnabled(false) |
| 1726 | // unless FLAG_LAYOUT_IN_SCREEN has any unintentional side-effects. |
| 1727 | popupWindow.setClippingEnabled(false); |
Abodunrinwa Toki | 103d48e | 2015-04-16 17:27:48 +0100 | [diff] [blame] | 1728 | popupWindow.setWindowLayoutType( |
| 1729 | WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL); |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1730 | popupWindow.setAnimationStyle(0); |
| 1731 | popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); |
| 1732 | content.setLayoutParams(new ViewGroup.LayoutParams( |
| 1733 | ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); |
| 1734 | popupContentHolder.addView(content); |
| 1735 | return popupWindow; |
| 1736 | } |
| 1737 | |
| 1738 | /** |
Abodunrinwa Toki | 7aa6d0a | 2015-06-03 21:33:15 +0100 | [diff] [blame] | 1739 | * Creates an "appear" animation for the specified view. |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1740 | * |
| 1741 | * @param view The view to animate |
| 1742 | */ |
Abodunrinwa Toki | 7aa6d0a | 2015-06-03 21:33:15 +0100 | [diff] [blame] | 1743 | private static AnimatorSet createEnterAnimation(View view) { |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1744 | AnimatorSet animation = new AnimatorSet(); |
Abodunrinwa Toki | 7aa6d0a | 2015-06-03 21:33:15 +0100 | [diff] [blame] | 1745 | animation.playTogether( |
Abodunrinwa Toki | 7e29d1b | 2015-10-30 16:41:45 +0000 | [diff] [blame] | 1746 | ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1).setDuration(150)); |
Abodunrinwa Toki | 7aa6d0a | 2015-06-03 21:33:15 +0100 | [diff] [blame] | 1747 | return animation; |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1748 | } |
| 1749 | |
| 1750 | /** |
Abodunrinwa Toki | 7aa6d0a | 2015-06-03 21:33:15 +0100 | [diff] [blame] | 1751 | * Creates a "disappear" animation for the specified view. |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1752 | * |
| 1753 | * @param view The view to animate |
Abodunrinwa Toki | cdffaa4 | 2015-05-06 18:35:41 +0100 | [diff] [blame] | 1754 | * @param startDelay The start delay of the animation |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1755 | * @param listener The animation listener |
| 1756 | */ |
Abodunrinwa Toki | 7aa6d0a | 2015-06-03 21:33:15 +0100 | [diff] [blame] | 1757 | private static AnimatorSet createExitAnimation( |
Abodunrinwa Toki | cdffaa4 | 2015-05-06 18:35:41 +0100 | [diff] [blame] | 1758 | View view, int startDelay, Animator.AnimatorListener listener) { |
Abodunrinwa Toki | 7aa6d0a | 2015-06-03 21:33:15 +0100 | [diff] [blame] | 1759 | AnimatorSet animation = new AnimatorSet(); |
| 1760 | animation.playTogether( |
Abodunrinwa Toki | b9acbe4 | 2015-09-16 19:47:38 +0100 | [diff] [blame] | 1761 | ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0).setDuration(100)); |
Abodunrinwa Toki | 7aa6d0a | 2015-06-03 21:33:15 +0100 | [diff] [blame] | 1762 | animation.setStartDelay(startDelay); |
| 1763 | animation.addListener(listener); |
| 1764 | return animation; |
Abodunrinwa Toki | 517adad | 2015-04-07 22:13:51 +0100 | [diff] [blame] | 1765 | } |
| 1766 | |
Abodunrinwa Toki | 079f33b | 2015-06-23 20:36:52 -0700 | [diff] [blame] | 1767 | /** |
| 1768 | * Returns a re-themed context with controlled look and feel for views. |
| 1769 | */ |
| 1770 | private static Context applyDefaultTheme(Context originalContext) { |
| 1771 | TypedArray a = originalContext.obtainStyledAttributes(new int[]{R.attr.isLightTheme}); |
| 1772 | boolean isLightTheme = a.getBoolean(0, true); |
Mihai Popa | fb4b6b8 | 2018-03-01 16:08:14 +0000 | [diff] [blame] | 1773 | int themeId |
| 1774 | = isLightTheme ? R.style.Theme_DeviceDefault_Light : R.style.Theme_DeviceDefault; |
Abodunrinwa Toki | 079f33b | 2015-06-23 20:36:52 -0700 | [diff] [blame] | 1775 | a.recycle(); |
| 1776 | return new ContextThemeWrapper(originalContext, themeId); |
| 1777 | } |
Abodunrinwa Toki | 30f8ef4 | 2016-02-01 13:54:10 +0000 | [diff] [blame] | 1778 | } |