From 5e68914621809861ea93e321d184a6abed762014 Mon Sep 17 00:00:00 2001 From: Schneider Victor-tulias Date: Thu, 6 Apr 2023 14:04:48 -0700 Subject: [PATCH] Update the BorderAnimator Updated the BorderAnimator to allow it to animate the border outside the view's bounds. This makes it match the specs and makes it more compatible with other animations. Flag: ENABLE_KEYBOARD_QUICK_SWITCH Test: tried keyboard quick switching on a handheld, foldable and tablet Fixes: 276336349 Change-Id: I025f8b0f431e78bcb5c7b4b3859a7d6dde5da600 --- .../keyboard_quick_switch_taskview.xml | 44 ++++--- .../layout/keyboard_quick_switch_overview.xml | 56 ++++---- .../layout/keyboard_quick_switch_taskview.xml | 12 ++ .../res/layout/keyboard_quick_switch_view.xml | 5 +- .../taskbar/KeyboardQuickSwitchTaskView.java | 18 ++- .../quickstep/util/BorderAnimator.java | 122 +++++++++++++++++- 6 files changed, 209 insertions(+), 48 deletions(-) diff --git a/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml b/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml index ebcbdcd2f2..04e87be412 100644 --- a/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml +++ b/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml @@ -25,28 +25,40 @@ android:clipToOutline="true" launcher:borderColor="?androidprv:attr/materialColorOutline"> - + app:layout_constraintEnd_toEndOf="parent"> - + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toStartOf="@id/thumbnail2"/> + + + + diff --git a/quickstep/res/layout/keyboard_quick_switch_overview.xml b/quickstep/res/layout/keyboard_quick_switch_overview.xml index 8c97c685fe..062a9c9fa1 100644 --- a/quickstep/res/layout/keyboard_quick_switch_overview.xml +++ b/quickstep/res/layout/keyboard_quick_switch_overview.xml @@ -20,35 +20,47 @@ xmlns:launcher="http://schemas.android.com/apk/res-auto" android:layout_width="@dimen/keyboard_quick_switch_taskview_width" android:layout_height="@dimen/keyboard_quick_switch_taskview_height" - android:background="@drawable/keyboard_quick_switch_overview_button_background" android:clipToOutline="true" android:importantForAccessibility="yes" launcher:borderColor="?androidprv:attr/materialColorOutline"> - - - + app:layout_constraintEnd_toEndOf="parent"> + + + + + + diff --git a/quickstep/res/layout/keyboard_quick_switch_taskview.xml b/quickstep/res/layout/keyboard_quick_switch_taskview.xml index 5e2d52a753..691df6e8f6 100644 --- a/quickstep/res/layout/keyboard_quick_switch_taskview.xml +++ b/quickstep/res/layout/keyboard_quick_switch_taskview.xml @@ -25,6 +25,16 @@ android:clipToOutline="true" launcher:borderColor="?androidprv:attr/materialColorOutline"> + + + + diff --git a/quickstep/res/layout/keyboard_quick_switch_view.xml b/quickstep/res/layout/keyboard_quick_switch_view.xml index 64d3f67eda..58c0c40980 100644 --- a/quickstep/res/layout/keyboard_quick_switch_view.xml +++ b/quickstep/res/layout/keyboard_quick_switch_view.xml @@ -18,7 +18,6 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingVertical="@dimen/keyboard_quick_switch_view_spacing" android:layout_marginTop="@dimen/keyboard_quick_switch_margin_top" android:layout_marginHorizontal="@dimen/keyboard_quick_switch_margin_ends" android:background="@drawable/keyboard_quick_switch_view_background" @@ -44,7 +43,9 @@ + android:layout_height="wrap_content" + android:paddingVertical="@dimen/keyboard_quick_switch_view_spacing" + android:clipToPadding="false"/> diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java index 84129fdde4..926ede1edd 100644 --- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java +++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java @@ -23,6 +23,7 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.util.AttributeSet; +import android.view.View; import android.widget.ImageView; import androidx.annotation.NonNull; @@ -45,6 +46,7 @@ public class KeyboardQuickSwitchTaskView extends ConstraintLayout { @Nullable private ImageView mThumbnailView1; @Nullable private ImageView mThumbnailView2; + @Nullable private View mContent; public KeyboardQuickSwitchTaskView(@NonNull Context context) { this(context, null); @@ -84,7 +86,20 @@ public class KeyboardQuickSwitchTaskView extends ConstraintLayout { .getColor( R.styleable.TaskView_borderColor, DEFAULT_BORDER_COLOR), - /* invalidateViewCallback= */ KeyboardQuickSwitchTaskView.this::invalidate); + /* invalidateViewCallback= */ KeyboardQuickSwitchTaskView.this::invalidate, + /* viewScaleTargetProvider= */ new BorderAnimator.ViewScaleTargetProvider() { + @NonNull + @Override + public View getContainerView() { + return KeyboardQuickSwitchTaskView.this; + } + + @NonNull + @Override + public View getContentView() { + return mContent; + } + }); } @Override @@ -93,6 +108,7 @@ public class KeyboardQuickSwitchTaskView extends ConstraintLayout { mThumbnailView1 = findViewById(R.id.thumbnail1); mThumbnailView2 = findViewById(R.id.thumbnail2); + mContent = findViewById(R.id.content); } @NonNull diff --git a/quickstep/src/com/android/quickstep/util/BorderAnimator.java b/quickstep/src/com/android/quickstep/util/BorderAnimator.java index c30661c42c..c43fb27f05 100644 --- a/quickstep/src/com/android/quickstep/util/BorderAnimator.java +++ b/quickstep/src/com/android/quickstep/util/BorderAnimator.java @@ -16,11 +16,13 @@ package com.android.quickstep.util; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.annotation.ColorInt; import android.annotation.Nullable; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; +import android.view.View; import android.view.animation.Interpolator; import androidx.annotation.NonNull; @@ -34,7 +36,9 @@ import com.android.launcher3.anim.Interpolators; * Utility class for drawing a rounded-rect border around a view. *

