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.dialer.widget; 18 19 import android.app.Activity; 20 import android.content.Context; 21 import android.content.res.ColorStateList; 22 import android.content.res.Resources; 23 import android.support.annotation.DrawableRes; 24 import android.support.design.widget.FloatingActionButton; 25 import android.support.design.widget.FloatingActionButton.OnVisibilityChangedListener; 26 import android.view.View; 27 import android.view.animation.AnimationUtils; 28 import android.view.animation.Interpolator; 29 import com.android.dialer.common.Assert; 30 31 /** Controls the movement and appearance of the FAB (Floating Action Button). */ 32 public class FloatingActionButtonController { 33 34 public static final int ALIGN_MIDDLE = 0; 35 public static final int ALIGN_QUARTER_END = 1; 36 public static final int ALIGN_END = 2; 37 38 private final int animationDuration; 39 private final int floatingActionButtonWidth; 40 private final int floatingActionButtonMarginRight; 41 private final FloatingActionButton fab; 42 private final Interpolator fabInterpolator; 43 private int fabIconId = -1; 44 private int screenWidth; 45 FloatingActionButtonController(Activity activity, FloatingActionButton fab)46 public FloatingActionButtonController(Activity activity, FloatingActionButton fab) { 47 Resources resources = activity.getResources(); 48 fabInterpolator = 49 AnimationUtils.loadInterpolator(activity, android.R.interpolator.fast_out_slow_in); 50 floatingActionButtonWidth = 51 resources.getDimensionPixelSize(R.dimen.floating_action_button_width); 52 floatingActionButtonMarginRight = 53 resources.getDimensionPixelOffset(R.dimen.floating_action_button_margin_right); 54 animationDuration = resources.getInteger(R.integer.floating_action_button_animation_duration); 55 this.fab = fab; 56 } 57 58 /** 59 * Passes the screen width into the class. Necessary for translation calculations. Should be 60 * called as soon as parent View width is available. 61 * 62 * @param screenWidth The width of the screen in pixels. 63 */ setScreenWidth(int screenWidth)64 public void setScreenWidth(int screenWidth) { 65 this.screenWidth = screenWidth; 66 } 67 68 /** @see FloatingActionButton#isShown() */ isVisible()69 public boolean isVisible() { 70 return fab.isShown(); 71 } 72 73 /** 74 * Sets FAB as shown or hidden. 75 * 76 * @see #scaleIn() 77 * @see #scaleOut() 78 */ setVisible(boolean visible)79 public void setVisible(boolean visible) { 80 if (visible) { 81 scaleIn(); 82 } else { 83 scaleOut(); 84 } 85 } 86 changeIcon(Context context, @DrawableRes int iconId, String description)87 public void changeIcon(Context context, @DrawableRes int iconId, String description) { 88 if (this.fabIconId != iconId) { 89 fab.setImageResource(iconId); 90 fab.setImageTintList( 91 ColorStateList.valueOf(context.getResources().getColor(android.R.color.white))); 92 this.fabIconId = iconId; 93 } 94 if (!fab.getContentDescription().equals(description)) { 95 fab.setContentDescription(description); 96 } 97 } 98 99 /** 100 * Updates the FAB location (middle to right position) as the PageView scrolls. 101 * 102 * @param positionOffset A fraction used to calculate position of the FAB during page scroll. 103 */ onPageScrolled(float positionOffset)104 public void onPageScrolled(float positionOffset) { 105 // As the page is scrolling, if we're on the first tab, update the FAB position so it 106 // moves along with it. 107 fab.setTranslationX(positionOffset * getTranslationXForAlignment(ALIGN_END)); 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 */ align(int align, boolean animate)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 */ align(int align, int offsetX, int offsetY, boolean animate)128 private void align(int align, int offsetX, int offsetY, boolean animate) { 129 if (screenWidth == 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 && fab.isShown()) { 137 fab.animate() 138 .translationX(translationX + offsetX) 139 .translationY(offsetY) 140 .setInterpolator(fabInterpolator) 141 .setDuration(animationDuration) 142 .start(); 143 } else { 144 fab.setTranslationX(translationX + offsetX); 145 fab.setTranslationY(offsetY); 146 } 147 } 148 149 /** @see FloatingActionButton#show() */ scaleIn()150 public void scaleIn() { 151 fab.show(); 152 } 153 154 /** @see FloatingActionButton#hide() */ scaleOut()155 public void scaleOut() { 156 fab.hide(); 157 } 158 scaleOut(OnVisibilityChangedListener listener)159 public void scaleOut(OnVisibilityChangedListener listener) { 160 fab.hide(listener); 161 } 162 163 /** 164 * Calculates the X offset of the FAB to the given alignment, adjusted for whether or not the view 165 * is in RTL mode. 166 * 167 * @param align One of ALIGN_MIDDLE, ALIGN_QUARTER_RIGHT, or ALIGN_RIGHT. 168 * @return The translationX for the given alignment. 169 */ getTranslationXForAlignment(int align)170 private int getTranslationXForAlignment(int align) { 171 int result; 172 switch (align) { 173 case ALIGN_MIDDLE: 174 // Moves the FAB to exactly center screen. 175 return 0; 176 case ALIGN_QUARTER_END: 177 // Moves the FAB a quarter of the screen width. 178 result = screenWidth / 4; 179 break; 180 case ALIGN_END: 181 // Moves the FAB half the screen width. Same as aligning right with a marginRight. 182 result = screenWidth / 2 - floatingActionButtonWidth / 2 - floatingActionButtonMarginRight; 183 break; 184 default: 185 throw Assert.createIllegalStateFailException("Invalid alignment value: " + align); 186 } 187 if (isLayoutRtl()) { 188 result *= -1; 189 } 190 return result; 191 } 192 isLayoutRtl()193 private boolean isLayoutRtl() { 194 return fab.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; 195 } 196 } 197