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

Commit abb55950 authored by Sunny Goyal's avatar Sunny Goyal
Browse files

Updating floating animation target if layout changes during the animation

Bug: 131360075
Change-Id: Id71e154aa59177955baa9daab00ecf11ee7d8c43
parent 1bdb0f40
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -1061,7 +1061,9 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>

        RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect);
        if (isFloatingIconView) {
            anim.addAnimatorListener((FloatingIconView) floatingView);
            FloatingIconView fiv = (FloatingIconView) floatingView;
            anim.addAnimatorListener(fiv);
            fiv.setOnTargetChangeListener(anim::onTargetPositionChanged);
        }

        AnimatorPlaybackController homeAnim = homeAnimationFactory.createActivityAnimationToHome();
+9 −0
Original line number Diff line number Diff line
@@ -113,6 +113,15 @@ public class RectFSpringAnim {
        mCurrentCenterY = mStartRect.centerY();
    }

    public void onTargetPositionChanged() {
        if (mRectXAnim != null && mRectXAnim.getTargetPosition() != mTargetRect.centerX()) {
            mRectXAnim.updatePosition(mCurrentCenterX, mTargetRect.centerX());
        }
        if (mRectYAnim != null && mRectYAnim.getTargetPosition() != mTargetRect.centerY()) {
            mRectYAnim.updatePosition(mCurrentCenterY, mTargetRect.centerY());
        }
    }

    public void addOnUpdateListener(OnUpdateListener onUpdateListener) {
        mOnUpdateListeners.add(onUpdateListener);
    }