* To use this class: - * 1. Create an instance in the target view. + * 1. Create an instance in the target view. NOTE: The border will animate outwards from the + * provided border bounds. If the border will not be visible outside of those bounds, then a + * {@link ViewScaleTargetProvider} must be provided in the constructor. * 2. Override the target view's {@link android.view.View#draw(Canvas)} method and call * {@link BorderAnimator#drawBorder(Canvas)} after {@code super.draw(canvas)}. * 3. Call {@link BorderAnimator#buildAnimator(boolean)} and start the animation or call @@ -55,6 +59,7 @@ public final class BorderAnimator { @Px private final int mBorderWidthPx; @Px private final int mBorderRadiusPx; @NonNull private final Runnable mInvalidateViewCallback; + @Nullable private final ViewScaleTargetProvider mViewScaleTargetProvider; private final long mAppearanceDurationMs; private final long mDisappearanceDurationMs; @NonNull private final Interpolator mInterpolator; @@ -75,6 +80,22 @@ public final class BorderAnimator { borderRadiusPx, borderColor, invalidateViewCallback, + /* viewScaleTargetProvider= */ null); + } + + public BorderAnimator( + @NonNull BorderBoundsBuilder borderBoundsBuilder, + int borderWidthPx, + int borderRadiusPx, + @ColorInt int borderColor, + @NonNull Runnable invalidateViewCallback, + @Nullable ViewScaleTargetProvider viewScaleTargetProvider) { + this(borderBoundsBuilder, + borderWidthPx, + borderRadiusPx, + borderColor, + invalidateViewCallback, + viewScaleTargetProvider, DEFAULT_APPEARANCE_ANIMATION_DURATION_MS, DEFAULT_DISAPPEARANCE_ANIMATION_DURATION_MS, DEFAULT_INTERPOLATOR); @@ -86,6 +107,7 @@ public final class BorderAnimator { int borderRadiusPx, @ColorInt int borderColor, @NonNull Runnable invalidateViewCallback, + @Nullable ViewScaleTargetProvider viewScaleTargetProvider, long appearanceDurationMs, long disappearanceDurationMs, @NonNull Interpolator interpolator) { @@ -93,6 +115,7 @@ public final class BorderAnimator { mBorderWidthPx = borderWidthPx; mBorderRadiusPx = borderRadiusPx; mInvalidateViewCallback = invalidateViewCallback; + mViewScaleTargetProvider = viewScaleTargetProvider; mAppearanceDurationMs = appearanceDurationMs; mDisappearanceDurationMs = disappearanceDurationMs; mInterpolator = interpolator; @@ -106,8 +129,10 @@ public final class BorderAnimator { float interpolatedProgress = mInterpolator.getInterpolation( mBorderAnimationProgress.value); float borderWidth = mBorderWidthPx * interpolatedProgress; - // Inset the border by half the width to create an inwards-growth animation - mAlignmentAdjustment = borderWidth / 2f; + // Outset the border by half the width to create an outwards-growth animation + mAlignmentAdjustment = (-borderWidth / 2f) + // Inset the border if we are scaling the container up + + (mViewScaleTargetProvider == null ? 0 : mBorderWidthPx); mBorderPaint.setAlpha(Math.round(255 * interpolatedProgress)); mBorderPaint.setStrokeWidth(borderWidth); @@ -121,13 +146,16 @@ public final class BorderAnimator { * calling super. */ public void drawBorder(Canvas canvas) { + // Increase the radius if we are scaling the container up + float radiusAdjustment = mViewScaleTargetProvider == null + ? -mAlignmentAdjustment : mAlignmentAdjustment; canvas.drawRoundRect( /* left= */ mBorderBounds.left + mAlignmentAdjustment, /* top= */ mBorderBounds.top + mAlignmentAdjustment, /* right= */ mBorderBounds.right - mAlignmentAdjustment, /* bottom= */ mBorderBounds.bottom - mAlignmentAdjustment, - /* rx= */ mBorderRadiusPx - mAlignmentAdjustment, - /* ry= */ mBorderRadiusPx - mAlignmentAdjustment, + /* rx= */ mBorderRadiusPx + radiusAdjustment, + /* ry= */ mBorderRadiusPx + radiusAdjustment, /* paint= */ mBorderPaint); } @@ -141,22 +169,81 @@ public final class BorderAnimator { mRunningBorderAnimation.setDuration( isAppearing ? mAppearanceDurationMs : mDisappearanceDurationMs); + mRunningBorderAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + setViewScales(); + } + }); mRunningBorderAnimation.addListener( - AnimatorListeners.forEndCallback(() -> mRunningBorderAnimation = null)); + AnimatorListeners.forEndCallback(() -> { + mRunningBorderAnimation = null; + if (isAppearing) { + return; + } + resetViewScales(); + })); return mRunningBorderAnimation; } /** * Immediately shows/hides the border without an animation. - * + *

