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
diff --git a/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml b/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml
index ebcbdcd..04e87be 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">
- <include
- layout="@layout/keyboard_quick_switch_thumbnail"
- android:id="@+id/thumbnail1"
- android:layout_width="0dp"
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/content"
+ android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/thumbnail2"/>
+ app:layout_constraintEnd_toEndOf="parent">
- <include
- layout="@layout/keyboard_quick_switch_thumbnail"
- android:id="@+id/thumbnail2"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:visibility="gone"
- android:layout_marginStart="@dimen/keyboard_quick_switch_split_view_spacing"
+ <include
+ layout="@layout/keyboard_quick_switch_thumbnail"
+ android:id="@+id/thumbnail1"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toEndOf="@id/thumbnail1"
- 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"/>
+
+ <include
+ layout="@layout/keyboard_quick_switch_thumbnail"
+ android:id="@+id/thumbnail2"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ android:layout_marginStart="@dimen/keyboard_quick_switch_split_view_spacing"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@id/thumbnail1"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
</com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView>
diff --git a/quickstep/res/layout/keyboard_quick_switch_overview.xml b/quickstep/res/layout/keyboard_quick_switch_overview.xml
index 8c97c68..062a9c9 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">
- <ImageView
- android:id="@+id/icon"
- android:layout_width="@dimen/keyboard_quick_switch_recents_icon_size"
- android:layout_height="@dimen/keyboard_quick_switch_recents_icon_size"
- android:layout_marginBottom="8dp"
- android:src="@drawable/ic_empty_recents"
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/keyboard_quick_switch_overview_button_background"
- app:tint="?androidprv:attr/materialColorOnSurface"
- app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/text"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
-
- <TextView
- style="@style/KeyboardQuickSwitchOverview"
- android:id="@+id/text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAlignment="center"
-
- app:layout_constraintTop_toBottomOf="@id/icon"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"/>
+ app:layout_constraintEnd_toEndOf="parent">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="@dimen/keyboard_quick_switch_recents_icon_size"
+ android:layout_height="@dimen/keyboard_quick_switch_recents_icon_size"
+ android:layout_marginBottom="8dp"
+ android:src="@drawable/ic_empty_recents"
+
+ app:tint="?androidprv:attr/materialColorOnSurface"
+ app:layout_constraintVertical_chainStyle="packed"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/text"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <TextView
+ style="@style/KeyboardQuickSwitchOverview"
+ android:id="@+id/text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAlignment="center"
+
+ app:layout_constraintTop_toBottomOf="@id/icon"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
</com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView>
diff --git a/quickstep/res/layout/keyboard_quick_switch_taskview.xml b/quickstep/res/layout/keyboard_quick_switch_taskview.xml
index 5e2d52a..691df6e 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">
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent">
+
<include
layout="@layout/keyboard_quick_switch_thumbnail"
android:id="@+id/thumbnail1"
@@ -49,4 +59,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
</com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView>
diff --git a/quickstep/res/layout/keyboard_quick_switch_view.xml b/quickstep/res/layout/keyboard_quick_switch_view.xml
index 64d3f67..58c0c40 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 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
+ android:layout_height="wrap_content"
+ android:paddingVertical="@dimen/keyboard_quick_switch_view_spacing"
+ android:clipToPadding="false"/>
</HorizontalScrollView>
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
index 84129fd..926ede1 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.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 @@
@Nullable private ImageView mThumbnailView1;
@Nullable private ImageView mThumbnailView2;
+ @Nullable private View mContent;
public KeyboardQuickSwitchTaskView(@NonNull Context context) {
this(context, null);
@@ -84,7 +86,20 @@
.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 @@
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 c30661c..c43fb27 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 @@
* Utility class for drawing a rounded-rect border around a view.
* <p>
* 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 @@
@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 @@
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 @@
int borderRadiusPx,
@ColorInt int borderColor,
@NonNull Runnable invalidateViewCallback,
+ @Nullable ViewScaleTargetProvider viewScaleTargetProvider,
long appearanceDurationMs,
long disappearanceDurationMs,
@NonNull Interpolator interpolator) {
@@ -93,6 +115,7 @@
mBorderWidthPx = borderWidthPx;
mBorderRadiusPx = borderRadiusPx;
mInvalidateViewCallback = invalidateViewCallback;
+ mViewScaleTargetProvider = viewScaleTargetProvider;
mAppearanceDurationMs = appearanceDurationMs;
mDisappearanceDurationMs = disappearanceDurationMs;
mInterpolator = interpolator;
@@ -106,8 +129,10 @@
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 @@
* 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 @@
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.
- *
+ * <p>
* 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 @@
*/
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();
+ }
}