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; 21 import android.graphics.drawable.Drawable; 22 import android.view.animation.AnimationUtils; 23 import android.view.animation.Interpolator; 24 import android.view.View; 25 import android.widget.ImageButton; 26 27 import com.android.contacts.common.util.ViewUtil; 28 import com.android.contacts.common.R; 29 import com.android.phone.common.animation.AnimUtils; 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; 36 public static final int ALIGN_QUARTER_END = 1; 37 public static final int ALIGN_END = 2; 38 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; 42 43 private final int mAnimationDuration; 44 private final int mFloatingActionButtonWidth; 45 private final int mFloatingActionButtonMarginRight; 46 private final View mFloatingActionButtonContainer; 47 private final ImageButton mFloatingActionButton; 48 private final Interpolator mFabInterpolator; 49 private int mScreenWidth; 50 FloatingActionButtonController(Activity activity, View container, ImageButton button)51 public FloatingActionButtonController(Activity activity, View container, ImageButton button) { 52 Resources resources = activity.getResources(); 53 mFabInterpolator = AnimationUtils.loadInterpolator(activity, 54 android.R.interpolator.fast_out_slow_in); 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; 62 mFloatingActionButton = button; 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 */ setScreenWidth(int screenWidth)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 */ setVisible(boolean visible)81 public void setVisible(boolean visible) { 82 mFloatingActionButtonContainer.setVisibility(visible ? View.VISIBLE : View.GONE); 83 } 84 isVisible()85 public boolean isVisible() { 86 return mFloatingActionButtonContainer.getVisibility() == View.VISIBLE; 87 } 88 changeIcon(Drawable icon, String description)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 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 */ onPageScrolled(float positionOffset)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( 106 (int) (positionOffset * getTranslationXForAlignment(ALIGN_END))); 107 } 108 109 /** 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 */ align(int align, boolean animate)115 public void align(int align, boolean animate) { 116 align(align, 0 /*offsetX */, 0 /* offsetY */, animate); 117 } 118 119 /** 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. 124 * @param offsetY Additional offsetY to translate by. 125 * @param animate Whether or not to animate the transition. 126 */ align(int align, int offsetX, int offsetY, boolean animate)127 public void align(int align, int offsetX, int offsetY, boolean animate) { 128 if (mScreenWidth == 0) { 129 return; 130 } 131 132 int translationX = getTranslationXForAlignment(align); 133 134 // Skip animation if container is not shown; animation causes container to show again. 135 if (animate && mFloatingActionButtonContainer.isShown()) { 136 mFloatingActionButtonContainer.animate() 137 .translationX(translationX + offsetX) 138 .translationY(offsetY) 139 .setInterpolator(mFabInterpolator) 140 .setDuration(mAnimationDuration) 141 .start(); 142 } else { 143 mFloatingActionButtonContainer.setTranslationX(translationX + offsetX); 144 mFloatingActionButtonContainer.setTranslationY(offsetY); 145 } 146 } 147 148 /** 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 */ resize(int dimension, boolean animate)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 /** 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. 166 * @param delayMs The delay for the effect, in milliseconds. 167 */ scaleIn(int delayMs)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); 173 } 174 175 /** 176 * Immediately remove the affects of the last call to {@link #scaleOut}. 177 */ resetIn()178 public void resetIn() { 179 mFloatingActionButton.setAlpha(1f); 180 mFloatingActionButton.setVisibility(View.VISIBLE); 181 mFloatingActionButtonContainer.setScaleX(1); 182 mFloatingActionButtonContainer.setScaleY(1); 183 } 184 185 /** 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 */ scaleOut()189 public void scaleOut() { 190 AnimUtils.scaleOut(mFloatingActionButtonContainer, mAnimationDuration); 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); 194 } 195 196 /** 197 * Calculates the X offset of the FAB to the given alignment, adjusted for whether or not the 198 * view is in RTL mode. 199 * 200 * @param align One of ALIGN_MIDDLE, ALIGN_QUARTER_RIGHT, or ALIGN_RIGHT. 201 * @return The translationX for the given alignment. 202 */ getTranslationXForAlignment(int align)203 public int getTranslationXForAlignment(int align) { 204 int result = 0; 205 switch (align) { 206 case ALIGN_MIDDLE: 207 // Moves the FAB to exactly center screen. 208 return 0; 209 case ALIGN_QUARTER_END: 210 // Moves the FAB a quarter of the screen width. 211 result = mScreenWidth / 4; 212 break; 213 case ALIGN_END: 214 // Moves the FAB half the screen width. Same as aligning right with a marginRight. 215 result = mScreenWidth / 2 216 - mFloatingActionButtonWidth / 2 217 - mFloatingActionButtonMarginRight; 218 break; 219 } 220 if (isLayoutRtl()) { 221 result *= -1; 222 } 223 return result; 224 } 225 isLayoutRtl()226 private boolean isLayoutRtl() { 227 return mFloatingActionButtonContainer.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; 228 } 229 } 230