Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7dee27f4 authored by Schneider Victor-tulias's avatar Schneider Victor-tulias
Browse files

Update BorderAnimator to work with layout updates

1. if a 'ViewScaleTargetProvider' is being used, this can cause a crash
2. otherwise, the old border bounds are reapplied, which is likely no longer correct

Updated BorderAnimator to use 'BorderAnimationParams' rather than 'ViewScaleTargetProvider'. This removes some unnecessary null checks while making the util class simpler to use. It also allows us to listen for specific view events for the border animation.

Flag: ENABLE_KEYBOARD_QUICK_SWITCH
Fixes: 283272516
Test: opened the keyboard quick switch view and highlighted a view in recents view, then rotated the screen several times
Change-Id: I7959d6cd892ebcdd2c68163dd56c358815494af6
parent 6597d5ec
Loading
Loading
Loading
Loading
+28 −27
Original line number Diff line number Diff line
@@ -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,18 +92,35 @@ 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);
        if (mBorderAnimator != null) {
            mBorderAnimator.drawBorder(canvas);
        }
    }

    protected void setThumbnails(
            @NonNull Task task1,
+188 −116
Original line number Diff line number Diff line
@@ -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;
 * <p>
 * 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,
            @ColorInt int borderColor,
            @NonNull Runnable invalidateViewCallback) {
        this(borderBoundsBuilder,
                borderWidthPx,
                borderRadiusPx,
                borderColor,
                invalidateViewCallback,
                /* viewScaleTargetProvider= */ null);
    }

    public BorderAnimator(
            @NonNull BorderBoundsBuilder borderBoundsBuilder,
            int borderWidthPx,
            int borderRadiusPx,
            @Px int borderRadiusPx,
            @ColorInt int borderColor,
            @NonNull Runnable invalidateViewCallback,
            @Nullable ViewScaleTargetProvider viewScaleTargetProvider) {
        this(borderBoundsBuilder,
                borderWidthPx,
                borderRadiusPx,
            @NonNull BorderAnimationParams borderAnimationParams) {
        this(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,85 +166,187 @@ 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;
    /**
     * Callback to update the border bounds when building this animation.
     */
    public interface BorderBoundsBuilder {

        /**
         * Sets the given rect to the most up-to-date bounds.
         */
        void updateBorderBounds(Rect rect);
    }
        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);
    /**
     * Params for handling different target view layout situation.
     */
    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;

        View contentView = mViewScaleTargetProvider.getContentView();
        contentView.setPivotX(contentView.getWidth() / 2f);
        contentView.setPivotY(contentView.getHeight() / 2f);
        contentView.setScaleX(1f / scaleX);
        contentView.setScaleY(1f / scaleY);
        /**
         * @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
         */
        private BorderAnimationParams(
                @Px int borderWidthPx,
                @NonNull BorderBoundsBuilder boundsBuilder,
                @NonNull View targetView) {
            mBorderWidthPx = borderWidthPx;
            mBoundsBuilder = boundsBuilder;
            mTargetView = targetView;
        }

    private void resetViewScales() {
        if (mViewScaleTargetProvider == null) {
            return;
        private void setProgress(float progress) {
            mAnimationProgress = progress;
        }
        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);
        private float getBorderWidth() {
            return mBorderWidthPx * mAnimationProgress;
        }

    /**
     * Callback to update the border bounds when building this animation.
     */
    public interface BorderBoundsBuilder {
        float getAlignmentAdjustment() {
            // Outset the border by half the width to create an outwards-growth animation
            return (-getBorderWidth() / 2f) + getAlignmentAdjustmentInset();
        }

        /**
         * Sets the given rect to the most up-to-date bounds.
         */
        void updateBorderBounds(Rect rect);

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

    /**
     * Provider for scaling target views for the beginning and end of this animation.
     * Use an instance of this {@link BorderAnimationParams} if the border can be drawn outside the
     * target view's bounds without any additional logic.
     */
    public interface ViewScaleTargetProvider {
    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();
        }
    }

    /**
         * Returns the content view's container. This view will be scaled up to make room for the
         * border.
     * Use an instance of this {@link BorderAnimationParams} if the border would other be clipped by
     * the target view's bound.
     * <p>
     * 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.
     */
        @NonNull
        View getContainerView();
    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();
        }
    }
}
+5 −4
Original line number Diff line number Diff line
@@ -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();
    }