Sai Cheemalapati | 6cff9cf | 2014-06-04 16:30:31 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2014 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.contacts.common.widget; |
| 18 | |
| 19 | import android.app.Activity; |
| 20 | import android.content.res.Resources; |
Andrew Lee | bf6731b | 2015-05-05 13:48:51 -0700 | [diff] [blame] | 21 | import android.graphics.drawable.Drawable; |
Sai Cheemalapati | 6cff9cf | 2014-06-04 16:30:31 -0700 | [diff] [blame] | 22 | import android.view.animation.AnimationUtils; |
| 23 | import android.view.animation.Interpolator; |
| 24 | import android.view.View; |
Andrew Lee | bf6731b | 2015-05-05 13:48:51 -0700 | [diff] [blame] | 25 | import android.widget.ImageButton; |
Sai Cheemalapati | 6cff9cf | 2014-06-04 16:30:31 -0700 | [diff] [blame] | 26 | |
Wenyi Wang | 1b6b7e2 | 2016-01-06 14:34:48 -0800 | [diff] [blame] | 27 | import com.android.contacts.common.util.ViewUtil; |
Wenyi Wang | 1ba9d8a | 2016-02-21 10:48:40 -0800 | [diff] [blame^] | 28 | import com.android.contacts.common.R; |
Andrew Lee | 7c7eaa7 | 2014-08-14 17:50:43 -0700 | [diff] [blame] | 29 | import com.android.phone.common.animation.AnimUtils; |
Sai Cheemalapati | 6cff9cf | 2014-06-04 16:30:31 -0700 | [diff] [blame] | 30 | |
| 31 | /** |
| 32 | * Controls the movement and appearance of the FAB (Floating Action Button). |
| 33 | */ |
| 34 | public class FloatingActionButtonController { |
| 35 | public static final int ALIGN_MIDDLE = 0; |
Yorke Lee | a2fa4ba | 2014-07-22 10:52:07 -0700 | [diff] [blame] | 36 | public static final int ALIGN_QUARTER_END = 1; |
| 37 | public static final int ALIGN_END = 2; |
Sai Cheemalapati | 6cff9cf | 2014-06-04 16:30:31 -0700 | [diff] [blame] | 38 | |
Andrew Lee | d2bbc2c | 2014-08-29 16:42:19 -0700 | [diff] [blame] | 39 | private static final int FAB_SCALE_IN_DURATION = 266; |
| 40 | private static final int FAB_SCALE_IN_FADE_IN_DELAY = 100; |
| 41 | private static final int FAB_ICON_FADE_OUT_DURATION = 66; |
Andrew Lee | 435dbb9 | 2014-08-19 11:37:37 -0700 | [diff] [blame] | 42 | |
Sai Cheemalapati | 6cff9cf | 2014-06-04 16:30:31 -0700 | [diff] [blame] | 43 | private final int mAnimationDuration; |
| 44 | private final int mFloatingActionButtonWidth; |
| 45 | private final int mFloatingActionButtonMarginRight; |
| 46 | private final View mFloatingActionButtonContainer; |
Andrew Lee | bf6731b | 2015-05-05 13:48:51 -0700 | [diff] [blame] | 47 | private final ImageButton mFloatingActionButton; |
Sai Cheemalapati | 6cff9cf | 2014-06-04 16:30:31 -0700 | [diff] [blame] | 48 | private final Interpolator mFabInterpolator; |
| 49 | private int mScreenWidth; |
| 50 | |
Andrew Lee | bf6731b | 2015-05-05 13:48:51 -0700 | [diff] [blame] | 51 | public FloatingActionButtonController(Activity activity, View container, ImageButton button) { |
Sai Cheemalapati | 6cff9cf | 2014-06-04 16:30:31 -0700 | [diff] [blame] | 52 | Resources resources = activity.getResources(); |
Wenyi Wang | 1ba9d8a | 2016-02-21 10:48:40 -0800 | [diff] [blame^] | 53 | mFabInterpolator = AnimationUtils.loadInterpolator(activity, |
| 54 | android.R.interpolator.fast_out_slow_in); |
Sai Cheemalapati | 6cff9cf | 2014-06-04 16:30:31 -0700 | [diff] [blame] | 55 | mFloatingActionButtonWidth = resources.getDimensionPixelSize( |
| 56 | R.dimen.floating_action_button_width); |
| 57 | mFloatingActionButtonMarginRight = resources.getDimensionPixelOffset( |
| 58 | R.dimen.floating_action_button_margin_right); |
| 59 | mAnimationDuration = resources.getInteger( |
| 60 | R.integer.floating_action_button_animation_duration); |
| 61 | mFloatingActionButtonContainer = container; |
Andrew Lee | 435dbb9 | 2014-08-19 11:37:37 -0700 | [diff] [blame] | 62 | mFloatingActionButton = button; |
Sai Cheemalapati | 6cff9cf | 2014-06-04 16:30:31 -0700 | [diff] [blame] | 63 | ViewUtil.setupFloatingActionButton(mFloatingActionButtonContainer, resources); |
| 64 | } |
| 65 | |
| 66 | /** |
| 67 | * Passes the screen width into the class. Necessary for translation calculations. |
| 68 | * Should be called as soon as parent View width is available. |
| 69 | * |
| 70 | * @param screenWidth The width of the screen in pixels. |
| 71 | */ |
| 72 | public void setScreenWidth(int screenWidth) { |
| 73 | mScreenWidth = screenWidth; |
| 74 | } |
| 75 | |
| 76 | /** |
| 77 | * Sets FAB as View.VISIBLE or View.GONE. |
| 78 | * |
| 79 | * @param visible Whether or not to make the container visible. |
| 80 | */ |
| 81 | public void setVisible(boolean visible) { |
| 82 | mFloatingActionButtonContainer.setVisibility(visible ? View.VISIBLE : View.GONE); |
| 83 | } |
| 84 | |
Andrew Lee | d01d238 | 2015-06-25 14:55:09 -0700 | [diff] [blame] | 85 | public boolean isVisible() { |
Andrew Lee | 41e1641 | 2015-06-16 14:15:55 -0700 | [diff] [blame] | 86 | return mFloatingActionButtonContainer.getVisibility() == View.VISIBLE; |
| 87 | } |
| 88 | |
Andrew Lee | bf6731b | 2015-05-05 13:48:51 -0700 | [diff] [blame] | 89 | public void changeIcon(Drawable icon, String description) { |
| 90 | if (mFloatingActionButton.getDrawable() != icon |
| 91 | || !mFloatingActionButton.getContentDescription().equals(description)) { |
| 92 | mFloatingActionButton.setImageDrawable(icon); |
| 93 | mFloatingActionButton.setContentDescription(description); |
| 94 | } |
| 95 | } |
| 96 | |
Sai Cheemalapati | 6cff9cf | 2014-06-04 16:30:31 -0700 | [diff] [blame] | 97 | /** |
| 98 | * Updates the FAB location (middle to right position) as the PageView scrolls. |
| 99 | * |
| 100 | * @param positionOffset A fraction used to calculate position of the FAB during page scroll. |
| 101 | */ |
| 102 | public void onPageScrolled(float positionOffset) { |
| 103 | // As the page is scrolling, if we're on the first tab, update the FAB position so it |
| 104 | // moves along with it. |
| 105 | mFloatingActionButtonContainer.setTranslationX( |
Yorke Lee | a2fa4ba | 2014-07-22 10:52:07 -0700 | [diff] [blame] | 106 | (int) (positionOffset * getTranslationXForAlignment(ALIGN_END))); |
Sai Cheemalapati | 6cff9cf | 2014-06-04 16:30:31 -0700 | [diff] [blame] | 107 | } |
| 108 | |
| 109 | /** |
Andrew Lee | 365fb20 | 2015-06-16 15:30:26 -0700 | [diff] [blame] | 110 | * Aligns the FAB to the described location |
| 111 | * |
| 112 | * @param align One of ALIGN_MIDDLE, ALIGN_QUARTER_RIGHT, or ALIGN_RIGHT. |
| 113 | * @param animate Whether or not to animate the transition. |
| 114 | */ |
| 115 | public void align(int align, boolean animate) { |
| 116 | align(align, 0 /*offsetX */, 0 /* offsetY */, animate); |
| 117 | } |
| 118 | |
| 119 | /** |
Sai Cheemalapati | 6cff9cf | 2014-06-04 16:30:31 -0700 | [diff] [blame] | 120 | * Aligns the FAB to the described location plus specified additional offsets. |
| 121 | * |
| 122 | * @param align One of ALIGN_MIDDLE, ALIGN_QUARTER_RIGHT, or ALIGN_RIGHT. |
| 123 | * @param offsetX Additional offsetX to translate by. |
Andrew Lee | 365fb20 | 2015-06-16 15:30:26 -0700 | [diff] [blame] | 124 | * @param offsetY Additional offsetY to translate by. |
Sai Cheemalapati | 6cff9cf | 2014-06-04 16:30:31 -0700 | [diff] [blame] | 125 | * @param animate Whether or not to animate the transition. |
| 126 | */ |
| 127 | public void align(int align, int offsetX, int offsetY, boolean animate) { |
Andrew Lee | 7c7eaa7 | 2014-08-14 17:50:43 -0700 | [diff] [blame] | 128 | if (mScreenWidth == 0) { |
| 129 | return; |
| 130 | } |
| 131 | |
Sai Cheemalapati | 6cff9cf | 2014-06-04 16:30:31 -0700 | [diff] [blame] | 132 | int translationX = getTranslationXForAlignment(align); |
Andrew Lee | 955e10d | 2014-08-28 15:12:19 -0700 | [diff] [blame] | 133 | |
| 134 | // Skip animation if container is not shown; animation causes container to show again. |
| 135 | if (animate && mFloatingActionButtonContainer.isShown()) { |
Sai Cheemalapati | 6cff9cf | 2014-06-04 16:30:31 -0700 | [diff] [blame] | 136 | mFloatingActionButtonContainer.animate() |
| 137 | .translationX(translationX + offsetX) |
| 138 | .translationY(offsetY) |
| 139 | .setInterpolator(mFabInterpolator) |
Andrew Lee | 7c7eaa7 | 2014-08-14 17:50:43 -0700 | [diff] [blame] | 140 | .setDuration(mAnimationDuration) |
| 141 | .start(); |
Sai Cheemalapati | 6cff9cf | 2014-06-04 16:30:31 -0700 | [diff] [blame] | 142 | } else { |
| 143 | mFloatingActionButtonContainer.setTranslationX(translationX + offsetX); |
| 144 | mFloatingActionButtonContainer.setTranslationY(offsetY); |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | /** |
Andrew Lee | 7c7eaa7 | 2014-08-14 17:50:43 -0700 | [diff] [blame] | 149 | * Resizes width and height of the floating action bar container. |
| 150 | * @param dimension The new dimensions for the width and height. |
| 151 | * @param animate Whether to animate this change. |
| 152 | */ |
| 153 | public void resize(int dimension, boolean animate) { |
| 154 | if (animate) { |
| 155 | AnimUtils.changeDimensions(mFloatingActionButtonContainer, dimension, dimension); |
| 156 | } else { |
| 157 | mFloatingActionButtonContainer.getLayoutParams().width = dimension; |
| 158 | mFloatingActionButtonContainer.getLayoutParams().height = dimension; |
| 159 | mFloatingActionButtonContainer.requestLayout(); |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | /** |
Andrew Lee | 435dbb9 | 2014-08-19 11:37:37 -0700 | [diff] [blame] | 164 | * Scales the floating action button from no height and width to its actual dimensions. This is |
| 165 | * an animation for showing the floating action button. |
Andrew Lee | d2bbc2c | 2014-08-29 16:42:19 -0700 | [diff] [blame] | 166 | * @param delayMs The delay for the effect, in milliseconds. |
Andrew Lee | 435dbb9 | 2014-08-19 11:37:37 -0700 | [diff] [blame] | 167 | */ |
Andrew Lee | d2bbc2c | 2014-08-29 16:42:19 -0700 | [diff] [blame] | 168 | public void scaleIn(int delayMs) { |
| 169 | setVisible(true); |
| 170 | AnimUtils.scaleIn(mFloatingActionButtonContainer, FAB_SCALE_IN_DURATION, delayMs); |
| 171 | AnimUtils.fadeIn(mFloatingActionButton, FAB_SCALE_IN_DURATION, |
| 172 | delayMs + FAB_SCALE_IN_FADE_IN_DELAY, null); |
Andrew Lee | 435dbb9 | 2014-08-19 11:37:37 -0700 | [diff] [blame] | 173 | } |
| 174 | |
| 175 | /** |
Brian Attwell | 143c189 | 2015-05-04 12:34:00 -0700 | [diff] [blame] | 176 | * Immediately remove the affects of the last call to {@link #scaleOut}. |
| 177 | */ |
| 178 | public void resetIn() { |
Brian Attwell | 6aa50bc | 2015-05-11 14:25:00 -0700 | [diff] [blame] | 179 | mFloatingActionButton.setAlpha(1f); |
Brian Attwell | 143c189 | 2015-05-04 12:34:00 -0700 | [diff] [blame] | 180 | mFloatingActionButton.setVisibility(View.VISIBLE); |
| 181 | mFloatingActionButtonContainer.setScaleX(1); |
| 182 | mFloatingActionButtonContainer.setScaleY(1); |
| 183 | } |
| 184 | |
| 185 | /** |
Andrew Lee | 435dbb9 | 2014-08-19 11:37:37 -0700 | [diff] [blame] | 186 | * Scales the floating action button from its actual dimensions to no height and width. This is |
| 187 | * an animation for hiding the floating action button. |
| 188 | */ |
| 189 | public void scaleOut() { |
| 190 | AnimUtils.scaleOut(mFloatingActionButtonContainer, mAnimationDuration); |
Andrew Lee | d2bbc2c | 2014-08-29 16:42:19 -0700 | [diff] [blame] | 191 | // Fade out the icon faster than the scale out animation, so that the icon scaling is less |
| 192 | // obvious. We don't want it to scale, but the resizing the container is not as performant. |
| 193 | AnimUtils.fadeOut(mFloatingActionButton, FAB_ICON_FADE_OUT_DURATION, null); |
Andrew Lee | 435dbb9 | 2014-08-19 11:37:37 -0700 | [diff] [blame] | 194 | } |
| 195 | |
| 196 | /** |
Yorke Lee | a2fa4ba | 2014-07-22 10:52:07 -0700 | [diff] [blame] | 197 | * Calculates the X offset of the FAB to the given alignment, adjusted for whether or not the |
| 198 | * view is in RTL mode. |
Sai Cheemalapati | 6cff9cf | 2014-06-04 16:30:31 -0700 | [diff] [blame] | 199 | * |
| 200 | * @param align One of ALIGN_MIDDLE, ALIGN_QUARTER_RIGHT, or ALIGN_RIGHT. |
| 201 | * @return The translationX for the given alignment. |
| 202 | */ |
| 203 | public int getTranslationXForAlignment(int align) { |
Yorke Lee | a2fa4ba | 2014-07-22 10:52:07 -0700 | [diff] [blame] | 204 | int result = 0; |
Sai Cheemalapati | 6cff9cf | 2014-06-04 16:30:31 -0700 | [diff] [blame] | 205 | switch (align) { |
| 206 | case ALIGN_MIDDLE: |
| 207 | // Moves the FAB to exactly center screen. |
| 208 | return 0; |
Yorke Lee | a2fa4ba | 2014-07-22 10:52:07 -0700 | [diff] [blame] | 209 | case ALIGN_QUARTER_END: |
Sai Cheemalapati | 6cff9cf | 2014-06-04 16:30:31 -0700 | [diff] [blame] | 210 | // Moves the FAB a quarter of the screen width. |
Yorke Lee | a2fa4ba | 2014-07-22 10:52:07 -0700 | [diff] [blame] | 211 | result = mScreenWidth / 4; |
| 212 | break; |
| 213 | case ALIGN_END: |
Sai Cheemalapati | 6cff9cf | 2014-06-04 16:30:31 -0700 | [diff] [blame] | 214 | // Moves the FAB half the screen width. Same as aligning right with a marginRight. |
Yorke Lee | a2fa4ba | 2014-07-22 10:52:07 -0700 | [diff] [blame] | 215 | result = mScreenWidth / 2 |
Sai Cheemalapati | 6cff9cf | 2014-06-04 16:30:31 -0700 | [diff] [blame] | 216 | - mFloatingActionButtonWidth / 2 |
| 217 | - mFloatingActionButtonMarginRight; |
Yorke Lee | a2fa4ba | 2014-07-22 10:52:07 -0700 | [diff] [blame] | 218 | break; |
Sai Cheemalapati | 6cff9cf | 2014-06-04 16:30:31 -0700 | [diff] [blame] | 219 | } |
Yorke Lee | a2fa4ba | 2014-07-22 10:52:07 -0700 | [diff] [blame] | 220 | if (isLayoutRtl()) { |
| 221 | result *= -1; |
| 222 | } |
| 223 | return result; |
Sai Cheemalapati | 6cff9cf | 2014-06-04 16:30:31 -0700 | [diff] [blame] | 224 | } |
| 225 | |
Yorke Lee | a2fa4ba | 2014-07-22 10:52:07 -0700 | [diff] [blame] | 226 | private boolean isLayoutRtl() { |
Yorke Lee | 923859f | 2014-07-25 14:40:52 -0700 | [diff] [blame] | 227 | return mFloatingActionButtonContainer.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; |
Yorke Lee | a2fa4ba | 2014-07-22 10:52:07 -0700 | [diff] [blame] | 228 | } |
Sai Cheemalapati | 6cff9cf | 2014-06-04 16:30:31 -0700 | [diff] [blame] | 229 | } |