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

Commit 26f5393d authored by Tracy Zhou's avatar Tracy Zhou
Browse files

Improve the staged split animation

- Introduce rounded corners (since we scale x and y differently, we can't use outline since it doesn't support rx and ry. It's achieved by custom drawing).
- Make sure the thumbnail content doesn't shift during the transition (we use custom cropping for TaskThumbnailView, and we have to do it accordingly here)

TODO: update UX of the initial split view (b/219085340)

Fixes: 194414938
Test: https://recall.googleplex.com/projects/f46cfe9c-8076-4efe-bf8a-b1cc4f1f5e1b/sessions/64953aa7-62ea-427c-8ec0-5f2bd96e4762
Change-Id: Id9a5d2f0f41cb4d619c8b3bd3a83c633e3d1f2de
parent 094a4bdd
Loading
Loading
Loading
Loading
+86 −0
Original line number Diff line number Diff line
/*
 * Copyright 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.quickstep.views;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.View;

import androidx.annotation.Nullable;

/**
 * A child view of {@link com.android.quickstep.views.FloatingTaskView} to draw the thumbnail in a
 * rounded corner frame. While the purpose of this class sounds similar to
 * {@link TaskThumbnailView}, it doesn't need a lot of complex logic in {@link TaskThumbnailView}
 * in relation to moving with {@link RecentsView}.
 */
public class FloatingTaskThumbnailView extends View {

    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private final Matrix mMatrix = new Matrix();

    private @Nullable BitmapShader mBitmapShader;
    private @Nullable Bitmap mBitmap;

    private FloatingTaskView.FullscreenDrawParams mFullscreenParams;

    public FloatingTaskThumbnailView(Context context) {
        this(context, null);
    }

    public FloatingTaskThumbnailView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public FloatingTaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mFullscreenParams == null || mBitmap == null) {
            return;
        }

        // Scale down the bitmap to fix x, and crop in y.
        float scale = 1.0f * getMeasuredWidth() / mBitmap.getWidth();
        mMatrix.postScale(scale, scale);
        mBitmapShader.setLocalMatrix(mMatrix);

        canvas.drawRoundRect(0, 0, getMeasuredWidth(),  getMeasuredHeight(),
                mFullscreenParams.mCurrentDrawnCornerRadius / mFullscreenParams.mScaleX,
                mFullscreenParams.mCurrentDrawnCornerRadius / mFullscreenParams.mScaleY, mPaint);
    }

    public void setThumbnail(Bitmap bitmap) {
        mBitmap = bitmap;
        if (bitmap != null) {
            mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
            mPaint.setShader(mBitmapShader);
        }
    }

    public void setFullscreenParams(FloatingTaskView.FullscreenDrawParams fullscreenParams) {
        mFullscreenParams = fullscreenParams;
    }
}
+52 −28
Original line number Diff line number Diff line
@@ -15,7 +15,6 @@ import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;

import androidx.annotation.Nullable;

@@ -29,6 +28,8 @@ import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.util.MultiValueUpdateListener;
import com.android.quickstep.util.TaskCornerRadius;
import com.android.systemui.shared.system.QuickStepContract;

import java.util.function.Consumer;

