mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-28 15:56:49 +00:00
Merge "Animate SplitPlaceholderView when entering split from overview" into sc-v2-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
f2af895b20
@@ -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>
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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() {
|
||||
|
||||
241
quickstep/src/com/android/quickstep/views/FloatingTaskView.java
Normal file
241
quickstep/src/com/android/quickstep/views/FloatingTaskView.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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());
|
||||
|
||||
20
res/layout/floating_split_select_view.xml
Normal file
20
res/layout/floating_split_select_view.xml
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user