summaryrefslogtreecommitdiffstats
path: root/src/com/android/contacts/common/widget/FloatingActionButtonController.java
blob: 31f44af87a20993000421862b45bf3bddb151a99 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.contacts.common.widget;

import android.app.Activity;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.view.View;
import android.widget.ImageButton;

import com.android.contacts.common.util.ViewUtil;
import com.android.contacts.common.R;
import com.android.phone.common.animation.AnimUtils;

/**
 * Controls the movement and appearance of the FAB (Floating Action Button).
 */
public class FloatingActionButtonController {
    public static final int ALIGN_MIDDLE = 0;
    public static final int ALIGN_QUARTER_END = 1;
    public static final int ALIGN_END = 2;

    private static final int FAB_SCALE_IN_DURATION = 266;
    private static final int FAB_SCALE_IN_FADE_IN_DELAY = 100;
    private static final int FAB_ICON_FADE_OUT_DURATION = 66;

    private final int mAnimationDuration;
    private final int mFloatingActionButtonWidth;
    private final int mFloatingActionButtonMarginRight;
    private final View mFloatingActionButtonContainer;
    private final ImageButton mFloatingActionButton;
    private final Interpolator mFabInterpolator;
    private int mScreenWidth;

    public FloatingActionButtonController(Activity activity, View container, ImageButton button) {
        Resources resources = activity.getResources();
        mFabInterpolator = AnimationUtils.loadInterpolator(activity,
                android.R.interpolator.fast_out_slow_in);
        mFloatingActionButtonWidth = resources.getDimensionPixelSize(
                R.dimen.floating_action_button_width);
        mFloatingActionButtonMarginRight = resources.getDimensionPixelOffset(
                R.dimen.floating_action_button_margin_right);
        mAnimationDuration = resources.getInteger(
                R.integer.floating_action_button_animation_duration);
        mFloatingActionButtonContainer = container;
        mFloatingActionButton = button;
        ViewUtil.setupFloatingActionButton(mFloatingActionButtonContainer, resources);
    }

    /**
     * Passes the screen width into the class. Necessary for translation calculations.
     * Should be called as soon as parent View width is available.
     *
     * @param screenWidth The width of the screen in pixels.
     */
    public void setScreenWidth(int screenWidth) {
        mScreenWidth = screenWidth;
    }

    /**
     * Sets FAB as View.VISIBLE or View.GONE.
     *
     * @param visible Whether or not to make the container visible.
     */
    public void setVisible(boolean visible) {
        mFloatingActionButtonContainer.setVisibility(visible ? View.VISIBLE : View.GONE);
    }

    private boolean isVisible() {
        return mFloatingActionButtonContainer.getVisibility() == View.VISIBLE;
    }

    public void changeIcon(Drawable icon, String description) {
        if (mFloatingActionButton.getDrawable() != icon
                || !mFloatingActionButton.getContentDescription().equals(description)) {
            mFloatingActionButton.setImageDrawable(icon);
            mFloatingActionButton.setContentDescription(description);
        }
    }

    /**
     * Updates the FAB location (middle to right position) as the PageView scrolls.
     *
     * @param positionOffset A fraction used to calculate position of the FAB during page scroll.
     */
    public void onPageScrolled(float positionOffset) {
        // As the page is scrolling, if we're on the first tab, update the FAB position so it
        // moves along with it.
        mFloatingActionButtonContainer.setTranslationX(
                (int) (positionOffset * getTranslationXForAlignment(ALIGN_END)));
        mFloatingActionButtonContainer.setTranslationY(0);
    }

    /**
     * Aligns the FAB to the described location
     *
     * @param align One of ALIGN_MIDDLE, ALIGN_QUARTER_RIGHT, or ALIGN_RIGHT.
     * @param animate Whether or not to animate the transition.
     */
    public void align(int align, boolean animate) {
        align(align, 0 /*offsetX */, 0 /* offsetY */, animate);
    }

