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