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.widget;
18 
19 import android.app.Activity;
20 import android.content.res.Resources;
21 import android.graphics.drawable.Drawable;
22 import android.view.View;
23 import android.view.animation.AnimationUtils;
24 import android.view.animation.Interpolator;
25 import android.widget.ImageButton;
26 
27 import com.android.contacts.R;
28 import com.android.contacts.util.ViewUtil;
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 = 186;
40     private static final int FAB_SCALE_IN_FADE_IN_DELAY = 70;
41     private static final int FAB_ICON_FADE_OUT_DURATION = 46;
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