diff --git a/res/drawable/work_mode_fab_background.xml b/res/drawable/work_mode_fab_background.xml index 6be33e86ce..fd948d1445 100644 --- a/res/drawable/work_mode_fab_background.xml +++ b/res/drawable/work_mode_fab_background.xml @@ -19,6 +19,9 @@ + diff --git a/res/layout/work_mode_fab.xml b/res/layout/work_mode_fab.xml index b3484c9410..fc59e568d3 100644 --- a/res/layout/work_mode_fab.xml +++ b/res/layout/work_mode_fab.xml @@ -23,18 +23,16 @@ android:gravity="center_vertical" android:background="@drawable/work_mode_fab_background" android:forceHasOverlappingRendering="false" - android:contentDescription="@string/work_apps_pause_btn_text" - android:paddingStart="@dimen/work_mode_fab_background_start_padding" - android:paddingEnd="@dimen/work_mode_fab_background_end_padding" - android:animateLayoutChanges="true"> + android:contentDescription="@string/work_apps_pause_btn_text"> diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 731e24e5a2..037687dec1 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -156,15 +156,16 @@ 56dp 16dp 24dp - 12dp - 16dp + 16dp + 4dp + 8dp + 10dp 10dp 214dp 52dp 16dp 20dp - 16dp - 4dp + 16dp 20dp 16dp 16dp diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java index 60495742fb..f1f72b267b 100644 --- a/src/com/android/launcher3/allapps/WorkModeSwitch.java +++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java @@ -15,10 +15,18 @@ */ package com.android.launcher3.allapps; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Rect; +import android.text.TextUtils; import android.util.AttributeSet; +import android.view.ViewGroup; import android.view.WindowInsets; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -26,10 +34,12 @@ import androidx.annotation.NonNull; import androidx.core.graphics.Insets; import androidx.core.view.WindowInsetsCompat; +import com.android.app.animation.Interpolators; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Insettable; import com.android.launcher3.R; import com.android.launcher3.Utilities; +import com.android.launcher3.anim.AnimatedPropertySetter; import com.android.launcher3.anim.KeyboardInsetAnimationCallback; import com.android.launcher3.model.StringCache; import com.android.launcher3.views.ActivityContext; @@ -39,9 +49,14 @@ import com.android.launcher3.views.ActivityContext; public class WorkModeSwitch extends LinearLayout implements Insettable, KeyboardInsetAnimationCallback.KeyboardInsetListener { + private static final int TEXT_EXPAND_OPACITY_DURATION = 300; + private static final int TEXT_COLLAPSE_OPACITY_DURATION = 50; + private static final int EXPAND_COLLAPSE_DURATION = 300; + private static final int TEXT_ALPHA_EXPAND_DELAY = 80; + private static final int TEXT_ALPHA_COLLAPSE_DELAY = 0; private static final int FLAG_FADE_ONGOING = 1 << 1; private static final int FLAG_TRANSLATION_ONGOING = 1 << 2; - private static final int FLAG_PROFILE_TOGGLE_ONGOING = 1 << 3; + private static final int FLAG_IS_EXPAND = 1 << 3; private static final int SCROLL_THRESHOLD_DP = 10; private final Rect mInsets = new Rect(); @@ -49,11 +64,15 @@ public class WorkModeSwitch extends LinearLayout implements Insettable, private int mFlags; private final ActivityContext mActivityContext; private final Context mContext; + private final int mTextMarginStart; + private final int mTextMarginEnd; + private final int mIconMarginStart; // Threshold when user scrolls up/down to determine when should button extend/collapse private final int mScrollThreshold; private TextView mTextView; - + private ImageView mIcon; + private ValueAnimator mPauseFABAnim; public WorkModeSwitch(@NonNull Context context) { this(context, null, 0); @@ -68,6 +87,12 @@ public class WorkModeSwitch extends LinearLayout implements Insettable, mContext = context; mScrollThreshold = Utilities.dpToPx(SCROLL_THRESHOLD_DP); mActivityContext = ActivityContext.lookupContext(getContext()); + mTextMarginStart = mContext.getResources().getDimensionPixelSize( + R.dimen.work_fab_text_start_margin); + mTextMarginEnd = mContext.getResources().getDimensionPixelSize( + R.dimen.work_fab_text_end_margin); + mIconMarginStart = mContext.getResources().getDimensionPixelSize( + R.dimen.work_fab_icon_start_margin_expanded); } @Override @@ -75,11 +100,13 @@ public class WorkModeSwitch extends LinearLayout implements Insettable, super.onFinishInflate(); mTextView = findViewById(R.id.pause_text); + mIcon = findViewById(R.id.work_icon); setSelected(true); KeyboardInsetAnimationCallback keyboardInsetAnimationCallback = new KeyboardInsetAnimationCallback(this); setWindowInsetsAnimationCallback(keyboardInsetAnimationCallback); - + // Expand is the default state upon initialization. + addFlag(FLAG_IS_EXPAND); setInsets(mActivityContext.getDeviceProfile().getInsets()); updateStringFromCache(); } @@ -114,18 +141,18 @@ public class WorkModeSwitch extends LinearLayout implements Insettable, @Override public boolean isEnabled() { - return super.isEnabled() && getVisibility() == VISIBLE && mFlags == 0; + return super.isEnabled() && getVisibility() == VISIBLE; } public void animateVisibility(boolean visible) { clearAnimation(); if (visible) { - setFlag(FLAG_FADE_ONGOING); + addFlag(FLAG_FADE_ONGOING); setVisibility(VISIBLE); extend(); animate().alpha(1).withEndAction(() -> removeFlag(FLAG_FADE_ONGOING)).start(); } else if (getVisibility() != GONE) { - setFlag(FLAG_FADE_ONGOING); + addFlag(FLAG_FADE_ONGOING); animate().alpha(0).withEndAction(() -> { removeFlag(FLAG_FADE_ONGOING); setVisibility(GONE); @@ -156,6 +183,79 @@ public class WorkModeSwitch extends LinearLayout implements Insettable, super.setTranslationY(Math.min(translationY, -mInsets.bottom)); } + + private void animatePillTransition(boolean isExpanding) { + if (!shouldAnimate(isExpanding)) { + return; + } + AnimatorSet animatorSet = new AnimatedPropertySetter().buildAnim(); + mTextView.measure(0,0); + int currentWidth = mTextView.getWidth(); + int fullWidth = mTextView.getMeasuredWidth(); + float from = isExpanding ? 0 : currentWidth; + float to = isExpanding ? fullWidth : 0; + mPauseFABAnim = ObjectAnimator.ofFloat(from, to); + mPauseFABAnim.setDuration(EXPAND_COLLAPSE_DURATION); + mPauseFABAnim.setInterpolator(Interpolators.STANDARD); + mPauseFABAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator valueAnimator) { + float translation = (float) valueAnimator.getAnimatedValue(); + float translationFraction = translation / fullWidth; + ViewGroup.MarginLayoutParams textViewLayoutParams = + (ViewGroup.MarginLayoutParams) mTextView.getLayoutParams(); + textViewLayoutParams.width = (int) translation; + textViewLayoutParams.setMarginStart((int) (mTextMarginStart * translationFraction)); + textViewLayoutParams.setMarginEnd((int) (mTextMarginEnd * translationFraction)); + mTextView.setLayoutParams(textViewLayoutParams); + ViewGroup.MarginLayoutParams iconLayoutParams = + (ViewGroup.MarginLayoutParams) mIcon.getLayoutParams(); + iconLayoutParams.setMarginStart((int) (mIconMarginStart * translationFraction)); + mIcon.setLayoutParams(iconLayoutParams); + } + }); + mPauseFABAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + if (isExpanding) { + addFlag(FLAG_IS_EXPAND); + } else { + mTextView.setVisibility(GONE); + removeFlag(FLAG_IS_EXPAND); + } + mTextView.setHorizontallyScrolling(false); + mTextView.setEllipsize(TextUtils.TruncateAt.END); + } + + @Override + public void onAnimationStart(Animator animator) { + mTextView.setHorizontallyScrolling(true); + mTextView.setVisibility(VISIBLE); + mTextView.setEllipsize(null); + } + }); + animatorSet.playTogether(mPauseFABAnim, updatePauseTextAlpha(isExpanding)); + animatorSet.start(); + } + + + private ValueAnimator updatePauseTextAlpha(boolean expand) { + float from = expand ? 0 : 1; + float to = expand ? 1 : 0; + ValueAnimator alphaAnim = ObjectAnimator.ofFloat(from, to); + alphaAnim.setDuration(expand ? TEXT_EXPAND_OPACITY_DURATION + : TEXT_COLLAPSE_OPACITY_DURATION); + alphaAnim.setStartDelay(expand ? TEXT_ALPHA_EXPAND_DELAY : TEXT_ALPHA_COLLAPSE_DELAY); + alphaAnim.setInterpolator(Interpolators.LINEAR); + alphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator valueAnimator) { + mTextView.setAlpha((float) valueAnimator.getAnimatedValue()); + } + }); + return alphaAnim; + } + private void setInsets(Rect rect, Insets insets) { rect.set(insets.left, insets.top, insets.right, insets.bottom); } @@ -166,7 +266,7 @@ public class WorkModeSwitch extends LinearLayout implements Insettable, @Override public void onTranslationStart() { - setFlag(FLAG_TRANSLATION_ONGOING); + addFlag(FLAG_TRANSLATION_ONGOING); } @Override @@ -174,7 +274,7 @@ public class WorkModeSwitch extends LinearLayout implements Insettable, removeFlag(FLAG_TRANSLATION_ONGOING); } - private void setFlag(int flag) { + private void addFlag(int flag) { mFlags |= flag; } @@ -182,12 +282,25 @@ public class WorkModeSwitch extends LinearLayout implements Insettable, mFlags &= ~flag; } + private boolean containsFlag(int flag) { + return (mFlags & flag) == flag; + } + public void extend() { - mTextView.setVisibility(VISIBLE); + animatePillTransition(true); } public void shrink(){ - mTextView.setVisibility(GONE); + animatePillTransition(false); + } + + /** + * Determines if the button should animate based on current state. It should animate the button + * only if it is not in the same state it is animating to. + */ + private boolean shouldAnimate(boolean expanding) { + return expanding != containsFlag(FLAG_IS_EXPAND) + && (mPauseFABAnim == null || !mPauseFABAnim.isRunning()); } public int getScrollThreshold() {