• 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.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 
FloatingActionButtonController(Activity activity, FloatingActionButton fab)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    */
setScreenWidth(int screenWidth)66   public void setScreenWidth(int screenWidth) {
67     mScreenWidth = screenWidth;
68   }
69 
isVisible()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    */
setVisible(boolean visible)79   public void setVisible(boolean visible) {
80     if (visible) {
81       mFab.show();
82     } else {
83       mFab.hide();
84     }
85   }
86 
changeIcon(Drawable icon, String description)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    */
onPageScrolled(float positionOffset)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    */
align(int align, boolean animate)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    */
align(int align, int offsetX, int offsetY, boolean animate)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    */
scaleIn(int delayMs)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    */
scaleOut()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    */
getTranslationXForAlignment(int align)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 
isLayoutRtl()198   private boolean isLayoutRtl() {
199     return mFab.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
200   }
201 }
202