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
17package com.android.contacts.widget;
18
19import android.app.Activity;
20import android.content.res.Resources;
21import android.graphics.drawable.Drawable;
22import android.view.View;
23import android.view.animation.AnimationUtils;
24import android.view.animation.Interpolator;
25import android.widget.ImageButton;
26
27import com.android.contacts.R;
28import com.android.contacts.util.ViewUtil;
29import com.android.phone.common.animation.AnimUtils;
30
31/**
32 * Controls the movement and appearance of the FAB (Floating Action Button).
33 */
34public 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
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     */
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     */
81    public void setVisible(boolean visible) {
82        mFloatingActionButtonContainer.setVisibility(visible ? View.VISIBLE : View.GONE);
83    }
84
85    public boolean isVisible() {
86        return mFloatingActionButtonContainer.getVisibility() == View.VISIBLE;
87    }
88
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
226    private boolean isLayoutRtl() {
227        return mFloatingActionButtonContainer.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
228    }
229}
230