diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java index 08857b7f94..8a11b57c11 100644 --- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java +++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java @@ -27,11 +27,13 @@ import android.util.AttributeSet; import android.view.View; import android.widget.ImageView; +import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.constraintlayout.widget.ConstraintLayout; import com.android.launcher3.R; +import com.android.launcher3.util.Preconditions; import com.android.quickstep.util.BorderAnimator; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -43,7 +45,9 @@ import java.util.function.Consumer; */ public class KeyboardQuickSwitchTaskView extends ConstraintLayout { - @NonNull private final BorderAnimator mBorderAnimator; + @ColorInt private final int mBorderColor; + + @Nullable private BorderAnimator mBorderAnimator; @Nullable private ImageView mThumbnailView1; @Nullable private ImageView mThumbnailView2; @@ -74,29 +78,9 @@ public class KeyboardQuickSwitchTaskView extends ConstraintLayout { attrs, R.styleable.TaskView, defStyleAttr, defStyleRes); setWillNotDraw(false); - Resources resources = context.getResources(); - mBorderAnimator = new BorderAnimator( - /* borderBoundsBuilder= */ bounds -> bounds.set(0, 0, getWidth(), getHeight()), - /* borderWidthPx= */ resources.getDimensionPixelSize( - R.dimen.keyboard_quick_switch_border_width), - /* borderRadiusPx= */ resources.getDimensionPixelSize( - R.dimen.keyboard_quick_switch_task_view_radius), - /* borderColor= */ ta.getColor( - R.styleable.TaskView_borderColor, DEFAULT_BORDER_COLOR), - /* invalidateViewCallback= */ KeyboardQuickSwitchTaskView.this::invalidate, - /* viewScaleTargetProvider= */ new BorderAnimator.ViewScaleTargetProvider() { - @NonNull - @Override - public View getContainerView() { - return KeyboardQuickSwitchTaskView.this; - } - @NonNull - @Override - public View getContentView() { - return mContent; - } - }); + mBorderColor = ta.getColor( + R.styleable.TaskView_borderColor, DEFAULT_BORDER_COLOR); ta.recycle(); } @@ -108,17 +92,34 @@ public class KeyboardQuickSwitchTaskView extends ConstraintLayout { mIcon1 = findViewById(R.id.icon1); mIcon2 = findViewById(R.id.icon2); mContent = findViewById(R.id.content); + + Resources resources = mContext.getResources(); + + Preconditions.assertNotNull(mContent); + mBorderAnimator = new BorderAnimator( + /* borderRadiusPx= */ resources.getDimensionPixelSize( + R.dimen.keyboard_quick_switch_task_view_radius), + /* borderColor= */ mBorderColor, + /* borderAnimationParams= */ new BorderAnimator.ScalingParams( + /* borderWidthPx= */ resources.getDimensionPixelSize( + R.dimen.keyboard_quick_switch_border_width), + /* boundsBuilder= */ bounds -> bounds.set( + 0, 0, getWidth(), getHeight()), + /* targetView= */ this, + /* contentView= */ mContent)); } - @NonNull + @Nullable protected Animator getFocusAnimator(boolean focused) { - return mBorderAnimator.buildAnimator(focused); + return mBorderAnimator == null ? null : mBorderAnimator.buildAnimator(focused); } @Override public void draw(Canvas canvas) { super.draw(canvas); - mBorderAnimator.drawBorder(canvas); + if (mBorderAnimator != null) { + mBorderAnimator.drawBorder(canvas); + } } protected void setThumbnails( diff --git a/quickstep/src/com/android/quickstep/util/BorderAnimator.java b/quickstep/src/com/android/quickstep/util/BorderAnimator.java index c43fb27f05..011d45c8e7 100644 --- a/quickstep/src/com/android/quickstep/util/BorderAnimator.java +++ b/quickstep/src/com/android/quickstep/util/BorderAnimator.java @@ -20,6 +20,7 @@ import android.animation.AnimatorListenerAdapter; import android.annotation.ColorInt; import android.annotation.Nullable; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.view.View; @@ -37,8 +38,8 @@ import com.android.launcher3.anim.Interpolators; *

* To use this class: * 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. + * provided border bounds. See {@link SimpleParams} and {@link ScalingParams} to determine + * which would be best for your target view. * 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 @@ -46,7 +47,7 @@ import com.android.launcher3.anim.Interpolators; */ public final class BorderAnimator { - public static final int DEFAULT_BORDER_COLOR = 0xffffffff; + public static final int DEFAULT_BORDER_COLOR = Color.WHITE; private static final long DEFAULT_APPEARANCE_ANIMATION_DURATION_MS = 300; private static final long DEFAULT_DISAPPEARANCE_ANIMATION_DURATION_MS = 133; @@ -54,68 +55,44 @@ public final class BorderAnimator { @NonNull private final AnimatedFloat mBorderAnimationProgress = new AnimatedFloat( this::updateOutline); - @NonNull private final Rect mBorderBounds = new Rect(); - @NonNull private final BorderBoundsBuilder mBorderBoundsBuilder; - @Px private final int mBorderWidthPx; @Px private final int mBorderRadiusPx; - @NonNull private final Runnable mInvalidateViewCallback; - @Nullable private final ViewScaleTargetProvider mViewScaleTargetProvider; + @NonNull private final BorderAnimationParams mBorderAnimationParams; private final long mAppearanceDurationMs; private final long mDisappearanceDurationMs; @NonNull private final Interpolator mInterpolator; @NonNull private final Paint mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - private float mAlignmentAdjustment; - @Nullable private Animator mRunningBorderAnimation; public BorderAnimator( - @NonNull BorderBoundsBuilder borderBoundsBuilder, - int borderWidthPx, - int borderRadiusPx, + @Px int borderRadiusPx, @ColorInt int borderColor, - @NonNull Runnable invalidateViewCallback) { - this(borderBoundsBuilder, - borderWidthPx, - borderRadiusPx, + @NonNull BorderAnimationParams borderAnimationParams) { + this(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, + borderAnimationParams, DEFAULT_APPEARANCE_ANIMATION_DURATION_MS, DEFAULT_DISAPPEARANCE_ANIMATION_DURATION_MS, DEFAULT_INTERPOLATOR); } + /** + * @param borderRadiusPx the radius of the border's corners, in pixels + * @param borderColor the border's color + * @param borderAnimationParams params for handling different target view layout situation. + * @param appearanceDurationMs appearance animation duration, in milliseconds + * @param disappearanceDurationMs disappearance animation duration, in milliseconds + * @param interpolator animation interpolator + */ public BorderAnimator( - @NonNull BorderBoundsBuilder borderBoundsBuilder, - int borderWidthPx, - int borderRadiusPx, + @Px int borderRadiusPx, @ColorInt int borderColor, - @NonNull Runnable invalidateViewCallback, - @Nullable ViewScaleTargetProvider viewScaleTargetProvider, + @NonNull BorderAnimationParams borderAnimationParams, long appearanceDurationMs, long disappearanceDurationMs, @NonNull Interpolator interpolator) { - mBorderBoundsBuilder = borderBoundsBuilder; - mBorderWidthPx = borderWidthPx; mBorderRadiusPx = borderRadiusPx; - mInvalidateViewCallback = invalidateViewCallback; - mViewScaleTargetProvider = viewScaleTargetProvider; + mBorderAnimationParams = borderAnimationParams; mAppearanceDurationMs = appearanceDurationMs; mDisappearanceDurationMs = disappearanceDurationMs; mInterpolator = interpolator; @@ -128,15 +105,11 @@ public final class BorderAnimator { private void updateOutline() { float interpolatedProgress = mInterpolator.getInterpolation( mBorderAnimationProgress.value); - float borderWidth = mBorderWidthPx * interpolatedProgress; - // 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); + mBorderAnimationParams.setProgress(interpolatedProgress); mBorderPaint.setAlpha(Math.round(255 * interpolatedProgress)); - mBorderPaint.setStrokeWidth(borderWidth); - mInvalidateViewCallback.run(); + mBorderPaint.setStrokeWidth(mBorderAnimationParams.getBorderWidth()); + mBorderAnimationParams.mTargetView.invalidate(); } /** @@ -146,16 +119,14 @@ 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; + float alignmentAdjustment = mBorderAnimationParams.getAlignmentAdjustment(); canvas.drawRoundRect( - /* left= */ mBorderBounds.left + mAlignmentAdjustment, - /* top= */ mBorderBounds.top + mAlignmentAdjustment, - /* right= */ mBorderBounds.right - mAlignmentAdjustment, - /* bottom= */ mBorderBounds.bottom - mAlignmentAdjustment, - /* rx= */ mBorderRadiusPx + radiusAdjustment, - /* ry= */ mBorderRadiusPx + radiusAdjustment, + /* left= */ mBorderAnimationParams.mBorderBounds.left + alignmentAdjustment, + /* top= */ mBorderAnimationParams.mBorderBounds.top + alignmentAdjustment, + /* right= */ mBorderAnimationParams.mBorderBounds.right - alignmentAdjustment, + /* bottom= */ mBorderAnimationParams.mBorderBounds.bottom - alignmentAdjustment, + /* rx= */ mBorderRadiusPx + mBorderAnimationParams.getRadiusAdjustment(), + /* ry= */ mBorderRadiusPx + mBorderAnimationParams.getRadiusAdjustment(), /* paint= */ mBorderPaint); } @@ -164,7 +135,6 @@ public final class BorderAnimator { */ @NonNull public Animator buildAnimator(boolean isAppearing) { - mBorderBoundsBuilder.updateBorderBounds(mBorderBounds); mRunningBorderAnimation = mBorderAnimationProgress.animateToValue(isAppearing ? 1f : 0f); mRunningBorderAnimation.setDuration( isAppearing ? mAppearanceDurationMs : mDisappearanceDurationMs); @@ -172,7 +142,7 @@ public final class BorderAnimator { mRunningBorderAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { - setViewScales(); + mBorderAnimationParams.onShowBorder(); } }); mRunningBorderAnimation.addListener( @@ -181,7 +151,7 @@ public final class BorderAnimator { if (isAppearing) { return; } - resetViewScales(); + mBorderAnimationParams.onHideBorder(); })); return mRunningBorderAnimation; @@ -196,56 +166,15 @@ public final class BorderAnimator { if (mRunningBorderAnimation != null) { mRunningBorderAnimation.end(); } - mBorderBoundsBuilder.updateBorderBounds(mBorderBounds); if (visible) { - setViewScales(); + mBorderAnimationParams.onShowBorder(); } mBorderAnimationProgress.updateValue(visible ? 1f : 0f); if (!visible) { - resetViewScales(); + mBorderAnimationParams.onHideBorder(); } } - 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); - } - /** * Callback to update the border bounds when building this animation. */ @@ -258,23 +187,166 @@ public final class BorderAnimator { } /** - * Provider for scaling target views for the beginning and end of this animation. + * Params for handling different target view layout situation. */ - public interface ViewScaleTargetProvider { + private abstract static class BorderAnimationParams { + + @NonNull private final Rect mBorderBounds = new Rect(); + @NonNull private final BorderBoundsBuilder mBoundsBuilder; + + @NonNull final View mTargetView; + @Px final int mBorderWidthPx; + + private float mAnimationProgress = 0f; + @Nullable private View.OnLayoutChangeListener mLayoutChangeListener; /** - * Returns the content view's container. This view will be scaled up to make room for the - * border. + * @param borderWidthPx the width of the border, in pixels + * @param boundsBuilder callback to update the border bounds + * @param targetView the view that will be drawing the border */ - @NonNull - View getContainerView(); + private BorderAnimationParams( + @Px int borderWidthPx, + @NonNull BorderBoundsBuilder boundsBuilder, + @NonNull View targetView) { + mBorderWidthPx = borderWidthPx; + mBoundsBuilder = boundsBuilder; + mTargetView = targetView; + } + + private void setProgress(float progress) { + mAnimationProgress = progress; + } + + private float getBorderWidth() { + return mBorderWidthPx * mAnimationProgress; + } + + float getAlignmentAdjustment() { + // Outset the border by half the width to create an outwards-growth animation + return (-getBorderWidth() / 2f) + getAlignmentAdjustmentInset(); + } + + + void onShowBorder() { + if (mLayoutChangeListener == null) { + mLayoutChangeListener = + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + onShowBorder(); + mTargetView.invalidate(); + }; + mTargetView.addOnLayoutChangeListener(mLayoutChangeListener); + } + mBoundsBuilder.updateBorderBounds(mBorderBounds); + } + + void onHideBorder() { + if (mLayoutChangeListener != null) { + mTargetView.removeOnLayoutChangeListener(mLayoutChangeListener); + mLayoutChangeListener = null; + } + } + + abstract int getAlignmentAdjustmentInset(); + + abstract float getRadiusAdjustment(); + } + + /** + * Use an instance of this {@link BorderAnimationParams} if the border can be drawn outside the + * target view's bounds without any additional logic. + */ + public static final class SimpleParams extends BorderAnimationParams { + + public SimpleParams( + @Px int borderWidthPx, + @NonNull BorderBoundsBuilder boundsBuilder, + @NonNull View targetView) { + super(borderWidthPx, boundsBuilder, targetView); + } + + @Override + int getAlignmentAdjustmentInset() { + return 0; + } + + @Override + float getRadiusAdjustment() { + return -getAlignmentAdjustment(); + } + } + + /** + * Use an instance of this {@link BorderAnimationParams} if the border would other be clipped by + * the target view's bound. + *

+ * Note: using these params will set the scales and pivots of the + * container and content views, however will only reset the scales back to 1. + */ + public static final class ScalingParams extends BorderAnimationParams { + + @NonNull private final View mContentView; /** - * 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. + * @param targetView the view that will be drawing the border. this view will be scaled up + * to make room for the border + * @param contentView the view around which the border will be drawn. this view will be + * scaled down reciprocally to keep its original size and location. */ - @NonNull - View getContentView(); + public ScalingParams( + @Px int borderWidthPx, + @NonNull BorderBoundsBuilder boundsBuilder, + @NonNull View targetView, + @NonNull View contentView) { + super(borderWidthPx, boundsBuilder, targetView); + mContentView = contentView; + } + + @Override + void onShowBorder() { + super.onShowBorder(); + float width = mTargetView.getWidth(); + float height = mTargetView.getHeight(); + // Scale up just enough to make room for the border. Fail fast and fix the scaling + // onLayout. + float scaleX = width == 0 ? 1f : 1f + ((2 * mBorderWidthPx) / width); + float scaleY = height == 0 ? 1f : 1f + ((2 * mBorderWidthPx) / height); + + mTargetView.setPivotX(width / 2); + mTargetView.setPivotY(height / 2); + mTargetView.setScaleX(scaleX); + mTargetView.setScaleY(scaleY); + + mContentView.setPivotX(mContentView.getWidth() / 2f); + mContentView.setPivotY(mContentView.getHeight() / 2f); + mContentView.setScaleX(1f / scaleX); + mContentView.setScaleY(1f / scaleY); + } + + @Override + void onHideBorder() { + super.onHideBorder(); + mTargetView.setPivotX(mTargetView.getWidth()); + mTargetView.setPivotY(mTargetView.getHeight()); + mTargetView.setScaleX(1f); + mTargetView.setScaleY(1f); + + mContentView.setPivotX(mContentView.getWidth() / 2f); + mContentView.setPivotY(mContentView.getHeight() / 2f); + mContentView.setScaleX(1f); + mContentView.setScaleY(1f); + } + + @Override + int getAlignmentAdjustmentInset() { + // Inset the border since we are scaling the container up + return mBorderWidthPx; + } + + @Override + float getRadiusAdjustment() { + // Increase the radius since we are scaling the container up + return getAlignmentAdjustment(); + } } } diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index c47c946a35..6e7b6dce62 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -445,13 +445,14 @@ public class TaskView extends FrameLayout implements Reusable { mBorderAnimator = !keyboardFocusHighlightEnabled ? null : new BorderAnimator( - /* borderBoundsBuilder= */ this::updateBorderBounds, - /* borderWidthPx= */ context.getResources().getDimensionPixelSize( - R.dimen.keyboard_quick_switch_border_width), /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius, /* borderColor= */ ta.getColor( R.styleable.TaskView_borderColor, DEFAULT_BORDER_COLOR), - /* invalidateViewCallback= */ TaskView.this::invalidate); + /* borderAnimationParams= */ new BorderAnimator.SimpleParams( + /* borderWidthPx= */ context.getResources().getDimensionPixelSize( + R.dimen.keyboard_quick_switch_border_width), + /* boundsBuilder= */ this::updateBorderBounds, + /* targetView= */ this)); ta.recycle(); }