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(); + } }