Home | History | Annotate | Download | only in widget
      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 
     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