@@ -50,9 +51,9 @@ public class FloatingTaskView extends FrameLayout {
    private RectF mStartingPosition;
    private final StatefulActivity mActivity;
    private final boolean mIsRtl;
    private final Rect mOutline = new Rect();
    private final FullscreenDrawParams mCurrentFullscreenParams;
    private PagedOrientationHandler mOrientationHandler;
    private ImageView mImageView;
    private FloatingTaskThumbnailView mThumbnailView;

    public FloatingTaskView(Context context) {
        this(context, null);
@@ -66,16 +67,17 @@ public class FloatingTaskView extends FrameLayout {
        super(context, attrs, defStyleAttr);
        mActivity = BaseActivity.fromContext(context);
        mIsRtl = Utilities.isRtl(getResources());
        mCurrentFullscreenParams = new FullscreenDrawParams(context);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mImageView = findViewById(R.id.thumbnail);
        mImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
        mImageView.setLayerType(LAYER_TYPE_HARDWARE, null);
        mThumbnailView = findViewById(R.id.thumbnail);
        mThumbnailView.setFullscreenParams(mCurrentFullscreenParams);
        mSplitPlaceholderView = findViewById(R.id.split_placeholder);
        mSplitPlaceholderView.setAlpha(0);
        mSplitPlaceholderView.setFullscreenParams(mCurrentFullscreenParams);
    }

    private void init(StatefulActivity launcher, View originalView, @Nullable Bitmap thumbnail,
@@ -86,13 +88,11 @@ public class FloatingTaskView extends FrameLayout {
                (InsettableFrameLayout.LayoutParams) getLayoutParams();

        mSplitPlaceholderView.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height));
        positionOut.round(mOutline);
        setPivotX(0);
        setPivotY(0);

        // Copy bounds of exiting thumbnail into ImageView
        mImageView.setImageBitmap(thumbnail);
        mImageView.setVisibility(VISIBLE);
        mThumbnailView.setThumbnail(thumbnail);

        RecentsView recentsView = launcher.getOverviewPanel();
        mOrientationHandler = recentsView.getPagedOrientationHandler();
@@ -133,27 +133,24 @@ public class FloatingTaskView extends FrameLayout {
        setLayoutParams(lp);
    }

    // TODO(194414938) set correct corner radii
    public void update(RectF position, float progress, float windowRadius) {
    public void update(RectF position, float progress) {
        MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();

        float dX = position.left - mStartingPosition.left;
        float dY = position.top - lp.topMargin;
        float scaleX = position.width() / lp.width;
        float scaleY = position.height() / lp.height;

        mCurrentFullscreenParams.updateParams(position, progress, scaleX, scaleY);

        setTranslationX(dX);
        setTranslationY(dY);

        float scaleX = position.width() / lp.width;
        float scaleY = position.height() / lp.height;
        setScaleX(scaleX);
        setScaleY(scaleY);
        mSplitPlaceholderView.invalidate();

        float childScaleX = 1f / scaleX;
        float childScaleY = 1f / scaleY;

        invalidate();
        // TODO(194414938) seems like this scale value could be fine tuned, some stretchiness
        mImageView.setScaleX(1f / scaleX + scaleX * progress);
        mImageView.setScaleY(1f / scaleY + scaleY * progress);
        mOrientationHandler.setPrimaryScale(mSplitPlaceholderView.getIconView(), childScaleX);
        mOrientationHandler.setSecondaryScale(mSplitPlaceholderView.getIconView(), childScaleY);
    }
@@ -181,7 +178,8 @@ public class FloatingTaskView extends FrameLayout {
    }

    public void addAnimation(PendingAnimation animation, RectF startingBounds, Rect endBounds,
            boolean fadeWithThumbnail) {
            boolean fadeWithThumbnail, boolean isInitialSplit) {
        mCurrentFullscreenParams.setIsInitialSplit(isInitialSplit);
        final BaseDragLayer dragLayer = mActivity.getDragLayer();
        int[] dragLayerBounds = new int[2];
        dragLayer.getLocationOnScreen(dragLayerBounds);
@@ -191,22 +189,16 @@ public class FloatingTaskView extends FrameLayout {
        ValueAnimator transitionAnimator = ValueAnimator.ofFloat(0, 1);
        animation.add(transitionAnimator);
        long animDuration = animation.getDuration();
        Rect crop = new Rect();
        RectF floatingTaskViewBounds = new RectF();
        final float initialWindowRadius = supportsRoundedCornersOnWindows(getResources())
                ? Math.max(crop.width(), crop.height()) / 2f
                : 0f;

        if (fadeWithThumbnail) {
            animation.addFloat(mSplitPlaceholderView, SplitPlaceholderView.ALPHA_FLOAT,
                    0, 1, ACCEL);
            animation.addFloat(mImageView, LauncherAnimUtils.VIEW_ALPHA,
            animation.addFloat(mThumbnailView, LauncherAnimUtils.VIEW_ALPHA,
                    1, 0, DEACCEL_3);
        }

        MultiValueUpdateListener listener = new MultiValueUpdateListener() {
            final FloatProp mWindowRadius = new FloatProp(initialWindowRadius,
                    initialWindowRadius, 0, animDuration, LINEAR);
            final FloatProp mDx = new FloatProp(0, prop.dX, 0, animDuration, LINEAR);
            final FloatProp mDy = new FloatProp(0, prop.dY, 0, animDuration, LINEAR);
            final FloatProp mTaskViewScaleX = new FloatProp(1f, prop.finalTaskViewScaleX, 0,
@@ -221,7 +213,7 @@ public class FloatingTaskView extends FrameLayout {
                Utilities.scaleRectFAboutCenter(floatingTaskViewBounds, mTaskViewScaleX.value,
                        mTaskViewScaleY.value);

                update(floatingTaskViewBounds, percent, mWindowRadius.value * 1);
                update(floatingTaskViewBounds, percent);
            }
        };
        transitionAnimator.addUpdateListener(listener);
@@ -250,4 +242,36 @@ public class FloatingTaskView extends FrameLayout {
            dY = centerY - startTaskViewBounds.centerY();
        }
    }

    public static class FullscreenDrawParams {

        private final float mCornerRadius;
        private final float mWindowCornerRadius;

        public boolean mIsInitialSplit = true;
        public final RectF mFloatingTaskViewBounds = new RectF();
        public float mCurrentDrawnCornerRadius;
        public float mScaleX = 1;
        public float mScaleY = 1;

        public FullscreenDrawParams(Context context) {
            mCornerRadius = TaskCornerRadius.get(context);
            mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context);

            mCurrentDrawnCornerRadius = mCornerRadius;
        }

        public void updateParams(RectF floatingTaskViewBounds, float progress, float scaleX,
                float scaleY) {
            mFloatingTaskViewBounds.set(floatingTaskViewBounds);
            mScaleX = scaleX;
            mScaleY = scaleY;
            mCurrentDrawnCornerRadius = mIsInitialSplit ? 0 :
                    Utilities.mapRange(progress, mCornerRadius, mWindowCornerRadius);
        }

        public void setIsInitialSplit(boolean isInitialSplit) {
            mIsInitialSplit = isInitialSplit;
        }
    }
}
+5 −5
Original line number Diff line number Diff line
@@ -2732,7 +2732,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
                    mSplitHiddenTaskView.getIconView().getDrawable(), startingTaskRect);
            mFirstFloatingTaskView.setAlpha(1);
            mFirstFloatingTaskView.addAnimation(anim, startingTaskRect,
                    mTempRect, true /*fadeWithThumbnail*/);
                    mTempRect, true /* fadeWithThumbnail */, true /* isInitialSplit */);
        } else {
            mSplitSelectSource.view.setVisibility(INVISIBLE);
            mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
@@ -2740,7 +2740,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
                    mSplitSelectSource.drawable, startingTaskRect);
            mFirstFloatingTaskView.setAlpha(1);
            mFirstFloatingTaskView.addAnimation(anim, startingTaskRect,
                    mTempRect, true /*fadeWithThumbnail*/);
                    mTempRect, true /* fadeWithThumbnail */, true /* isInitialSplit */);
        }
        anim.addEndListener(success -> {
            if (success) {
@@ -4030,14 +4030,14 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
        mFirstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
        mFirstFloatingTaskView.addAnimation(pendingAnimation,
                new RectF(firstTaskStartingBounds), firstTaskEndingBounds,
                false /*fadeWithThumbnail*/);
                false /* fadeWithThumbnail */, false /* isInitialSplit */);

        mSecondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
                thumbnailView, thumbnailView.getThumbnail(),
                iconView.getDrawable(), secondTaskStartingBounds);
        mSecondFloatingTaskView.setAlpha(1);
        mSecondFloatingTaskView.addAnimation(pendingAnimation, secondTaskStartingBounds,
                secondTaskEndingBounds, true /* fadeWithThumbnail */);
                secondTaskEndingBounds, true /* fadeWithThumbnail */, false /* isInitialSplit */);
        pendingAnimation.addEndListener(aBoolean ->
                mSplitSelectStateController.setSecondTaskId(task.key.id,
                aBoolean1 -> RecentsView.this.resetFromSplitSelectionState()));
