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.common.widget;
18
19import android.app.Activity;
20import android.content.res.Resources;
21import android.graphics.drawable.Drawable;
22import android.view.animation.AnimationUtils;
23import android.view.animation.Interpolator;
24import android.view.View;
25import android.widget.ImageButton;
26
27import com.android.contacts.common.util.ViewUtil;
28import com.android.contacts.common.R;
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 = 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
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        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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
227    private boolean isLayoutRtl() {
228        return mFloatingActionButtonContainer.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
229    }
230}
231