Merge "Animate SplitPlaceholderView when entering split from overview" into sc-v2-dev

This commit is contained in:
TreeHugger Robot
2021-07-22 21:17:35 +00:00
committed by Android (Google) Code Review
19 changed files with 542 additions and 97 deletions

View File

@@ -43,7 +43,6 @@
<dimen name="overview_grid_side_margin">54dp</dimen>
<dimen name="overview_grid_row_spacing">42dp</dimen>
<dimen name="overview_grid_focus_vertical_margin">40dp</dimen>
<dimen name="split_placeholder_size">110dp</dimen>
<!-- These speeds are in dp/s -->
<dimen name="max_task_dismiss_drag_velocity">2.25dp</dimen>

View File

@@ -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));

View File

@@ -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

View File

@@ -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;

View File

@@ -227,7 +227,8 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
if (goingUp) {
currentInterpolator = Interpolators.LINEAR;
pa = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
true /* animateTaskView */, true /* removeTask */, maxDuration);
true /* animateTaskView */, true /* removeTask */, maxDuration,
false /* dismissingForSplitSelection*/);
mEndDisplacement = -secondaryTaskDimension;
} else {

View File

@@ -122,13 +122,10 @@ public final class RecentsActivity extends StatefulActivity<RecentsState> {
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

View File

@@ -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<RecentsActivity, RecentsSta
}
@Override
public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
super.init(actionsView, splitPlaceholderView);
public void init(OverviewActionsView actionsView, SplitSelectStateController splitController) {
super.init(actionsView, splitController);
setOverviewStateEnabled(true);
setOverlayEnabled(true);
}
@@ -96,7 +97,8 @@ public class FallbackRecentsView extends RecentsView<RecentsActivity, RecentsSta
if (mHomeTaskInfo != null && endTarget == RECENTS && animatorSet != null) {
TaskView tv = getTaskView(mHomeTaskInfo.taskId);
if (tv != null) {
PendingAnimation pa = createTaskDismissAnimation(tv, true, false, 150);
PendingAnimation pa = createTaskDismissAnimation(tv, true, false, 150,
false /* dismissingForSplitSelection*/);
pa.addEndListener(e -> setCurrentTask(-1));
AnimatorPlaybackController controller = pa.createPlaybackController();
controller.dispatchOnStart();

View File

@@ -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() {

View File

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

View File

@@ -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<BaseQuickstepLauncher, Laun
}
@Override
public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
public void init(OverviewActionsView actionsView,
SplitSelectStateController splitPlaceholderView) {
super.init(actionsView, splitPlaceholderView);
setContentAlpha(0);
}

View File

@@ -545,15 +545,18 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
/**
* Placeholder view indicating where the first split screen selected app will be placed
*/
private SplitPlaceholderView mSplitPlaceholderView;
private SplitSelectStateController mSplitSelectStateController;
/**
* The first task that split screen selection was initiated with. When split select state is
* initialized, we create a
* {@link #createTaskDismissAnimation(TaskView, boolean, boolean, long)} for this TaskView but
* don't actually remove the task since the user might back out. As such, we also ensure this
* View doesn't go back into the {@link #mTaskViewPool}, see {@link #onViewRemoved(View)}
* {@link #createTaskDismissAnimation(TaskView, boolean, boolean, long, boolean)} for this
* TaskView but don't actually remove the task since the user might back out. As such, we also
* ensure this View doesn't go back into the {@link #mTaskViewPool},
* see {@link #onViewRemoved(View)}
*/
private TaskView mSplitHiddenTaskView;
private TaskView mSecondSplitHiddenTaskView;
/**
* Keeps track of the index of the TaskView that split screen was initialized with so we know
* where to insert it back into list of taskViews in case user backs out of entering split
@@ -563,6 +566,9 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
* removed from recentsView
*/
private int mSplitHiddenTaskViewIndex;
private FloatingTaskView mFirstFloatingTaskView;
private FloatingTaskView mSecondFloatingTaskView;
/**
* The task to be removed and immediately re-added. Should not be added to task pool.
*/
@@ -776,18 +782,18 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
updateTaskStackListenerState();
}
public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
public void init(OverviewActionsView actionsView, SplitSelectStateController splitController) {
mActionsView = actionsView;
mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
mSplitPlaceholderView = splitPlaceholderView;
mSplitSelectStateController = splitController;
}
public SplitPlaceholderView getSplitPlaceholder() {
return mSplitPlaceholderView;
public SplitSelectStateController getSplitPlaceholder() {
return mSplitSelectStateController;
}
public boolean isSplitSelectionActive() {
return mSplitPlaceholderView.getSplitController().isSplitSelectActive();
return mSplitSelectStateController.isSplitSelectActive();
}
@Override
@@ -984,7 +990,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
// Reset the running task when leaving overview since it can still have a reference to
// its thumbnail
mTmpRunningTask = null;
if (mSplitPlaceholderView.getSplitController().isSplitSelectActive()) {
if (mSplitSelectStateController.isSplitSelectActive()) {
cancelSplitSelect(false);
}
}
@@ -2286,50 +2292,23 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
PendingAnimation anim) {
// Use setFloat instead of setViewAlpha as we want to keep the view visible even when it's
// alpha is set to 0 so that it can be recycled in the view pool properly
anim.setFloat(taskView, VIEW_ALPHA, 0, clampToProgress(ACCEL, 0, 0.5f));
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && taskView.isRunningTask()) {
anim.setFloat(mLiveTileParams, TransformParams.TARGET_ALPHA, 0,
clampToProgress(ACCEL, 0, 0.5f));
}
SplitSelectStateController splitController = mSplitPlaceholderView.getSplitController();
anim.setFloat(taskView, VIEW_ALPHA, 0, clampToProgress(ACCEL, 0, 0.5f));
FloatProperty<TaskView> 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<TaskView> 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<ACTIVITY_TYPE extends StatefulActivity<STATE_T
}
}
/**
* Places an {@link FloatingTaskView} on top of the thumbnail for {@link #mSplitHiddenTaskView}
* and then animates it into the split position that was desired
*/
private void createInitialSplitSelectAnimation(PendingAnimation anim) {
float placeholderHeight = getResources().getDimension(R.dimen.split_placeholder_size);
mOrientationHandler.getInitialSplitPlaceholderBounds((int) placeholderHeight,
mActivity.getDeviceProfile(),
mSplitSelectStateController.getActiveSplitPositionOption(), mTempRect);
RectF startingTaskRect = new RectF();
mSplitHiddenTaskView.setVisibility(INVISIBLE);
mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
mSplitHiddenTaskView, startingTaskRect);
mFirstFloatingTaskView.setAlpha(1);
mFirstFloatingTaskView.addAnimation(anim, startingTaskRect,
mTempRect, mSplitHiddenTaskView, true /*fadeWithThumbnail*/);
}
/**
* Creates a {@link PendingAnimation} for dismissing the specified {@link TaskView}.
* @param dismissedTaskView the {@link TaskView} to be dismissed
@@ -2350,9 +2348,12 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
* @param shouldRemoveTask whether the associated {@link Task} should be removed from
* ActivityManager after dismissal
* @param duration duration of the animation
* @param dismissingForSplitSelection task dismiss animation is used for entering split
* selection state from app icon
*/
public PendingAnimation createTaskDismissAnimation(TaskView dismissedTaskView,
boolean animateTaskView, boolean shouldRemoveTask, long duration) {
boolean animateTaskView, boolean shouldRemoveTask, long duration,
boolean dismissingForSplitSelection) {
if (mPendingAnimation != null) {
mPendingAnimation.createPlaybackController().dispatchOnCancel().dispatchOnEnd();
}
@@ -2419,7 +2420,11 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
View child = getChildAt(i);
if (child == dismissedTaskView) {
if (animateTaskView) {
addDismissedTaskAnimations(dismissedTaskView, duration, anim);
if (dismissingForSplitSelection) {
createInitialSplitSelectAnimation(anim);
} else {
addDismissedTaskAnimations(dismissedTaskView, duration, anim);
}
}
} else if (!showAsGrid) {
// Compute scroll offsets from task dismissal for animation.
@@ -2602,7 +2607,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
/**
* @return {@code true} if one of the task thumbnails would intersect/overlap with the
* {@link #mSplitPlaceholderView}
* {@link #mFirstFloatingTaskView}
*/
public boolean shouldShiftThumbnailsForSplitSelect(@SplitConfigurationOptions.StagePosition
int stagePosition) {
@@ -2705,7 +2710,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
public void dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask) {
runDismissAnimation(createTaskDismissAnimation(taskView, animateTaskView, removeTask,
DISMISS_TASK_DURATION));
DISMISS_TASK_DURATION, false /* dismissingForSplitSelection*/));
}
@SuppressWarnings("unused")
@@ -3107,8 +3112,11 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
protected void setTaskViewsSecondarySplitTranslation(float translation) {
mTaskViewsSecondarySplitTranslation = translation;
for (int i = 0; i < getTaskViewCount(); i++) {
TaskView task = getTaskViewAt(i);
task.getSecondarySplitTranslationProperty().set(task, translation);
TaskView taskView = getTaskViewAt(i);
if (taskView == mSplitHiddenTaskView) {
continue;
}
taskView.getSecondarySplitTranslationProperty().set(taskView, translation);
}
}
@@ -3124,15 +3132,11 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
public void initiateSplitSelect(TaskView taskView, SplitPositionOption splitPositionOption) {
mSplitHiddenTaskView = taskView;
SplitSelectStateController splitController = mSplitPlaceholderView.getSplitController();
Rect initialBounds = new Rect(taskView.getLeft(), taskView.getTop(), taskView.getRight(),
taskView.getBottom());
splitController.setInitialTaskSelect(taskView, splitPositionOption, initialBounds);
mSplitSelectStateController.setInitialTaskSelect(taskView,
splitPositionOption, initialBounds);
mSplitHiddenTaskViewIndex = indexOfChild(taskView);
mSplitPlaceholderView.setLayoutParams(
splitController.getLayoutParamsForActivePosition(getResources(),
mActivity.getDeviceProfile()));
mSplitPlaceholderView.setIcon(taskView.getIconView());
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
finishRecentsAnimation(true, null);
}
@@ -3140,17 +3144,48 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
public PendingAnimation createSplitSelectInitAnimation() {
int duration = mActivity.getStateManager().getState().getTransitionDuration(getContext());
return createTaskDismissAnimation(mSplitHiddenTaskView, true, false, duration);
return createTaskDismissAnimation(mSplitHiddenTaskView, true, false, duration,
true /* dismissingForSplitSelection*/);
}
public void confirmSplitSelect(TaskView taskView) {
mSplitPlaceholderView.getSplitController().setSecondTaskId(taskView);
resetTaskVisuals();
setTranslationY(0);
RectF secondTaskStartingBounds = new RectF();
Rect secondTaskEndingBounds = new Rect();
// TODO(194414938) starting bounds seem slightly off, investigate
Rect firstTaskStartingBounds = new Rect();
Rect firstTaskEndingBounds = mTempRect;
int duration = mActivity.getStateManager().getState().getTransitionDuration(getContext());
PendingAnimation pendingAnimation = new PendingAnimation(duration);
int halfDividerSize = getResources()
.getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
mOrientationHandler.getFinalSplitPlaceholderBounds(halfDividerSize,
mActivity.getDeviceProfile(),
mSplitSelectStateController.getActiveSplitPositionOption(), firstTaskEndingBounds,
secondTaskEndingBounds);
mFirstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
mFirstFloatingTaskView.addAnimation(pendingAnimation,
new RectF(firstTaskStartingBounds), firstTaskEndingBounds, mFirstFloatingTaskView,
false /*fadeWithThumbnail*/);
mSecondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
taskView, secondTaskStartingBounds);
mSecondFloatingTaskView.setAlpha(1);
mSecondFloatingTaskView.addAnimation(pendingAnimation, secondTaskStartingBounds,
secondTaskEndingBounds, taskView.getThumbnail(),
true /*fadeWithThumbnail*/);
pendingAnimation.addEndListener(aBoolean -> {
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<ACTIVITY_TYPE extends StatefulActivity<STATE_T
}
onLayout(false /* changed */, getLeft(), getTop(), getRight(), getBottom());
resetTaskVisuals();
mSplitHiddenTaskView.setVisibility(VISIBLE);
mSplitHiddenTaskView = null;
mSecondSplitHiddenTaskView.setVisibility(VISIBLE);
mSecondSplitHiddenTaskView = null;
mSplitHiddenTaskViewIndex = -1;
if (mFirstFloatingTaskView != null) {
mActivity.getRootView().removeView(mFirstFloatingTaskView);
mFirstFloatingTaskView = null;
}
if (mSecondFloatingTaskView != null) {
mActivity.getRootView().removeView(mSecondFloatingTaskView);
mSecondFloatingTaskView = null;
}
}
private void updateDeadZoneRects() {

View File

@@ -22,6 +22,8 @@ import android.util.FloatProperty;
import android.view.Gravity;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
import com.android.quickstep.util.SplitSelectStateController;
public class SplitPlaceholderView extends FrameLayout {
@@ -55,6 +57,11 @@ public class SplitPlaceholderView extends FrameLayout {
return mSplitController;
}
@Nullable
public IconView getIcon() {
return mIcon;
}
public void setIcon(IconView icon) {
if (mIcon == null) {
mIcon = new IconView(getContext());

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<com.android.quickstep.views.FloatingTaskView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
<com.android.quickstep.views.SplitPlaceholderView
android:id="@+id/split_placeholder"
android:layout_width="match_parent"
android:layout_height="@dimen/split_placeholder_size"
android:background="@android:color/white"
android:visibility="gone" />
</com.android.quickstep.views.FloatingTaskView>

View File

@@ -326,6 +326,7 @@
<dimen name="overview_task_margin">0dp</dimen>
<dimen name="overview_actions_bottom_margin_gesture">0dp</dimen>
<dimen name="overview_actions_bottom_margin_three_button">0dp</dimen>
<dimen name="split_placeholder_size">110dp</dimen>
<!-- Workspace grid visualization parameters -->
<dimen name="grid_visualization_rounding_radius">22dp</dimen>

View File

@@ -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.

View File

@@ -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
*/

View File

@@ -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) {

View File

@@ -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> T getPrimaryValue(T x, T y);
<T> T getSecondaryValue(T x, T y);
@@ -114,6 +116,22 @@ public interface PagedOrientationHandler {
DeviceProfile deviceProfile);
int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect);
List<SplitPositionOption> 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);

View File

@@ -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> 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) {