    /**
     * Aligns the FAB to the described location plus specified additional offsets.
     *
     * @param align One of ALIGN_MIDDLE, ALIGN_QUARTER_RIGHT, or ALIGN_RIGHT.
     * @param offsetX Additional offsetX to translate by.
     * @param offsetY Additional offsetY to translate by.
     * @param animate Whether or not to animate the transition.
     */
    public void align(int align, int offsetX, int offsetY, boolean animate) {
        if (mScreenWidth == 0) {
            return;
        }

        int translationX = getTranslationXForAlignment(align);

        // Skip animation if container is not shown; animation causes container to show again.
        if (animate && mFloatingActionButtonContainer.isShown()) {
            mFloatingActionButtonContainer.animate()
                    .translationX(translationX + offsetX)
                    .translationY(offsetY)
                    .setInterpolator(mFabInterpolator)
                    .setDuration(mAnimationDuration)
                    .start();
        } else {
            mFloatingActionButtonContainer.setTranslationX(translationX + offsetX);
            mFloatingActionButtonContainer.setTranslationY(offsetY);
        }
    }

    /**
     * Resizes width and height of the floating action bar container.
     * @param dimension The new dimensions for the width and height.
     * @param animate Whether to animate this change.
     */
    public void resize(int dimension, boolean animate) {
        if (animate) {
            AnimUtils.changeDimensions(mFloatingActionButtonContainer, dimension, dimension);
        } else {
            mFloatingActionButtonContainer.getLayoutParams().width = dimension;
            mFloatingActionButtonContainer.getLayoutParams().height = dimension;
            mFloatingActionButtonContainer.requestLayout();
        }
    }

    /**
     * Scales the floating action button from no height and width to its actual dimensions. This is
     * an animation for showing the floating action button.
     * @param delayMs The delay for the effect, in milliseconds.
     */
    public void scaleIn(int delayMs) {
        if (isVisible()) {
            mFloatingActionButtonContainer.setScaleX(1);
            mFloatingActionButtonContainer.setScaleY(1);
            return;
        }

        setVisible(true);
        AnimUtils.scaleIn(mFloatingActionButtonContainer, FAB_SCALE_IN_DURATION, delayMs);
        AnimUtils.fadeIn(mFloatingActionButton, FAB_SCALE_IN_DURATION,
                delayMs + FAB_SCALE_IN_FADE_IN_DELAY, null);
    }

    /**
     * Immediately remove the affects of the last call to {@link #scaleOut}.
     */
    public void resetIn() {
        mFloatingActionButton.setAlpha(1f);
        mFloatingActionButton.setVisibility(View.VISIBLE);
        mFloatingActionButtonContainer.setScaleX(1);
        mFloatingActionButtonContainer.setScaleY(1);
    }

    /**
     * Scales the floating action button from its actual dimensions to no height and width. This is
     * an animation for hiding the floating action button.
     */
    public void scaleOut() {
        AnimUtils.scaleOut(mFloatingActionButtonContainer, mAnimationDuration);
        // Fade out the icon faster than the scale out animation, so that the icon scaling is less
        // obvious. We don't want it to scale, but the resizing the container is not as performant.
        AnimUtils.fadeOut(mFloatingActionButton, FAB_ICON_FADE_OUT_DURATION, null);
    }

    /**
     * Calculates the X offset of the FAB to the given alignment, adjusted for whether or not the
     * view is in RTL mode.
     *
     * @param align One of ALIGN_MIDDLE, ALIGN_QUARTER_RIGHT, or ALIGN_RIGHT.
     * @return The translationX for the given alignment.
     */
    public int getTranslationXForAlignment(int align) {
        int result = 0;
        switch (align) {
            case ALIGN_MIDDLE:
                // Moves the FAB to exactly center screen.
                return 0;
            case ALIGN_QUARTER_END:
                // Moves the FAB a quarter of the screen width.
                result = mScreenWidth / 4;
                break;
            case ALIGN_END:
                // Moves the FAB half the screen width. Same as aligning right with a marginRight.
                result = mScreenWidth / 2
                        - mFloatingActionButtonWidth / 2
                        - mFloatingActionButtonMarginRight;
                break;
        }
        if (isLayoutRtl()) {
            result *= -1;
        }
        return result;
    }

    private boolean isLayoutRtl() {
        return mFloatingActionButtonContainer.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
    }
}