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.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 
     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    */
     66   public void setScreenWidth(int screenWidth) {
     67     mScreenWidth = screenWidth;
     68   }
     69 
     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    */
     79   public void setVisible(boolean visible) {
     80     if (visible) {
     81       mFab.show();
     82     } else {
     83       mFab.hide();
     84     }
     85   }
     86 
     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    */
     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    */
    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    */
    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    */
    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    */
    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    */
    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 
    198   private boolean isLayoutRtl() {
    199     return mFab.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
    200   }
    201 }
    202