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