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.view.animation.AnimationUtils; 22 import android.view.animation.Interpolator; 23 import android.view.View; 24 25 import com.android.contacts.common.util.ViewUtil; 26 import com.android.contacts.common.R; 27 import com.android.phone.common.animation.AnimUtils; 28 29 /** 30 * Controls the movement and appearance of the FAB (Floating Action Button). 31 */ 32 public class FloatingActionButtonController { 33 public static final int ALIGN_MIDDLE = 0; 34 public static final int ALIGN_QUARTER_END = 1; 35 public static final int ALIGN_END = 2; 36 37 private static final int FAB_SCALE_IN_DURATION = 266; 38 private static final int FAB_SCALE_IN_FADE_IN_DELAY = 100; 39 private static final int FAB_ICON_FADE_OUT_DURATION = 66; 40 41 private final int mAnimationDuration; 42 private final int mFloatingActionButtonWidth; 43 private final int mFloatingActionButtonMarginRight; 44 private final View mFloatingActionButtonContainer; 45 private final View mFloatingActionButton; 46 private final Interpolator mFabInterpolator; 47 private int mScreenWidth; 48 FloatingActionButtonController(Activity activity, View container, View button)49 public FloatingActionButtonController(Activity activity, View container, View button) { 50 Resources resources = activity.getResources(); 51 mFabInterpolator = AnimationUtils.loadInterpolator(activity, 52 android.R.interpolator.fast_out_slow_in); 53 mFloatingActionButtonWidth = resources.getDimensionPixelSize( 54 R.dimen.floating_action_button_width); 55 mFloatingActionButtonMarginRight = resources.getDimensionPixelOffset( 56 R.dimen.floating_action_button_margin_right); 57 mAnimationDuration = resources.getInteger( 58 R.integer.floating_action_button_animation_duration); 59 mFloatingActionButtonContainer = container; 60 mFloatingActionButton = button; 61 ViewUtil.setupFloatingActionButton(mFloatingActionButtonContainer, resources); 62 } 63 64 /** 65 * Passes the screen width into the class. Necessary for translation calculations. 66 * Should be called as soon as parent View width is available. 67 * 68 * @param screenWidth The width of the screen in pixels. 69 */ setScreenWidth(int screenWidth)70 public void setScreenWidth(int screenWidth) { 71 mScreenWidth = screenWidth; 72 } 73 74 /** 75 * Sets FAB as View.VISIBLE or View.GONE. 76 * 77 * @param visible Whether or not to make the container visible. 78 */ setVisible(boolean visible)79 public void setVisible(boolean visible) { 80 mFloatingActionButtonContainer.setVisibility(visible ? View.VISIBLE : View.GONE); 81 } 82 83 /** 84 * Updates the FAB location (middle to right position) as the PageView scrolls. 85 * 86 * @param positionOffset A fraction used to calculate position of the FAB during page scroll. 87 */ onPageScrolled(float positionOffset)88 public void onPageScrolled(float positionOffset) { 89 // As the page is scrolling, if we're on the first tab, update the FAB position so it 90 // moves along with it. 91 mFloatingActionButtonContainer.setTranslationX( 92 (int) (positionOffset * getTranslationXForAlignment(ALIGN_END))); 93 mFloatingActionButtonContainer.setTranslationY(0); 94 } 95 96 /** 97 * Aligns the FAB to the described location plus specified additional offsets. 98 * 99 * @param align One of ALIGN_MIDDLE, ALIGN_QUARTER_RIGHT, or ALIGN_RIGHT. 100 * @param offsetX Additional offsetX to translate by. 101 * @param animate Whether or not to animate the transition. 102 */ align(int align, int offsetX, int offsetY, boolean animate)103 public void align(int align, int offsetX, int offsetY, boolean animate) { 104 if (mScreenWidth == 0) { 105 return; 106 } 107 108 int translationX = getTranslationXForAlignment(align); 109 110 // Skip animation if container is not shown; animation causes container to show again. 111 if (animate && mFloatingActionButtonContainer.isShown()) { 112 mFloatingActionButtonContainer.animate() 113 .translationX(translationX + offsetX) 114 .translationY(offsetY) 115 .setInterpolator(mFabInterpolator) 116 .setDuration(mAnimationDuration) 117 .start(); 118 } else { 119 mFloatingActionButtonContainer.setTranslationX(translationX + offsetX); 120 mFloatingActionButtonContainer.setTranslationY(offsetY); 121 } 122 } 123 124 /** 125 * Resizes width and height of the floating action bar container. 126 * @param dimension The new dimensions for the width and height. 127 * @param animate Whether to animate this change. 128 */ resize(int dimension, boolean animate)129 public void resize(int dimension, boolean animate) { 130 if (animate) { 131 AnimUtils.changeDimensions(mFloatingActionButtonContainer, dimension, dimension); 132 } else { 133 mFloatingActionButtonContainer.getLayoutParams().width = dimension; 134 mFloatingActionButtonContainer.getLayoutParams().height = dimension; 135 mFloatingActionButtonContainer.requestLayout(); 136 } 137 } 138 139 /** 140 * Scales the floating action button from no height and width to its actual dimensions. This is 141 * an animation for showing the floating action button. 142 * @param delayMs The delay for the effect, in milliseconds. 143 */ scaleIn(int delayMs)144 public void scaleIn(int delayMs) { 145 setVisible(true); 146 AnimUtils.scaleIn(mFloatingActionButtonContainer, FAB_SCALE_IN_DURATION, delayMs); 147 AnimUtils.fadeIn(mFloatingActionButton, FAB_SCALE_IN_DURATION, 148 delayMs + FAB_SCALE_IN_FADE_IN_DELAY, null); 149 } 150 151 /** 152 * Scales the floating action button from its actual dimensions to no height and width. This is 153 * an animation for hiding the floating action button. 154 */ scaleOut()155 public void scaleOut() { 156 AnimUtils.scaleOut(mFloatingActionButtonContainer, mAnimationDuration); 157 // Fade out the icon faster than the scale out animation, so that the icon scaling is less 158 // obvious. We don't want it to scale, but the resizing the container is not as performant. 159 AnimUtils.fadeOut(mFloatingActionButton, FAB_ICON_FADE_OUT_DURATION, null); 160 } 161 162 /** 163 * Calculates the X offset of the FAB to the given alignment, adjusted for whether or not the 164 * view is in RTL mode. 165 * 166 * @param align One of ALIGN_MIDDLE, ALIGN_QUARTER_RIGHT, or ALIGN_RIGHT. 167 * @return The translationX for the given alignment. 168 */ getTranslationXForAlignment(int align)169 public int getTranslationXForAlignment(int align) { 170 int result = 0; 171 switch (align) { 172 case ALIGN_MIDDLE: 173 // Moves the FAB to exactly center screen. 174 return 0; 175 case ALIGN_QUARTER_END: 176 // Moves the FAB a quarter of the screen width. 177 result = mScreenWidth / 4; 178 break; 179 case ALIGN_END: 180 // Moves the FAB half the screen width. Same as aligning right with a marginRight. 181 result = mScreenWidth / 2 182 - mFloatingActionButtonWidth / 2 183 - mFloatingActionButtonMarginRight; 184 break; 185 } 186 if (isLayoutRtl()) { 187 result *= -1; 188 } 189 return result; 190 } 191 isLayoutRtl()192 private boolean isLayoutRtl() { 193 return mFloatingActionButtonContainer.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; 194 } 195 } 196