• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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         mFloatingActionButtonContainer.setTranslationY(0);
108     }
109 
110     /**
111      * Aligns the FAB to the described location
112      *
113      * @param align One of ALIGN_MIDDLE, ALIGN_QUARTER_RIGHT, or ALIGN_RIGHT.
114      * @param animate Whether or not to animate the transition.
115      */
align(int align, boolean animate)116     public void align(int align, boolean animate) {
117         align(align, 0 /*offsetX */, 0 /* offsetY */, animate);
118     }
119 
120     /**
121      * Aligns the FAB to the described location plus specified additional offsets.
122      *
123      * @param align One of ALIGN_MIDDLE, ALIGN_QUARTER_RIGHT, or ALIGN_RIGHT.
124      * @param offsetX Additional offsetX to translate by.
125      * @param offsetY Additional offsetY to translate by.
126      * @param animate Whether or not to animate the transition.
127      */
align(int align, int offsetX, int offsetY, boolean animate)128     public void align(int align, int offsetX, int offsetY, boolean animate) {
129         if (mScreenWidth == 0) {
130             return;
131         }
132 
133         int translationX = getTranslationXForAlignment(align);
134 
135         // Skip animation if container is not shown; animation causes container to show again.
136         if (animate && mFloatingActionButtonContainer.isShown()) {
137             mFloatingActionButtonContainer.animate()
138                     .translationX(translationX + offsetX)
139                     .translationY(offsetY)
140                     .setInterpolator(mFabInterpolator)
141                     .setDuration(mAnimationDuration)
142                     .start();
143         } else {
144             mFloatingActionButtonContainer.setTranslationX(translationX + offsetX);
145             mFloatingActionButtonContainer.setTranslationY(offsetY);
146         }
147     }
148 
149     /**
150      * Resizes width and height of the floating action bar container.
151      * @param dimension The new dimensions for the width and height.
152      * @param animate Whether to animate this change.
153      */
resize(int dimension, boolean animate)154     public void resize(int dimension, boolean animate) {
155         if (animate) {
156             AnimUtils.changeDimensions(mFloatingActionButtonContainer, dimension, dimension);
157         } else {
158             mFloatingActionButtonContainer.getLayoutParams().width = dimension;
159             mFloatingActionButtonContainer.getLayoutParams().height = dimension;
160             mFloatingActionButtonContainer.requestLayout();
161         }
162     }
163 
164     /**
165      * Scales the floating action button from no height and width to its actual dimensions. This is
166      * an animation for showing the floating action button.
167      * @param delayMs The delay for the effect, in milliseconds.
168      */
scaleIn(int delayMs)169     public void scaleIn(int delayMs) {
170         setVisible(true);
171         AnimUtils.scaleIn(mFloatingActionButtonContainer, FAB_SCALE_IN_DURATION, delayMs);
172         AnimUtils.fadeIn(mFloatingActionButton, FAB_SCALE_IN_DURATION,
173                 delayMs + FAB_SCALE_IN_FADE_IN_DELAY, null);
174     }
175 
176     /**
177      * Immediately remove the affects of the last call to {@link #scaleOut}.
178      */
resetIn()179     public void resetIn() {
180         mFloatingActionButton.setAlpha(1f);
181         mFloatingActionButton.setVisibility(View.VISIBLE);
182         mFloatingActionButtonContainer.setScaleX(1);
183         mFloatingActionButtonContainer.setScaleY(1);
184     }
185 
186     /**
187      * Scales the floating action button from its actual dimensions to no height and width. This is
188      * an animation for hiding the floating action button.
189      */
scaleOut()190     public void scaleOut() {
191         AnimUtils.scaleOut(mFloatingActionButtonContainer, mAnimationDuration);
192         // Fade out the icon faster than the scale out animation, so that the icon scaling is less
193         // obvious. We don't want it to scale, but the resizing the container is not as performant.
194         AnimUtils.fadeOut(mFloatingActionButton, FAB_ICON_FADE_OUT_DURATION, null);
195     }
196 
197     /**
198      * Calculates the X offset of the FAB to the given alignment, adjusted for whether or not the
199      * view is in RTL mode.
200      *
201      * @param align One of ALIGN_MIDDLE, ALIGN_QUARTER_RIGHT, or ALIGN_RIGHT.
202      * @return The translationX for the given alignment.
203      */
getTranslationXForAlignment(int align)204     public int getTranslationXForAlignment(int align) {
205         int result = 0;
206         switch (align) {
207             case ALIGN_MIDDLE:
208                 // Moves the FAB to exactly center screen.
209                 return 0;
210             case ALIGN_QUARTER_END:
211                 // Moves the FAB a quarter of the screen width.
212                 result = mScreenWidth / 4;
213                 break;
214             case ALIGN_END:
215                 // Moves the FAB half the screen width. Same as aligning right with a marginRight.
216                 result = mScreenWidth / 2
217                         - mFloatingActionButtonWidth / 2
218                         - mFloatingActionButtonMarginRight;
219                 break;
220         }
221         if (isLayoutRtl()) {
222             result *= -1;
223         }
224         return result;
225     }
226 
isLayoutRtl()227     private boolean isLayoutRtl() {
228         return mFloatingActionButtonContainer.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
229     }
230 }
231