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