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