From 7903758d085554ebb7348bcee07205baae7756fa Mon Sep 17 00:00:00 2001 From: Vinit Nayak Date: Thu, 24 Jun 2021 15:23:21 -0700 Subject: [PATCH] Animate SplitPlaceholderView when entering split from overview Bugs tracked in b/181704764 Bug: 181704764 Test: Tested on phone and large screen in multiple orientations Change-Id: I07509006ae3d1f4425dc5119d0c8ed52b41a3bc2 --- quickstep/res/values/dimens.xml | 1 - .../launcher3/BaseQuickstepLauncher.java | 8 +- .../RecentsViewStateController.java | 7 - .../states/SplitScreenSelectState.java | 2 +- .../TaskViewTouchController.java | 3 +- .../android/quickstep/RecentsActivity.java | 9 +- .../fallback/FallbackRecentsView.java | 8 +- .../util/SplitSelectStateController.java | 24 +- .../quickstep/views/FloatingTaskView.java | 241 ++++++++++++++++++ .../quickstep/views/LauncherRecentsView.java | 4 +- .../android/quickstep/views/RecentsView.java | 168 +++++++----- .../quickstep/views/SplitPlaceholderView.java | 7 + res/layout/floating_split_select_view.xml | 20 ++ res/values/dimens.xml | 1 + src/com/android/launcher3/Utilities.java | 15 ++ .../launcher3/anim/PendingAnimation.java | 4 + .../touch/LandscapePagedViewHandler.java | 29 +++ .../touch/PagedOrientationHandler.java | 18 ++ .../touch/PortraitPagedViewHandler.java | 70 +++++ 19 files changed, 542 insertions(+), 97 deletions(-) create mode 100644 quickstep/src/com/android/quickstep/views/FloatingTaskView.java create mode 100644 res/layout/floating_split_select_view.xml diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index f54070e3f3..5fc969dec1 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -43,7 +43,6 @@ 54dp 42dp 40dp - 110dp 2.25dp diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index ecd38b486c..5250d18724 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -271,12 +271,10 @@ public abstract class BaseQuickstepLauncher extends Launcher SysUINavigationMode.INSTANCE.get(this).updateMode(); mActionsView = findViewById(R.id.overview_actions_view); - mSplitPlaceholderView = findViewById(R.id.split_placeholder); RecentsView overviewPanel = (RecentsView) getOverviewPanel(); - mSplitPlaceholderView.init( - new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this)) - ); - overviewPanel.init(mActionsView, mSplitPlaceholderView); + SplitSelectStateController controller = + new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this)); + overviewPanel.init(mActionsView, controller); mActionsView.setDp(getDeviceProfile()); mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this)); diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java index 6cad3ddc92..1f744e1953 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java @@ -18,13 +18,11 @@ package com.android.launcher3.uioverrides; import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON; import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS; import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT; -import static com.android.launcher3.LauncherState.SPLIT_PLACHOLDER_VIEW; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE; import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA; import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS; import static com.android.quickstep.views.RecentsView.TASK_MODALNESS; -import static com.android.quickstep.views.SplitPlaceholderView.ALPHA_FLOAT; import static com.android.quickstep.views.TaskView.FLAG_UPDATE_ALL; import android.annotation.TargetApi; @@ -110,11 +108,6 @@ public final class RecentsViewStateController extends propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(), MultiValueAlpha.VALUE, overviewButtonAlpha, config.getInterpolator( ANIM_OVERVIEW_ACTIONS_FADE, LINEAR)); - - float splitPlaceholderAlpha = state.areElementsVisible(mLauncher, SPLIT_PLACHOLDER_VIEW) ? - 0.85f : 0; - propertySetter.setFloat(mRecentsView.getSplitPlaceholder(), ALPHA_FLOAT, - splitPlaceholderAlpha, LINEAR); } @Override diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java index 6968494b0f..1882a0c929 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java @@ -43,7 +43,7 @@ public class SplitScreenSelectState extends OverviewState { @Override public float getSplitSelectTranslation(Launcher launcher) { RecentsView recentsView = launcher.getOverviewPanel(); - int splitPosition = recentsView.getSplitPlaceholder().getSplitController() + int splitPosition = recentsView.getSplitPlaceholder() .getActiveSplitPositionOption().mStagePosition; if (!recentsView.shouldShiftThumbnailsForSplitSelect(splitPosition)) { return 0f; diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java index 051485acc6..0603ba502a 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java @@ -227,7 +227,8 @@ public abstract class TaskViewTouchController if (goingUp) { currentInterpolator = Interpolators.LINEAR; pa = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged, - true /* animateTaskView */, true /* removeTask */, maxDuration); + true /* animateTaskView */, true /* removeTask */, maxDuration, + false /* dismissingForSplitSelection*/); mEndDisplacement = -secondaryTaskDimension; } else { diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java index 9dfcd12dde..95be45a5b2 100644 --- a/quickstep/src/com/android/quickstep/RecentsActivity.java +++ b/quickstep/src/com/android/quickstep/RecentsActivity.java @@ -122,13 +122,10 @@ public final class RecentsActivity extends StatefulActivity { mActionsView = findViewById(R.id.overview_actions_view); SYSUI_PROGRESS.set(getRootView().getSysUiScrim(), 0f); - SplitPlaceholderView splitPlaceholderView = findViewById(R.id.split_placeholder); - splitPlaceholderView.init( - new SplitSelectStateController(mUiHandler, SystemUiProxy.INSTANCE.get(this)) - ); - + SplitSelectStateController controller = + new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this)); mDragLayer.recreateControllers(); - mFallbackRecentsView.init(mActionsView, splitPlaceholderView); + mFallbackRecentsView.init(mActionsView, controller); } @Override diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java index 3bf79f1579..de79372591 100644 --- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java +++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java @@ -37,6 +37,7 @@ import com.android.quickstep.FallbackActivityInterface; import com.android.quickstep.GestureState; import com.android.quickstep.RecentsActivity; import com.android.quickstep.util.TaskViewSimulator; +import com.android.quickstep.util.SplitSelectStateController; import com.android.quickstep.views.OverviewActionsView; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.SplitPlaceholderView; @@ -62,8 +63,8 @@ public class FallbackRecentsView extends RecentsView setCurrentTask(-1)); AnimatorPlaybackController controller = pa.createPlaybackController(); controller.dispatchOnStart(); diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java index 2351a4e164..16c925a6e9 100644 --- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java +++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java @@ -56,6 +56,7 @@ public class SplitSelectStateController { private final SystemUiProxy mSystemUiProxy; private TaskView mInitialTaskView; + private TaskView mSecondTaskView; private SplitPositionOption mInitialPosition; private Rect mInitialBounds; private final Handler mHandler; @@ -79,23 +80,19 @@ public class SplitSelectStateController { * To be called after second task selected */ public void setSecondTaskId(TaskView taskView) { - if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { - // Assume initial task is for top/left part of screen - final int[] taskIds = mInitialPosition.mStagePosition == STAGE_POSITION_TOP_OR_LEFT - ? new int[]{mInitialTaskView.getTask().key.id, taskView.getTask().key.id} - : new int[]{taskView.getTask().key.id, mInitialTaskView.getTask().key.id}; + mSecondTaskView = taskView; + // Assume initial task is for top/left part of screen + final int[] taskIds = mInitialPosition.mStagePosition == STAGE_POSITION_TOP_OR_LEFT + ? new int[]{mInitialTaskView.getTask().key.id, taskView.getTask().key.id} + : new int[]{taskView.getTask().key.id, mInitialTaskView.getTask().key.id}; + if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { RemoteSplitLaunchTransitionRunner animationRunner = new RemoteSplitLaunchTransitionRunner(mInitialTaskView, taskView); mSystemUiProxy.startTasks(taskIds[0], null /* mainOptions */, taskIds[1], null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT, new RemoteTransitionCompat(animationRunner, MAIN_EXECUTOR)); } else { - // Assume initial task is for top/left part of screen - final int[] taskIds = mInitialPosition.mStagePosition == STAGE_POSITION_TOP_OR_LEFT - ? new int[]{mInitialTaskView.getTask().key.id, taskView.getTask().key.id} - : new int[]{taskView.getTask().key.id, mInitialTaskView.getTask().key.id}; - RemoteSplitLaunchAnimationRunner animationRunner = new RemoteSplitLaunchAnimationRunner(mInitialTaskView, taskView); final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter( @@ -191,12 +188,17 @@ public class SplitSelectStateController { */ public void resetState() { mInitialTaskView = null; + mSecondTaskView = null; mInitialPosition = null; mInitialBounds = null; } + /** + * @return {@code true} if first task has been selected and waiting for the second task to be + * chosen + */ public boolean isSplitSelectActive() { - return mInitialTaskView != null; + return mInitialTaskView != null && mSecondTaskView == null; } public Rect getInitialBounds() { diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java new file mode 100644 index 0000000000..a1befc59fa --- /dev/null +++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java @@ -0,0 +1,241 @@ +package com.android.quickstep.views; + +import static com.android.launcher3.anim.Interpolators.ACCEL; +import static com.android.launcher3.anim.Interpolators.DEACCEL_3; +import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows; + +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Rect; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.android.launcher3.InsettableFrameLayout; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAnimUtils; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.anim.PendingAnimation; +import com.android.launcher3.statemanager.StatefulActivity; +import com.android.launcher3.touch.PagedOrientationHandler; +import com.android.launcher3.views.BaseDragLayer; +import com.android.quickstep.util.MultiValueUpdateListener; + +/** + * Create an instance via {@link #getFloatingTaskView(StatefulActivity, TaskView, RectF)} to + * which will have the thumbnail from the provided existing TaskView overlaying the taskview itself. + * + * Can then animate the taskview using + * {@link #addAnimation(PendingAnimation, RectF, Rect, View, boolean)} + * giving a starting and ending bounds. Currently this is set to use the split placeholder view, + * but it could be generified. + * + * TODO: Figure out how to copy thumbnail data from existing TaskView to this view. + */ +public class FloatingTaskView extends FrameLayout { + + private SplitPlaceholderView mSplitPlaceholderView; + private RectF mStartingPosition; + private final Launcher mLauncher; + private final boolean mIsRtl; + private final Rect mOutline = new Rect(); + private PagedOrientationHandler mOrientationHandler; + private ImageView mImageView; + + public FloatingTaskView(Context context) { + this(context, null); + } + + public FloatingTaskView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public FloatingTaskView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mLauncher = Launcher.getLauncher(context); + mIsRtl = Utilities.isRtl(getResources()); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mImageView = findViewById(R.id.thumbnail); + mImageView.setScaleType(ImageView.ScaleType.CENTER_CROP); + mImageView.setLayerType(LAYER_TYPE_HARDWARE, null); + mSplitPlaceholderView = findViewById(R.id.split_placeholder); + mSplitPlaceholderView.setAlpha(0); + mSplitPlaceholderView.setBackgroundColor(getResources().getColor(android.R.color.white)); + } + + public static FloatingTaskView getFloatingTaskView(StatefulActivity launcher, + TaskView originalView, RectF positionOut) { + final BaseDragLayer dragLayer = launcher.getDragLayer(); + ViewGroup parent = (ViewGroup) dragLayer.getParent(); + final FloatingTaskView floatingView = (FloatingTaskView) launcher.getLayoutInflater() + .inflate(R.layout.floating_split_select_view, parent, false); + + floatingView.mStartingPosition = positionOut; + floatingView.updateInitialPositionForView(originalView); + final InsettableFrameLayout.LayoutParams lp = + (InsettableFrameLayout.LayoutParams) floatingView.getLayoutParams(); + + floatingView.mSplitPlaceholderView.setLayoutParams( + new FrameLayout.LayoutParams(lp.width, lp.height)); + positionOut.round(floatingView.mOutline); + floatingView.setPivotX(0); + floatingView.setPivotY(0); + + // Copy bounds of exiting thumbnail into ImageView + TaskThumbnailView thumbnail = originalView.getThumbnail(); + floatingView.mImageView.setImageBitmap(thumbnail.getThumbnail()); + floatingView.mImageView.setVisibility(VISIBLE); + + floatingView.mOrientationHandler = + originalView.getRecentsView().getPagedOrientationHandler(); + floatingView.mSplitPlaceholderView.setIcon(originalView.getIconView()); + floatingView.mSplitPlaceholderView.getIcon() + .setRotation(floatingView.mOrientationHandler.getDegreesRotated()); + parent.addView(floatingView); + return floatingView; + } + + public void updateInitialPositionForView(TaskView originalView) { + View thumbnail = originalView.getThumbnail(); + Rect viewBounds = new Rect(0, 0, thumbnail.getWidth(), thumbnail.getHeight()); + Utilities.getBoundsForViewInDragLayer(mLauncher.getDragLayer(), thumbnail, viewBounds, + true /* ignoreTransform */, null /* recycle */, + mStartingPosition); + mStartingPosition.offset(originalView.getTranslationX(), originalView.getTranslationY()); + final InsettableFrameLayout.LayoutParams lp = new InsettableFrameLayout.LayoutParams( + Math.round(mStartingPosition.width()), + Math.round(mStartingPosition.height())); + initPosition(mStartingPosition, lp); + setLayoutParams(lp); + } + + // TODO(194414938) set correct corner radii + public void update(RectF position, float progress, float windowRadius) { + MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams(); + + float dX = mIsRtl + ? position.left - (lp.getMarginStart() - lp.width) + : position.left - lp.getMarginStart(); + float dY = position.top - lp.topMargin; + + setTranslationX(dX); + setTranslationY(dY); + + float scaleX = position.width() / lp.width; + float scaleY = position.height() / lp.height; + setScaleX(scaleX); + setScaleY(scaleY); + 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.getIcon(), childScaleX); + mOrientationHandler.setSecondaryScale(mSplitPlaceholderView.getIcon(), childScaleY); + } + + protected void initPosition(RectF pos, InsettableFrameLayout.LayoutParams lp) { + mStartingPosition.set(pos); + lp.ignoreInsets = true; + // Position the floating view exactly on top of the original + lp.topMargin = Math.round(pos.top); + if (mIsRtl) { + lp.setMarginStart(Math.round(mLauncher.getDeviceProfile().widthPx - pos.right)); + } else { + lp.setMarginStart(Math.round(pos.left)); + } + // Set the properties here already to make sure they are available when running the first + // animation frame. + int left = mIsRtl + ? mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width + : lp.leftMargin; + layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height); + } + + public void addAnimation(PendingAnimation animation, RectF startingBounds, Rect endBounds, + View viewToCover, boolean fadeWithThumbnail) { + final BaseDragLayer dragLayer = mLauncher.getDragLayer(); + int[] dragLayerBounds = new int[2]; + dragLayer.getLocationOnScreen(dragLayerBounds); + SplitOverlayProperties prop = new SplitOverlayProperties(endBounds, + startingBounds, viewToCover, dragLayerBounds[0], + dragLayerBounds[1]); + + 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, + 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(prop.initialTaskViewScaleX, + prop.finalTaskViewScaleX, 0, animDuration, LINEAR); + final FloatProp mTaskViewScaleY = new FloatProp(prop.initialTaskViewScaleY, + prop.finalTaskViewScaleY, 0, animDuration, LINEAR); + @Override + public void onUpdate(float percent, boolean initOnly) { + // Calculate the icon position. + floatingTaskViewBounds.set(startingBounds); + floatingTaskViewBounds.offset(mDx.value, mDy.value); + Utilities.scaleRectFAboutCenter(floatingTaskViewBounds, mTaskViewScaleX.value, + mTaskViewScaleY.value); + + update(floatingTaskViewBounds, percent, mWindowRadius.value * 1); + } + }; + transitionAnimator.addUpdateListener(listener); + } + + private static class SplitOverlayProperties { + + private final float initialTaskViewScaleX; + private final float initialTaskViewScaleY; + private final float finalTaskViewScaleX; + private final float finalTaskViewScaleY; + private final float dX; + private final float dY; + + SplitOverlayProperties(Rect endBounds, RectF startTaskViewBounds, View view, + int dragLayerLeft, int dragLayerTop) { + float maxScaleX = endBounds.width() / startTaskViewBounds.width(); + float maxScaleY = endBounds.height() / startTaskViewBounds.height(); + + initialTaskViewScaleX = view.getScaleX(); + initialTaskViewScaleY = view.getScaleY(); + finalTaskViewScaleX = maxScaleX; + finalTaskViewScaleY = maxScaleY; + + // Animate the app icon to the center of the window bounds in screen coordinates. + float centerX = endBounds.centerX() - dragLayerLeft; + float centerY = endBounds.centerY() - dragLayerTop; + + dX = centerX - startTaskViewBounds.centerX(); + dY = centerY - startTaskViewBounds.centerY(); + } + } +} diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java index 65956d578f..152c2bd47f 100644 --- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java +++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java @@ -38,6 +38,7 @@ import com.android.launcher3.statemanager.StateManager.StateListener; import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; import com.android.launcher3.util.SplitConfigurationOptions; import com.android.quickstep.LauncherActivityInterface; +import com.android.quickstep.util.SplitSelectStateController; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.RecentsExtraCard; @@ -81,7 +82,8 @@ public class LauncherRecentsView extends RecentsView secondaryViewTranslate = + taskView.getSecondaryDissmissTranslationProperty(); + int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView); + int verticalFactor = mOrientationHandler.getSecondaryTranslationDirectionFactor(); ResourceProvider rp = DynamicResource.provider(mActivity); SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_START) .setDampingRatio(rp.getFloat(R.dimen.dismiss_task_trans_y_damping_ratio)) .setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_y_stiffness)); - FloatProperty dismissingTaskViewTranslate = - taskView.getSecondaryDissmissTranslationProperty(); - // TODO(b/186800707) translate entire grid size distance - int translateDistance = mOrientationHandler.getSecondaryDimension(taskView); - int positiveNegativeFactor = mOrientationHandler.getSecondaryTranslationDirectionFactor(); - if (splitController.isSplitSelectActive()) { - // Have the task translate towards whatever side was just pinned - int dir = mOrientationHandler.getSplitTaskViewDismissDirection(splitController - .getActiveSplitPositionOption(), mActivity.getDeviceProfile()); - switch (dir) { - case PagedOrientationHandler.SPLIT_TRANSLATE_SECONDARY_NEGATIVE: - dismissingTaskViewTranslate = taskView - .getSecondaryDissmissTranslationProperty(); - positiveNegativeFactor = -1; - break; - case PagedOrientationHandler.SPLIT_TRANSLATE_PRIMARY_POSITIVE: - dismissingTaskViewTranslate = taskView.getPrimaryDismissTranslationProperty(); - positiveNegativeFactor = 1; - break; - - case PagedOrientationHandler.SPLIT_TRANSLATE_PRIMARY_NEGATIVE: - dismissingTaskViewTranslate = taskView.getPrimaryDismissTranslationProperty(); - positiveNegativeFactor = -1; - break; - default: - throw new IllegalStateException("Invalid split task translation: " + dir); - } - } - // Double translation distance so dismissal drag is the full height, as we only animate - // the drag for the first half of the progress. - anim.add(ObjectAnimator.ofFloat(taskView, dismissingTaskViewTranslate, - positiveNegativeFactor * translateDistance * 2).setDuration(duration), LINEAR, sp); + anim.add(ObjectAnimator.ofFloat(taskView, secondaryViewTranslate, + verticalFactor * secondaryTaskDimension * 2).setDuration(duration), LINEAR, sp); if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile && taskView.isRunningTask()) { @@ -2343,6 +2322,25 @@ public abstract class RecentsView { + mSplitSelectStateController.setSecondTaskId(taskView); + resetFromSplitSelectionState(); + }); + mSecondSplitHiddenTaskView = taskView; + taskView.setVisibility(INVISIBLE); + pendingAnimation.buildAnim().start(); } public PendingAnimation cancelSplitSelect(boolean animate) { - SplitSelectStateController splitController = mSplitPlaceholderView.getSplitController(); + SplitSelectStateController splitController = mSplitSelectStateController; SplitPositionOption splitOption = splitController.getActiveSplitPositionOption(); Rect initialBounds = splitController.getInitialBounds(); splitController.resetState(); @@ -3263,8 +3298,19 @@ public abstract class RecentsView + + + + + + + \ No newline at end of file diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 46570f05e9..dd276859e4 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -326,6 +326,7 @@ 0dp 0dp 0dp + 110dp 22dp diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 3cabc87e31..7d818d2ef5 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -380,6 +380,21 @@ public final class Utilities { return scale; } + /** + * Similar to {@link #scaleRectAboutCenter(Rect, float)} except this allows different scales + * for X and Y + */ + public static void scaleRectFAboutCenter(RectF r, float scaleX, float scaleY) { + float px = r.centerX(); + float py = r.centerY(); + r.offset(-px, -py); + r.left = r.left * scaleX; + r.top = r.top * scaleY; + r.right = r.right * scaleX; + r.bottom = r.bottom * scaleY; + r.offset(px, py); + } + /** * Maps t from one range to another range. * @param t The value to map. diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java index 01f7de66ce..3ab893b0a1 100644 --- a/src/com/android/launcher3/anim/PendingAnimation.java +++ b/src/com/android/launcher3/anim/PendingAnimation.java @@ -56,6 +56,10 @@ public class PendingAnimation implements PropertySetter { mAnim = new AnimatorSet(); } + public long getDuration() { + return mDuration; + } + /** * Utility method to sent an interpolator on an animation and add it to the list */ diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java index d047eca29e..816e5dc398 100644 --- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java +++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java @@ -204,6 +204,16 @@ public class LandscapePagedViewHandler implements PagedOrientationHandler { return Surface.ROTATION_90; } + @Override + public void setPrimaryScale(View view, float scale) { + view.setScaleY(scale); + } + + @Override + public void setSecondaryScale(View view, float scale) { + view.setScaleX(scale); + } + @Override public int getChildStart(View view) { return view.getTop(); @@ -352,6 +362,25 @@ public class LandscapePagedViewHandler implements PagedOrientationHandler { STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN)); } + @Override + public void getInitialSplitPlaceholderBounds(int placeholderHeight, DeviceProfile dp, + SplitPositionOption splitPositionOption, Rect out) { + // In fake land/seascape, the placeholder always needs to go to the "top" of the device, + // which is the same bounds as 0 rotation. + int width = dp.widthPx; + out.set(0, 0, width, placeholderHeight); + } + + @Override + public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp, + SplitPositionOption initialSplitOption, Rect out1, Rect out2) { + // In fake land/seascape, the window bounds are always top and bottom half + int screenHeight = dp.heightPx; + int screenWidth = dp.widthPx; + out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize); + out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight); + } + @Override public FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary, DeviceProfile deviceProfile) { diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java index 266e05fee1..dae2dde730 100644 --- a/src/com/android/launcher3/touch/PagedOrientationHandler.java +++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java @@ -99,6 +99,8 @@ public interface PagedOrientationHandler { boolean getRecentsRtlSetting(Resources resources); float getDegreesRotated(); int getRotation(); + void setPrimaryScale(View view, float scale); + void setSecondaryScale(View view, float scale); T getPrimaryValue(T x, T y); T getSecondaryValue(T x, T y); @@ -114,6 +116,22 @@ public interface PagedOrientationHandler { DeviceProfile deviceProfile); int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect); List getSplitPositionOptions(DeviceProfile dp); + /** + * @param splitholderSize height of placeholder view in portrait, width in landscape + */ + void getInitialSplitPlaceholderBounds(int splitholderSize, DeviceProfile dp, + SplitPositionOption splitPositionOption, Rect out); + + /** + * @param splitDividerSize height of split screen drag handle in portrait, width in landscape + * @param initialSplitOption the split position option (top/left, bottom/right) of the first + * task selected for entering split + * @param out1 the bounds for where the first selected app will be + * @param out2 the bounds for where the second selected app will be, complimentary to + * {@param out1} based on {@param initialSplitOption} + */ + void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp, + SplitPositionOption initialSplitOption, Rect out1, Rect out2); // Overview TaskMenuView methods float getTaskMenuX(float x, View thumbnailView, int overScroll); diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java index dd97af5d76..1253589ef3 100644 --- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java +++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java @@ -24,6 +24,7 @@ import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITIO import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN; import android.content.res.Resources; +import android.graphics.Matrix; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; @@ -47,6 +48,9 @@ import java.util.List; public class PortraitPagedViewHandler implements PagedOrientationHandler { + private final Matrix mTmpMatrix = new Matrix(); + private final RectF mTmpRectF = new RectF(); + @Override public T getPrimaryValue(T x, T y) { return x; @@ -206,6 +210,16 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler { return Surface.ROTATION_0; } + @Override + public void setPrimaryScale(View view, float scale) { + view.setScaleX(scale); + } + + @Override + public void setSecondaryScale(View view, float scale) { + view.setScaleY(scale); + } + @Override public int getChildStart(View view) { return view.getLeft(); @@ -397,6 +411,62 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler { return options; } + @Override + public void getInitialSplitPlaceholderBounds(int placeholderHeight, DeviceProfile dp, + SplitPositionOption splitPositionOption, Rect out) { + int width = dp.widthPx; + out.set(0, 0, width, placeholderHeight); + if (!dp.isLandscape) { + // portrait, phone or tablet - spans width of screen, nothing else to do + return; + } + + // Now we rotate the portrait rect depending on what side we want pinned + boolean pinToRight = splitPositionOption.mStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT; + + int screenHeight = dp.heightPx; + float postRotateScale = (float) screenHeight / width; + mTmpMatrix.reset(); + mTmpMatrix.postRotate(pinToRight ? 90 : 270); + mTmpMatrix.postTranslate(pinToRight ? width : 0, pinToRight ? 0 : width); + // The placeholder height stays constant after rotation, so we don't change width scale + mTmpMatrix.postScale(1, postRotateScale); + + mTmpRectF.set(out); + mTmpMatrix.mapRect(mTmpRectF); + mTmpRectF.roundOut(out); + } + + @Override + public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp, + SplitPositionOption initialSplitOption, Rect out1, Rect out2) { + int screenHeight = dp.heightPx; + int screenWidth = dp.widthPx; + out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize); + out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight); + if (!dp.isLandscape) { + // Portrait - the window bounds are always top and bottom half + return; + } + + // Now we rotate the portrait rect depending on what side we want pinned + boolean pinToRight = initialSplitOption.mStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT; + float postRotateScale = (float) screenHeight / screenWidth; + + mTmpMatrix.reset(); + mTmpMatrix.postRotate(pinToRight ? 90 : 270); + mTmpMatrix.postTranslate(pinToRight ? screenHeight : 0, pinToRight ? 0 : screenWidth); + mTmpMatrix.postScale(1 / postRotateScale, postRotateScale); + + mTmpRectF.set(out1); + mTmpMatrix.mapRect(mTmpRectF); + mTmpRectF.roundOut(out1); + + mTmpRectF.set(out2); + mTmpMatrix.mapRect(mTmpRectF); + mTmpRectF.roundOut(out2); + } + @Override public FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary, DeviceProfile dp) {