@@ -4110,7 +4110,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
        mTempRectF.set(mTempRect);
        // TODO(194414938) set correct corner radius
        mFirstFloatingTaskView.updateOrientationHandler(mOrientationHandler);
        mFirstFloatingTaskView.update(mTempRectF, /*progress=*/1f, /*windowRadius=*/0f);
        mFirstFloatingTaskView.update(mTempRectF, /*progress=*/1f);

        PagedOrientationHandler orientationHandler = getPagedOrientationHandler();
        Pair<FloatProperty, FloatProperty> taskViewsFloat =
+38 −0
Original line number Diff line number Diff line
@@ -17,9 +17,12 @@
package com.android.quickstep.views;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.TypedValue;
import android.view.Gravity;
import android.widget.FrameLayout;

@@ -27,6 +30,10 @@ import androidx.annotation.Nullable;

public class SplitPlaceholderView extends FrameLayout {

    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    private FloatingTaskView.FullscreenDrawParams mFullscreenParams;

    public static final FloatProperty<SplitPlaceholderView> ALPHA_FLOAT =
            new FloatProperty<SplitPlaceholderView>("SplitViewAlpha") {
                @Override
@@ -46,6 +53,17 @@ public class SplitPlaceholderView extends FrameLayout {

    public SplitPlaceholderView(Context context, AttributeSet attrs) {
        super(context, attrs);

        mPaint.setColor(getThemePrimaryColor(context));
        setWillNotDraw(false);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        // Call this before super call to draw below the children.
        drawBackground(canvas);

        super.dispatchDraw(canvas);
    }

    @Nullable
@@ -53,6 +71,10 @@ public class SplitPlaceholderView extends FrameLayout {
        return mIconView;
    }

    public void setFullscreenParams(FloatingTaskView.FullscreenDrawParams fullscreenParams) {
        mFullscreenParams = fullscreenParams;
    }

    public void setIcon(Drawable drawable, int iconSize) {
        if (mIconView == null) {
            mIconView = new IconView(getContext());
@@ -64,4 +86,20 @@ public class SplitPlaceholderView extends FrameLayout {
        params.gravity = Gravity.CENTER;
        mIconView.setLayoutParams(params);
    }

    private void drawBackground(Canvas canvas) {
        if (mFullscreenParams == null) {
            return;
        }

        canvas.drawRoundRect(0, 0, getMeasuredWidth(),  getMeasuredHeight(),
                mFullscreenParams.mCurrentDrawnCornerRadius / mFullscreenParams.mScaleX,
                mFullscreenParams.mCurrentDrawnCornerRadius / mFullscreenParams.mScaleY, mPaint);
    }

    private static int getThemePrimaryColor(Context context) {
        final TypedValue value = new TypedValue();
        context.getTheme().resolveAttribute(android.R.attr.colorPrimary, value, true);
        return value.data;
    }
}
+1 −2
Original line number Diff line number Diff line
@@ -4,7 +4,7 @@
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
    <com.android.quickstep.views.FloatingTaskThumbnailView
        android:id="@+id/thumbnail"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
@@ -14,7 +14,6 @@
        android:id="@+id/split_placeholder"
        android:layout_width="match_parent"
        android:layout_height="@dimen/split_placeholder_size"
        android:background="?android:colorPrimary"
        android:visibility="gone" />

</com.android.quickstep.views.FloatingTaskView>
 No newline at end of file