+18 −1
Original line number Diff line number Diff line
@@ -36,6 +36,8 @@ public class FlingSpringAnim {
    private final FlingAnimation mFlingAnim;
    private SpringAnimation mSpringAnim;

    private float mTargetPosition;

    public <K> FlingSpringAnim(K object, FloatPropertyCompat<K> property, float startPosition,
            float targetPosition, float startVelocity, OnAnimationEndListener onEndListener) {
        mFlingAnim = new FlingAnimation(object, property)
@@ -44,10 +46,12 @@ public class FlingSpringAnim {
                .setStartVelocity(startVelocity)
                .setMinValue(Math.min(startPosition, targetPosition))
                .setMaxValue(Math.max(startPosition, targetPosition));
        mTargetPosition = targetPosition;

        mFlingAnim.addEndListener(((animation, canceled, value, velocity) -> {
            mSpringAnim = new SpringAnimation(object, property)
                    .setStartVelocity(velocity)
                    .setSpring(new SpringForce(targetPosition)
                    .setSpring(new SpringForce(mTargetPosition)
                            .setStiffness(SPRING_STIFFNESS)
                            .setDampingRatio(SPRING_DAMPING));
            mSpringAnim.addEndListener(onEndListener);
@@ -55,6 +59,19 @@ public class FlingSpringAnim {
        }));
    }

    public float getTargetPosition() {
        return mTargetPosition;
    }

    public void updatePosition(float startPosition, float targetPosition) {
        mFlingAnim.setMinValue(Math.min(startPosition, targetPosition))
                .setMaxValue(Math.max(startPosition, targetPosition));
        mTargetPosition = targetPosition;
        if (mSpringAnim != null) {
            mSpringAnim.animateToFinalPosition(mTargetPosition);
        }
    }

    public void start() {
        mFlingAnim.start();
    }
+70 −22
Original line number Diff line number Diff line
@@ -25,7 +25,6 @@ import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Outline;
@@ -42,6 +41,7 @@ import android.os.Looper;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.ImageView;

import com.android.launcher3.BubbleTextView;
@@ -67,16 +67,19 @@ import androidx.annotation.WorkerThread;
 * A view that is created to look like another view with the purpose of creating fluid animations.
 */
@TargetApi(Build.VERSION_CODES.Q)
public class FloatingIconView extends View implements Animator.AnimatorListener, ClipPathView {
public class FloatingIconView extends View implements
        Animator.AnimatorListener, ClipPathView, OnGlobalLayoutListener {

    public static final float SHAPE_PROGRESS_DURATION = 0.15f;
    private static final int FADE_DURATION_MS = 200;
    private static final Rect sTmpRect = new Rect();
    private static final RectF sTmpRectF = new RectF();
    private static final Object[] sTmpObjArray = new Object[1];

    private Runnable mEndRunnable;
    private CancellationSignal mLoadIconSignal;

    private final Launcher mLauncher;
    private final int mBlurSizeOutline;

    private boolean mIsVerticalBarLayout = false;
@@ -91,6 +94,10 @@ public class FloatingIconView extends View implements Animator.AnimatorListener,
    private Path mClipPath;
    private float mTaskCornerRadius;

    private View mOriginalIcon;
    private RectF mPositionOut;
    private Runnable mOnTargetChangeRunnable;

    private final Rect mFinalDrawableBounds = new Rect();
    private final Rect mBgDrawableBounds = new Rect();
    private float mBgDrawableStartScale = 1f;
@@ -99,12 +106,24 @@ public class FloatingIconView extends View implements Animator.AnimatorListener,
    private AnimatorSet mFadeAnimatorSet;
    private ListenerView mListenerView;

    private FloatingIconView(Context context) {
        super(context);

        mBlurSizeOutline = context.getResources().getDimensionPixelSize(
    private FloatingIconView(Launcher launcher) {
        super(launcher);
        mLauncher = launcher;
        mBlurSizeOutline = getResources().getDimensionPixelSize(
                R.dimen.blur_size_medium_outline);
        mListenerView = new ListenerView(context, null);
        mListenerView = new ListenerView(launcher, null);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        getViewTreeObserver().addOnGlobalLayoutListener(this);
    }

    @Override
    protected void onDetachedFromWindow() {
        getViewTreeObserver().removeOnGlobalLayoutListener(this);
        super.onDetachedFromWindow();
    }

    /**
@@ -188,21 +207,28 @@ public class FloatingIconView extends View implements Animator.AnimatorListener,
     * @param v The view to copy
     * @param positionOut Rect that will hold the size and position of v.
     */
    private void matchPositionOf(Launcher launcher, View v, RectF positionOut) {
        mRotation = getLocationBoundsForView(launcher, v, positionOut);
    private void matchPositionOf(View v, RectF positionOut) {
        float rotation = getLocationBoundsForView(v, positionOut);
        final LayoutParams lp = new LayoutParams(
                Math.round(positionOut.width()),
                Math.round(positionOut.height()));
        lp.ignoreInsets = true;
        updatePosition(rotation, positionOut, lp);
        setLayoutParams(lp);
    }

    private void updatePosition(float rotation, RectF position, LayoutParams lp) {
        mRotation = rotation;
        mPositionOut.set(position);
        lp.ignoreInsets = true;
        // Position the floating view exactly on top of the original
        lp.leftMargin = Math.round(positionOut.left);
        lp.topMargin = Math.round(positionOut.top);
        setLayoutParams(lp);
        lp.leftMargin = Math.round(position.left);
        lp.topMargin = Math.round(position.top);

        // Set the properties here already to make sure they are available when running the first
        // animation frame.
        layout(lp.leftMargin, lp.topMargin, lp.leftMargin + lp.width, lp.topMargin
                + lp.height);

    }

    /**
@@ -210,7 +236,7 @@ public class FloatingIconView extends View implements Animator.AnimatorListener,
     * - For DeepShortcutView, we return the bounds of the icon view.
     * - For BubbleTextView, we return the icon bounds.
     */
    private float getLocationBoundsForView(Launcher launcher, View v, RectF outRect) {
    private float getLocationBoundsForView(View v, RectF outRect) {
        boolean ignoreTransform = true;
        if (v instanceof DeepShortcutView) {
            v = ((DeepShortcutView) v).getBubbleText();
@@ -235,7 +261,7 @@ public class FloatingIconView extends View implements Animator.AnimatorListener,
        float[] points = new float[] {iconBounds.left, iconBounds.top, iconBounds.right,
                iconBounds.bottom};
        float[] rotation = new float[] {0};
        Utilities.getDescendantCoordRelativeToAncestor(v, launcher.getDragLayer(), points,
        Utilities.getDescendantCoordRelativeToAncestor(v, mLauncher.getDragLayer(), points,
                false, ignoreTransform, rotation);
        outRect.set(
                Math.min(points[0], points[2]),
@@ -246,7 +272,7 @@ public class FloatingIconView extends View implements Animator.AnimatorListener,
    }

    @WorkerThread
    private void getIcon(Launcher launcher, View v, ItemInfo info, boolean isOpening,
    private void getIcon(View v, ItemInfo info, boolean isOpening,
            Runnable onIconLoadedRunnable, CancellationSignal loadIconSignal) {
        final LayoutParams lp = (LayoutParams) getLayoutParams();
        Drawable drawable = null;
@@ -263,7 +289,7 @@ public class FloatingIconView extends View implements Animator.AnimatorListener,
            }
        } else {
            if (supportsAdaptiveIcons) {
                drawable = Utilities.getFullDrawable(launcher, info, lp.width, lp.height,
                drawable = Utilities.getFullDrawable(mLauncher, info, lp.width, lp.height,
                        false, sTmpObjArray);
                if (!(drawable instanceof AdaptiveIconDrawable)) {
                    // The drawable we get back is not an adaptive icon, so we need to use the
@@ -275,7 +301,7 @@ public class FloatingIconView extends View implements Animator.AnimatorListener,
                    // Similar to DragView, we simply use the BubbleTextView icon here.
                    drawable = btvIcon;
                } else {
                    drawable = Utilities.getFullDrawable(launcher, info, lp.width, lp.height,
                    drawable = Utilities.getFullDrawable(mLauncher, info, lp.width, lp.height,
                            false, sTmpObjArray);
                }
            }
@@ -328,7 +354,7 @@ public class FloatingIconView extends View implements Animator.AnimatorListener,
                    mStartRevealRect.inset(mBlurSizeOutline, mBlurSizeOutline);
                }

                float aspectRatio = launcher.getDeviceProfile().aspectRatio;
                float aspectRatio = mLauncher.getDeviceProfile().aspectRatio;
                if (mIsVerticalBarLayout) {
                    lp.width = (int) Math.max(lp.width, lp.height * aspectRatio);
                } else {
@@ -395,7 +421,7 @@ public class FloatingIconView extends View implements Animator.AnimatorListener,
        Rect bounds = new Rect(0, 0, lp.width + mBlurSizeOutline, lp.height + mBlurSizeOutline);
        bounds.inset(mBlurSizeOutline / 2, mBlurSizeOutline / 2);

        try (LauncherIcons li = LauncherIcons.obtain(Launcher.fromContext(getContext()))) {
        try (LauncherIcons li = LauncherIcons.obtain(getContext())) {
            Utilities.scaleRectAboutCenter(bounds, li.getNormalizer().getScale(drawable, null));
        }

@@ -452,6 +478,23 @@ public class FloatingIconView extends View implements Animator.AnimatorListener,
    @Override
    public void onAnimationRepeat(Animator animator) {}

    @Override
    public void onGlobalLayout() {
        if (mOriginalIcon.isAttachedToWindow() && mPositionOut != null) {
            float rotation = getLocationBoundsForView(mOriginalIcon, sTmpRectF);
            if (rotation != mRotation || !sTmpRectF.equals(mPositionOut)) {
                updatePosition(rotation, sTmpRectF, (LayoutParams) getLayoutParams());
                if (mOnTargetChangeRunnable != null) {
                    mOnTargetChangeRunnable.run();
                }
            }
        }
    }

    public void setOnTargetChangeListener(Runnable onTargetChangeListener) {
        mOnTargetChangeRunnable = onTargetChangeListener;
    }

    /**
     * Creates a floating icon view for {@param originalView}.
     * @param originalView The view to copy
@@ -468,8 +511,10 @@ public class FloatingIconView extends View implements Animator.AnimatorListener,
        FloatingIconView view = recycle != null ? recycle : new FloatingIconView(launcher);
        view.mIsVerticalBarLayout = launcher.getDeviceProfile().isVerticalBarLayout();

        view.mOriginalIcon = originalView;
        view.mPositionOut = positionOut;
        // Match the position of the original view.
        view.matchPositionOf(launcher, originalView, positionOut);
        view.matchPositionOf(originalView, positionOut);

        // Get the drawable on the background thread
        // Must be called after matchPositionOf so that we know what size to load.
@@ -482,7 +527,7 @@ public class FloatingIconView extends View implements Animator.AnimatorListener,
            };
            CancellationSignal loadIconSignal = view.mLoadIconSignal;
            new Handler(LauncherModel.getWorkerLooper()).postAtFrontOfQueue(() -> {
                view.getIcon(launcher, originalView, (ItemInfo) originalView.getTag(), isOpening,
                view.getIcon(originalView, (ItemInfo) originalView.getTag(), isOpening,
                        onIconLoaded, loadIconSignal);
            });
        }
@@ -580,7 +625,10 @@ public class FloatingIconView extends View implements Animator.AnimatorListener,
        if (mFadeAnimatorSet != null) {
            mFadeAnimatorSet.cancel();
        }
        mPositionOut = null;
        mFadeAnimatorSet = null;
        mListenerView.setListener(null);
        mOriginalIcon = null;
        mOnTargetChangeRunnable = null;
    }
}