From 6007f5208514c4f69ca6fa75507abba92ef5372d Mon Sep 17 00:00:00 2001 From: calderwoodra Date: Mon, 9 Oct 2017 16:59:04 -0700 Subject: Improved FAB UX. One of the biggest issues with the FAB is that it doesn't scale in/out properly. It looked more like setVisibility was being called instead. This CL migrates the FAB controller to use the built in scale in/out methods #show and #hide and the animation is much nicer. Some of the other issues were: - Now scaling animation is correct and visible. - No longer flashes when going in/out of search. - No longer shows on top of the return to call controller. - No longer bugs when switching between VVM TOS and contacts. - No longer shows FAB over VVM TOS when entering/exiting search Bug: 62588192,35359563,64116334,27458212,37991480,67419607 Test: manual PiperOrigin-RevId: 171608105 Change-Id: I9b3f61df35abf3659a432adf411b1b7d20eba683 --- .../widget/FloatingActionButtonController.java | 201 --------------------- java/com/android/dialer/app/DialtactsActivity.java | 43 +++-- .../dialer/app/calllog/CallLogFragment.java | 7 +- .../com/android/dialer/app/list/ListsFragment.java | 11 ++ .../dialer/dialpadview/DialpadFragment.java | 20 +- .../widget/FloatingActionButtonController.java | 190 +++++++++++++++++++ 6 files changed, 248 insertions(+), 224 deletions(-) delete mode 100644 java/com/android/contacts/common/widget/FloatingActionButtonController.java create mode 100644 java/com/android/dialer/widget/FloatingActionButtonController.java (limited to 'java') diff --git a/java/com/android/contacts/common/widget/FloatingActionButtonController.java b/java/com/android/contacts/common/widget/FloatingActionButtonController.java deleted file mode 100644 index d924681ea..000000000 --- a/java/com/android/contacts/common/widget/FloatingActionButtonController.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * 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.support.design.widget.FloatingActionButton; -import android.view.View; -import android.view.animation.AnimationUtils; -import android.view.animation.Interpolator; -import com.android.contacts.common.R; -import com.android.dialer.animation.AnimUtils; -import com.android.dialer.common.Assert; - -/** 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 FloatingActionButton mFab; - private final Interpolator mFabInterpolator; - private int mScreenWidth; - - public FloatingActionButtonController(Activity activity, FloatingActionButton fab) { - 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); - mFab = fab; - } - - /** - * 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; - } - - public boolean isVisible() { - return mFab.getVisibility() == View.VISIBLE; - } - - /** - * Sets FAB as shown or hidden. - * - * @param visible Whether or not to make the container visible. - */ - public void setVisible(boolean visible) { - if (visible) { - mFab.show(); - } else { - mFab.hide(); - } - } - - public void changeIcon(Drawable icon, String description) { - if (mFab.getDrawable() != icon || !mFab.getContentDescription().equals(description)) { - mFab.setImageDrawable(icon); - mFab.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. - mFab.setTranslationX(positionOffset * getTranslationXForAlignment(ALIGN_END)); - } - - /** - * 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. - */ - private 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 && mFab.isShown()) { - mFab.animate() - .translationX(translationX + offsetX) - .translationY(offsetY) - .setInterpolator(mFabInterpolator) - .setDuration(mAnimationDuration) - .start(); - } else { - mFab.setTranslationX(translationX + offsetX); - mFab.setTranslationY(offsetY); - } - } - - /** - * 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) { - setVisible(true); - AnimUtils.scaleIn(mFab, FAB_SCALE_IN_DURATION, delayMs); - AnimUtils.fadeIn(mFab, FAB_SCALE_IN_DURATION, delayMs + FAB_SCALE_IN_FADE_IN_DELAY, null); - } - - /** - * 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(mFab, 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(mFab, 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. - */ - private int getTranslationXForAlignment(int align) { - int result; - 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; - default: - throw Assert.createIllegalStateFailException("Invalid alignment value: " + align); - } - if (isLayoutRtl()) { - result *= -1; - } - return result; - } - - private boolean isLayoutRtl() { - return mFab.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; - } -} diff --git a/java/com/android/dialer/app/DialtactsActivity.java b/java/com/android/dialer/app/DialtactsActivity.java index 5f3620b1c..164c1ea82 100644 --- a/java/com/android/dialer/app/DialtactsActivity.java +++ b/java/com/android/dialer/app/DialtactsActivity.java @@ -38,6 +38,7 @@ import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.FloatingActionButton; +import android.support.design.widget.FloatingActionButton.OnVisibilityChangedListener; import android.support.design.widget.Snackbar; import android.support.v4.app.ActivityCompat; import android.support.v4.view.ViewPager; @@ -67,7 +68,6 @@ import com.android.contacts.common.list.OnPhoneNumberPickerActionListener; import com.android.contacts.common.list.PhoneNumberListAdapter; import com.android.contacts.common.list.PhoneNumberPickerFragment.CursorReranker; import com.android.contacts.common.list.PhoneNumberPickerFragment.OnLoadFinishedListener; -import com.android.contacts.common.widget.FloatingActionButtonController; import com.android.dialer.animation.AnimUtils; import com.android.dialer.animation.AnimationListenerAdapter; import com.android.dialer.app.calllog.CallLogActivity; @@ -98,6 +98,7 @@ import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.callintent.CallSpecificAppData; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.ThreadUtil; import com.android.dialer.configprovider.ConfigProviderBindings; import com.android.dialer.constants.ActivityRequestCodes; import com.android.dialer.contactsfragment.ContactsFragment; @@ -133,6 +134,7 @@ import com.android.dialer.util.PermissionsUtil; import com.android.dialer.util.TouchPointManager; import com.android.dialer.util.TransactionSafeActivity; import com.android.dialer.util.ViewUtil; +import com.android.dialer.widget.FloatingActionButtonController; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -548,7 +550,7 @@ public class DialtactsActivity extends TransactionSafeActivity mDialerDatabaseHelper.startSmartDialUpdateThread(); } if (mIsDialpadShown) { - mFloatingActionButtonController.setVisible(false); + mFloatingActionButtonController.scaleOut(); } else { mFloatingActionButtonController.align(getFabAlignment(), false /* animate */); } @@ -857,7 +859,7 @@ public class DialtactsActivity extends TransactionSafeActivity mFloatingActionButtonController.scaleOut(); maybeEnterSearchUi(); } else { - mFloatingActionButtonController.setVisible(false); + mFloatingActionButtonController.scaleOut(); maybeEnterSearchUi(); } mActionBarController.onDialpadUp(); @@ -955,7 +957,7 @@ public class DialtactsActivity extends TransactionSafeActivity ft.hide(mDialpadFragment); ft.commit(); } - mFloatingActionButtonController.scaleIn(AnimUtils.NO_DELAY); + mFloatingActionButtonController.scaleIn(); } private void updateSearchFragmentPosition() { @@ -1271,12 +1273,28 @@ public class DialtactsActivity extends TransactionSafeActivity setNotInSearchUi(); - // Restore the FAB for the lists fragment. - if (getFabAlignment() != FloatingActionButtonController.ALIGN_END) { - mFloatingActionButtonController.setVisible(false); + // There are four states the fab can be in: + // - Not visible and should remain not visible (do nothing) + // - Not visible (move then show the fab) + // - Visible, in the correct position (do nothing) + // - Visible, in the wrong position (hide, move, then show the fab) + if (mFloatingActionButtonController.isVisible() + && getFabAlignment() != FloatingActionButtonController.ALIGN_END) { + mFloatingActionButtonController.scaleOut( + new OnVisibilityChangedListener() { + @Override + public void onHidden(FloatingActionButton floatingActionButton) { + super.onHidden(floatingActionButton); + onPageScrolled( + mListsFragment.getCurrentTabIndex(), 0 /* offset */, 0 /* pixelOffset */); + mFloatingActionButtonController.scaleIn(); + } + }); + } else if (!mFloatingActionButtonController.isVisible() && mListsFragment.shouldShowFab()) { + onPageScrolled(mListsFragment.getCurrentTabIndex(), 0 /* offset */, 0 /* pixelOffset */); + ThreadUtil.getUiThreadHandler() + .postDelayed(() -> mFloatingActionButtonController.scaleIn(), FAB_SCALE_IN_DELAY_MS); } - mFloatingActionButtonController.scaleIn(FAB_SCALE_IN_DELAY_MS); - onPageScrolled(mListsFragment.getCurrentTabIndex(), 0 /* offset */, 0 /* pixelOffset */); final FragmentTransaction transaction = getFragmentManager().beginTransaction(); if (mSmartDialSearchFragment != null) { @@ -1527,8 +1545,10 @@ public class DialtactsActivity extends TransactionSafeActivity public void onPageSelected(int position) { updateMissedCalls(); int tabIndex = mListsFragment.getCurrentTabIndex(); + if (tabIndex != mPreviouslySelectedTabIndex) { + mFloatingActionButtonController.scaleIn(); + } mPreviouslySelectedTabIndex = tabIndex; - mFloatingActionButtonController.setVisible(true); timeTabSelected = SystemClock.elapsedRealtime(); } @@ -1563,7 +1583,8 @@ public class DialtactsActivity extends TransactionSafeActivity return mActionBarHeight; } - private int getFabAlignment() { + @VisibleForTesting + public int getFabAlignment() { if (!mIsLandscape && !isInSearchUi() && mListsFragment.getCurrentTabIndex() == DialtactsPagerAdapter.TAB_INDEX_SPEED_DIAL) { diff --git a/java/com/android/dialer/app/calllog/CallLogFragment.java b/java/com/android/dialer/app/calllog/CallLogFragment.java index a82afa5fe..5cb0244c5 100644 --- a/java/com/android/dialer/app/calllog/CallLogFragment.java +++ b/java/com/android/dialer/app/calllog/CallLogFragment.java @@ -602,11 +602,14 @@ public class CallLogFragment extends Fragment public void onVisible() { LogUtil.enterBlock("CallLogFragment.onPageSelected"); if (getActivity() != null && getActivity() instanceof HostInterface) { - ((HostInterface) getActivity()) - .enableFloatingButton(mModalAlertManager == null || mModalAlertManager.isEmpty()); + ((HostInterface) getActivity()).enableFloatingButton(!isModalAlertVisible()); } } + public boolean isModalAlertVisible() { + return mModalAlertManager != null && !mModalAlertManager.isEmpty(); + } + @CallSuper public void onNotVisible() { LogUtil.enterBlock("CallLogFragment.onPageUnselected"); diff --git a/java/com/android/dialer/app/list/ListsFragment.java b/java/com/android/dialer/app/list/ListsFragment.java index a94f9c137..362997a5a 100644 --- a/java/com/android/dialer/app/list/ListsFragment.java +++ b/java/com/android/dialer/app/list/ListsFragment.java @@ -35,6 +35,7 @@ import com.android.contacts.common.list.ViewPagerTabs; import com.android.dialer.app.R; import com.android.dialer.app.calllog.CallLogFragment; import com.android.dialer.app.calllog.CallLogNotificationsService; +import com.android.dialer.app.calllog.VisualVoicemailCallLogFragment; import com.android.dialer.app.voicemail.error.VoicemailStatusCorruptionHandler; import com.android.dialer.app.voicemail.error.VoicemailStatusCorruptionHandler.Source; import com.android.dialer.common.LogUtil; @@ -409,6 +410,16 @@ public class ListsFragment extends Fragment implements OnPageChangeListener, Lis return mTabIndex; } + public boolean shouldShowFab() { + // If the VVM TOS is visible, don't show the fab + if (mCurrentPage instanceof VisualVoicemailCallLogFragment + && ((VisualVoicemailCallLogFragment) mCurrentPage).isModalAlertVisible()) { + return false; + } + + return true; + } + /** * External method to update unread count because the unread count changes when the user expands a * voicemail in the call log or when the user expands an unread call in the call history tab. diff --git a/java/com/android/dialer/dialpadview/DialpadFragment.java b/java/com/android/dialer/dialpadview/DialpadFragment.java index 7dd557863..c4e6c61d8 100644 --- a/java/com/android/dialer/dialpadview/DialpadFragment.java +++ b/java/com/android/dialer/dialpadview/DialpadFragment.java @@ -69,7 +69,6 @@ import android.widget.RelativeLayout; import android.widget.TextView; import com.android.contacts.common.dialog.CallSubjectDialog; import com.android.contacts.common.util.StopWatch; -import com.android.contacts.common.widget.FloatingActionButtonController; import com.android.dialer.animation.AnimUtils; import com.android.dialer.callintent.CallInitiationType; import com.android.dialer.callintent.CallIntentBuilder; @@ -79,6 +78,7 @@ import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DialerExecutor; import com.android.dialer.common.concurrent.DialerExecutor.Worker; import com.android.dialer.common.concurrent.DialerExecutors; +import com.android.dialer.common.concurrent.ThreadUtil; import com.android.dialer.location.GeoUtil; import com.android.dialer.logging.UiAction; import com.android.dialer.oem.MotorolaUtils; @@ -88,6 +88,7 @@ import com.android.dialer.telecom.TelecomUtil; import com.android.dialer.util.CallUtil; import com.android.dialer.util.DialerUtils; import com.android.dialer.util.PermissionsUtil; +import com.android.dialer.widget.FloatingActionButtonController; import java.util.HashSet; import java.util.List; @@ -1193,7 +1194,7 @@ public class DialpadFragment extends Fragment mOverflowPopupMenu.dismiss(); } - mFloatingActionButtonController.setVisible(false); + mFloatingActionButtonController.scaleOut(); mDialpadChooser.setVisibility(View.VISIBLE); // Instantiate the DialpadChooserAdapter and hook it up to the @@ -1207,6 +1208,7 @@ public class DialpadFragment extends Fragment if (mDialpadView != null) { LogUtil.i("DialpadFragment.showDialpadChooser", "mDialpadView not null"); mDialpadView.setVisibility(View.VISIBLE); + mFloatingActionButtonController.scaleIn(); } else { LogUtil.i("DialpadFragment.showDialpadChooser", "mDialpadView null"); mDigits.setVisibility(View.VISIBLE); @@ -1217,7 +1219,7 @@ public class DialpadFragment extends Fragment if (!mFloatingActionButtonController.isVisible()) { // Just call 'scaleIn()' method if the mFloatingActionButtonController was not already // previously visible. - mFloatingActionButtonController.scaleIn(0); + mFloatingActionButtonController.scaleIn(); } mDialpadChooser.setVisibility(View.GONE); } @@ -1433,17 +1435,15 @@ public class DialpadFragment extends Fragment if (mAnimate) { dialpadView.animateShow(); } - mFloatingActionButtonController.setVisible(false); - mFloatingActionButtonController.scaleIn(mAnimate ? mDialpadSlideInDuration : 0); + ThreadUtil.getUiThreadHandler() + .postDelayed( + () -> mFloatingActionButtonController.scaleIn(), + mAnimate ? mDialpadSlideInDuration : 0); FragmentUtils.getParentUnsafe(this, DialpadListener.class).onDialpadShown(); mDigits.requestFocus(); } if (hidden) { - if (mAnimate) { - mFloatingActionButtonController.scaleOut(); - } else { - mFloatingActionButtonController.setVisible(false); - } + mFloatingActionButtonController.scaleOut(); } } diff --git a/java/com/android/dialer/widget/FloatingActionButtonController.java b/java/com/android/dialer/widget/FloatingActionButtonController.java new file mode 100644 index 000000000..a0c4e6ddd --- /dev/null +++ b/java/com/android/dialer/widget/FloatingActionButtonController.java @@ -0,0 +1,190 @@ +/* + * 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.dialer.widget; + +import android.app.Activity; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.support.design.widget.FloatingActionButton; +import android.support.design.widget.FloatingActionButton.OnVisibilityChangedListener; +import android.view.View; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import com.android.contacts.common.R; +import com.android.dialer.common.Assert; + +/** 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 final int mAnimationDuration; + private final int mFloatingActionButtonWidth; + private final int mFloatingActionButtonMarginRight; + private final FloatingActionButton mFab; + private final Interpolator mFabInterpolator; + private int mScreenWidth; + + public FloatingActionButtonController(Activity activity, FloatingActionButton fab) { + 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); + mFab = fab; + } + + /** + * 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; + } + + /** @see FloatingActionButton#isShown() */ + public boolean isVisible() { + return mFab.isShown(); + } + + /** + * Sets FAB as shown or hidden. + * + * @see #scaleIn() + * @see #scaleOut() + */ + public void setVisible(boolean visible) { + if (visible) { + scaleIn(); + } else { + scaleOut(); + } + } + + public void changeIcon(Drawable icon, String description) { + if (mFab.getDrawable() != icon || !mFab.getContentDescription().equals(description)) { + mFab.setImageDrawable(icon); + mFab.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. + mFab.setTranslationX(positionOffset * getTranslationXForAlignment(ALIGN_END)); + } + + /** + * 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. + */ + private 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 && mFab.isShown()) { + mFab.animate() + .translationX(translationX + offsetX) + .translationY(offsetY) + .setInterpolator(mFabInterpolator) + .setDuration(mAnimationDuration) + .start(); + } else { + mFab.setTranslationX(translationX + offsetX); + mFab.setTranslationY(offsetY); + } + } + + /** @see FloatingActionButton#show() */ + public void scaleIn() { + mFab.show(); + } + + /** @see FloatingActionButton#hide() */ + public void scaleOut() { + mFab.hide(); + } + + public void scaleOut(OnVisibilityChangedListener listener) { + mFab.hide(listener); + } + + /** + * 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. + */ + private int getTranslationXForAlignment(int align) { + int result; + 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; + default: + throw Assert.createIllegalStateFailException("Invalid alignment value: " + align); + } + if (isLayoutRtl()) { + result *= -1; + } + return result; + } + + private boolean isLayoutRtl() { + return mFab.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + } +} -- cgit v1.2.3