* To animate the appearance/disappearance, see {@link BorderAnimator#buildAnimator(boolean)} */ public void setBorderVisible(boolean visible) { if (mRunningBorderAnimation != null) { mRunningBorderAnimation.end(); } + mBorderBoundsBuilder.updateBorderBounds(mBorderBounds); + if (visible) { + setViewScales(); + } mBorderAnimationProgress.updateValue(visible ? 1f : 0f); + if (!visible) { + resetViewScales(); + } + } + + private void setViewScales() { + if (mViewScaleTargetProvider == null) { + return; + } + View container = mViewScaleTargetProvider.getContainerView(); + float width = container.getWidth(); + float height = container.getHeight(); + // scale up just enough to make room for the border + float scaleX = 1f + ((2 * mBorderWidthPx) / width); + float scaleY = 1f + ((2 * mBorderWidthPx) / height); + + container.setPivotX(width / 2); + container.setPivotY(height / 2); + container.setScaleX(scaleX); + container.setScaleY(scaleY); + + View contentView = mViewScaleTargetProvider.getContentView(); + contentView.setPivotX(contentView.getWidth() / 2f); + contentView.setPivotY(contentView.getHeight() / 2f); + contentView.setScaleX(1f / scaleX); + contentView.setScaleY(1f / scaleY); + } + + private void resetViewScales() { + if (mViewScaleTargetProvider == null) { + return; + } + View container = mViewScaleTargetProvider.getContainerView(); + container.setPivotX(container.getWidth()); + container.setPivotY(container.getHeight()); + container.setScaleX(1f); + container.setScaleY(1f); + + View contentView = mViewScaleTargetProvider.getContentView(); + contentView.setPivotX(contentView.getWidth() / 2f); + contentView.setPivotY(contentView.getHeight() / 2f); + contentView.setScaleX(1f); + contentView.setScaleY(1f); } /** @@ -169,4 +256,25 @@ public final class BorderAnimator { */ void updateBorderBounds(Rect rect); } + + /** + * Provider for scaling target views for the beginning and end of this animation. + */ + public interface ViewScaleTargetProvider { + + /** + * Returns the content view's container. This view will be scaled up to make room for the + * border. + */ + @NonNull + View getContainerView(); + + /** + * Returns the content view. This view will be scaled down reciprocally to the container's + * up-scaling to maintain its original size. This should be the view containing all of the + * content being surrounded by the border. + */ + @NonNull + View getContentView(); + } }