diff --git a/quickstep/res/drawable/bg_workspace_card_button.xml b/quickstep/res/drawable/bg_workspace_card_button.xml
new file mode 100644
index 0000000000..3ba47bb4d1
--- /dev/null
+++ b/quickstep/res/drawable/bg_workspace_card_button.xml
@@ -0,0 +1,30 @@
+
+
+
+
+ -
+
+
+
+ -
+
+
+
diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml
index 22f014a521..78238fab9f 100644
--- a/quickstep/res/layout/overview_panel.xml
+++ b/quickstep/res/layout/overview_panel.xml
@@ -22,4 +22,36 @@
android:clipChildren="false"
android:clipToPadding="false"
android:alpha="0.0"
- android:visibility="invisible" />
\ No newline at end of file
+ android:visibility="invisible" >
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index 267c4d06f4..f34aa85c97 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -22,6 +22,7 @@ import android.view.View;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.quickstep.RecentsView;
@@ -31,8 +32,9 @@ import com.android.quickstep.RecentsView;
*/
public class OverviewState extends LauncherState {
- private static final int STATE_FLAGS = FLAG_SHOW_SCRIM
- | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED;
+ public static final float WORKSPACE_SCALE_ON_SCROLL = 0.9f;
+
+ private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED;
public OverviewState(int id) {
super(id, ContainerType.WORKSPACE, OVERVIEW_TRANSITION_MS, 1f, STATE_FLAGS);
@@ -42,18 +44,15 @@ public class OverviewState extends LauncherState {
public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
Rect pageRect = new Rect();
RecentsView.getPageRect(launcher, pageRect);
- Workspace ws = launcher.getWorkspace();
- float childWidth = ws.getNormalChildWidth();
- if (childWidth <= 0 || pageRect.isEmpty()) {
+ if (launcher.getWorkspace().getNormalChildWidth() <= 0 || pageRect.isEmpty()) {
return super.getWorkspaceScaleAndTranslation(launcher);
}
- Rect insets = launcher.getDragLayer().getInsets();
- float scale = pageRect.width() / childWidth;
-
- float halfHeight = ws.getHeight() / 2;
- float childTop = halfHeight - scale * (halfHeight - ws.getPaddingTop() - insets.top);
- return new float[] {scale, pageRect.top - childTop};
+ RecentsView rv = launcher.getOverviewPanel();
+ if (rv.getCurrentPage() >= rv.getFirstTaskIndex()) {
+ Utilities.scaleRectAboutCenter(pageRect, WORKSPACE_SCALE_ON_SCROLL);
+ }
+ return getScaleAndTranslationForPageRect(launcher, pageRect);
}
@Override
@@ -77,4 +76,16 @@ public class OverviewState extends LauncherState {
public View getFinalFocus(Launcher launcher) {
return launcher.getOverviewPanel();
}
+
+ public static float[] getScaleAndTranslationForPageRect(Launcher launcher, Rect pageRect) {
+ Workspace ws = launcher.getWorkspace();
+ float childWidth = ws.getNormalChildWidth();
+
+ Rect insets = launcher.getDragLayer().getInsets();
+ float scale = pageRect.width() / childWidth;
+
+ float halfHeight = ws.getHeight() / 2;
+ float childTop = halfHeight - scale * (halfHeight - ws.getPaddingTop() - insets.top);
+ return new float[] {scale, pageRect.top - childTop};
+ }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 60cd0c2be3..1f6ffe9fdc 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -30,10 +30,13 @@ import com.android.launcher3.anim.Interpolators;
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.RecentsView;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+
public class RecentsViewStateController implements StateHandler {
private final Launcher mLauncher;
private final RecentsView mRecentsView;
+ private final WorkspaceCard mWorkspaceCard;
private final AnimatedFloat mTransitionProgress = new AnimatedFloat(this::applyProgress);
// The fraction representing the visibility of the RecentsView. This allows delaying the
@@ -44,24 +47,40 @@ public class RecentsViewStateController implements StateHandler {
mLauncher = launcher;
mRecentsView = launcher.getOverviewPanel();
mRecentsView.setStateController(this);
+
+ mWorkspaceCard = (WorkspaceCard) mRecentsView.getChildAt(0);
+ mWorkspaceCard.setup(launcher);
}
@Override
public void setState(LauncherState state) {
- setVisibility(state == LauncherState.OVERVIEW);
- setTransitionProgress(state == LauncherState.OVERVIEW ? 1 : 0);
+ mWorkspaceCard.setWorkspaceScrollingEnabled(state == OVERVIEW);
+ setVisibility(state == OVERVIEW);
+ setTransitionProgress(state == OVERVIEW ? 1 : 0);
}
@Override
- public void setStateWithAnimation(LauncherState toState,
+ public void setStateWithAnimation(final LauncherState toState,
AnimatorSetBuilder builder, AnimationConfig config) {
ObjectAnimator progressAnim =
- mTransitionProgress.animateToValue(toState == LauncherState.OVERVIEW ? 1 : 0);
+ mTransitionProgress.animateToValue(toState == OVERVIEW ? 1 : 0);
progressAnim.setDuration(config.duration);
progressAnim.setInterpolator(Interpolators.LINEAR);
+ progressAnim.addListener(new AnimationSuccessListener() {
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mWorkspaceCard.setWorkspaceScrollingEnabled(false);
+ }
+
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ mWorkspaceCard.setWorkspaceScrollingEnabled(toState == OVERVIEW);
+ }
+ });
builder.play(progressAnim);
- ObjectAnimator visibilityAnim = animateVisibility(toState == LauncherState.OVERVIEW);
+ ObjectAnimator visibilityAnim = animateVisibility(toState == OVERVIEW);
visibilityAnim.setDuration(config.duration);
visibilityAnim.setInterpolator(Interpolators.LINEAR);
builder.play(visibilityAnim);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java b/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java
new file mode 100644
index 0000000000..990d28648f
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.uioverrides;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.uioverrides.OverviewState.WORKSPACE_SCALE_ON_SCROLL;
+import static com.android.quickstep.RecentsView.SCROLL_TYPE_WORKSPACE;
+
+import android.animation.FloatArrayEvaluator;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.FrameLayout;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.widget.WidgetsFullSheet;
+import com.android.quickstep.RecentsView;
+import com.android.quickstep.RecentsView.PageCallbacks;
+import com.android.quickstep.RecentsView.ScrollState;
+
+public class WorkspaceCard extends FrameLayout implements PageCallbacks, OnClickListener {
+
+ private final Rect mTempRect = new Rect();
+ private final float[] mEvaluatedFloats = new float[2];
+ private final FloatArrayEvaluator mEvaluator = new FloatArrayEvaluator(mEvaluatedFloats);
+
+ // UI related information
+ private float[] mScaleAndTranslatePage0, mScaleAndTranslatePage1;
+ private boolean mUIDataValid = false;
+
+ private Launcher mLauncher;
+ private Workspace mWorkspace;
+
+ private boolean mIsWorkspaceScrollingEnabled;
+
+ private View mWorkspaceClickTarget;
+ private View mWidgetsButton;
+
+ public WorkspaceCard(Context context) {
+ super(context);
+ }
+
+ public WorkspaceCard(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public WorkspaceCard(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mWorkspaceClickTarget = findViewById(R.id.workspace_click_target);
+ mWidgetsButton = findViewById(R.id.widget_button);
+
+ mWorkspaceClickTarget.setOnClickListener(this);
+ mWidgetsButton.setOnClickListener(this);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // We measure the dimensions of the PagedView to be larger than the pages so that when we
+ // zoom out (and scale down), the view is still contained in the parent
+ int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ return;
+ }
+
+ // Return early if we aren't given a proper dimension
+ if (widthSize <= 0 || heightSize <= 0) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ return;
+ }
+
+ int childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
+
+ int pageHeight = mWorkspace.getNormalChildHeight() * widthSize /
+ mWorkspace.getNormalChildWidth();
+ mWorkspaceClickTarget.measure(childWidthSpec,
+ MeasureSpec.makeMeasureSpec(pageHeight, MeasureSpec.EXACTLY));
+
+ int buttonHeight = heightSize - pageHeight - getPaddingTop() - getPaddingBottom();
+ mWidgetsButton.measure(childWidthSpec,
+ MeasureSpec.makeMeasureSpec(buttonHeight, MeasureSpec.EXACTLY));
+
+ setMeasuredDimension(widthSize, heightSize);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ int y1 = getPaddingTop();
+ int y2 = y1 + mWorkspaceClickTarget.getMeasuredHeight();
+
+ mWorkspaceClickTarget.layout(getPaddingLeft(), y1,
+ mWorkspaceClickTarget.getMeasuredWidth(), y2);
+
+ mWidgetsButton.layout(getPaddingLeft(), y2, mWidgetsButton.getMeasuredWidth(),
+ mWidgetsButton.getMeasuredHeight() + y2);
+
+ mUIDataValid = false;
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (view == mWorkspaceClickTarget) {
+ mLauncher.getStateManager().goToState(NORMAL);
+ } else if (view == mWidgetsButton) {
+ WidgetsFullSheet.show(mLauncher, true);
+ }
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ mUIDataValid = false;
+ }
+
+ public void setup(Launcher launcher) {
+ mLauncher = launcher;
+ mWorkspace = mLauncher.getWorkspace();
+ }
+
+ public void setWorkspaceScrollingEnabled(boolean isEnabled) {
+ mIsWorkspaceScrollingEnabled = isEnabled;
+ }
+
+ @Override
+ public int onPageScroll(ScrollState scrollState) {
+ setTranslationX(scrollState.distanceFromScreenCenter);
+
+ float factor = scrollState.linearInterpolation;
+ float scale = factor * WORKSPACE_SCALE_ON_SCROLL + (1 - factor);
+ setScaleX(scale);
+ setScaleY(scale);
+
+ if (mIsWorkspaceScrollingEnabled) {
+ initUiData();
+
+ mEvaluator.evaluate(factor, mScaleAndTranslatePage0, mScaleAndTranslatePage1);
+ mWorkspace.setScaleX(mEvaluatedFloats[0]);
+ mWorkspace.setScaleY(mEvaluatedFloats[0]);
+ mWorkspace.setTranslationY(mEvaluatedFloats[1]);
+ }
+ return SCROLL_TYPE_WORKSPACE;
+ }
+
+ private void initUiData() {
+ if (mUIDataValid && mScaleAndTranslatePage0 != null) {
+ return;
+ }
+
+ RecentsView.getPageRect(mLauncher, mTempRect);
+ mScaleAndTranslatePage0 = OverviewState
+ .getScaleAndTranslationForPageRect(mLauncher, mTempRect);
+ Rect scaledDown = new Rect(mTempRect);
+ Utilities.scaleRectAboutCenter(scaledDown, WORKSPACE_SCALE_ON_SCROLL);
+ mScaleAndTranslatePage1 = OverviewState
+ .getScaleAndTranslationForPageRect(mLauncher, scaledDown);
+ mUIDataValid = true;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
index 43a01a79c6..095b44518c 100644
--- a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
+++ b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
@@ -250,6 +250,7 @@ public class NavBarSwipeInteractionHandler extends InternalStateHandler {
private void setTaskPlanToUi() {
mRecentsView.update(mLoadPlan);
+ mRecentsView.initToPage(mRecentsView.getFirstTaskIndex());
ObjectAnimator anim = mStateController.animateVisibility(true /* isVisible */)
.setDuration(RECENTS_VIEW_VISIBILITY_DURATION);
anim.addListener(new AnimationSuccessListener() {
diff --git a/quickstep/src/com/android/quickstep/RecentsView.java b/quickstep/src/com/android/quickstep/RecentsView.java
index fd9010dffa..6161858cb3 100644
--- a/quickstep/src/com/android/quickstep/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/RecentsView.java
@@ -17,7 +17,6 @@
package com.android.quickstep;
import android.animation.LayoutTransition;
-import android.animation.TimeInterpolator;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
@@ -47,17 +46,11 @@ import java.util.ArrayList;
*/
public class RecentsView extends PagedView {
- /** Designates how "curvy" the carousel is from 0 to 1, where 0 is a straight line. */
- private static final float CURVE_FACTOR = 0.25f;
- /** A circular curve of x from 0 to 1, where 0 is the center of the screen and 1 is the edge. */
- private static final TimeInterpolator CURVE_INTERPOLATOR
- = x -> (float) (1 - Math.sqrt(1 - Math.pow(x, 2)));
- /**
- * The alpha of a black scrim on a page in the carousel as it leaves the screen.
- * In the resting position of the carousel, the adjacent pages have about half this scrim.
- */
- private static final float MAX_PAGE_SCRIM_ALPHA = 0.8f;
+ public static final int SCROLL_TYPE_NONE = 0;
+ public static final int SCROLL_TYPE_TASK = 1;
+ public static final int SCROLL_TYPE_WORKSPACE = 2;
+ private final ScrollState mScrollState = new ScrollState();
private boolean mOverviewStateEnabled;
private boolean mTaskStackListenerRegistered;
private LayoutTransition mLayoutTransition;
@@ -65,7 +58,7 @@ public class RecentsView extends PagedView {
private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
@Override
public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
- for (int i = 0; i < getChildCount(); i++) {
+ for (int i = mFirstTaskIndex; i < getChildCount(); i++) {
final TaskView taskView = (TaskView) getChildAt(i);
if (taskView.getTask().key.id == taskId) {
taskView.getThumbnail().setThumbnail(snapshot);
@@ -76,6 +69,7 @@ public class RecentsView extends PagedView {
};
private RecentsViewStateController mStateController;
+ private int mFirstTaskIndex;
public RecentsView(Context context) {
this(context, null);
@@ -87,10 +81,12 @@ public class RecentsView extends PagedView {
public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- setWillNotDraw(false);
- setPageSpacing((int) getResources().getDimension(R.dimen.recents_page_spacing));
+ setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing));
enableFreeScroll(true);
+ setClipChildren(true);
setupLayoutTransition();
+
+ mScrollState.isRtl = mIsRtl;
}
private void setupLayoutTransition() {
@@ -110,6 +106,8 @@ public class RecentsView extends PagedView {
Rect padding = getPadding(Launcher.getLauncher(getContext()));
setPadding(padding.left, padding.top, padding.right, padding.bottom);
+
+ mFirstTaskIndex = getPageCount();
}
@Override
@@ -130,6 +128,10 @@ public class RecentsView extends PagedView {
updateTaskStackListenerState();
}
+ public int getFirstTaskIndex() {
+ return mFirstTaskIndex;
+ }
+
public void setStateController(RecentsViewStateController stateController) {
mStateController = stateController;
}
@@ -145,10 +147,6 @@ public class RecentsView extends PagedView {
public void update(RecentsTaskLoadPlan loadPlan) {
final RecentsTaskLoader loader = TouchInteractionService.getRecentsTaskLoader();
- setCurrentPage(0);
- if (getPageAt(mCurrentPage) instanceof TaskView) {
- ((TaskView) getPageAt(mCurrentPage)).setIconScale(0);
- }
TaskStack stack = loadPlan != null ? loadPlan.getTaskStack() : null;
if (stack == null) {
removeAllViews();
@@ -160,11 +158,13 @@ public class RecentsView extends PagedView {
final LayoutInflater inflater = LayoutInflater.from(getContext());
final ArrayList tasks = stack.getTasks();
setLayoutTransition(null);
- for (int i = getChildCount(); i < tasks.size(); i++) {
+ int requiredChildCount = tasks.size() + mFirstTaskIndex;
+
+ for (int i = getChildCount(); i < requiredChildCount; i++) {
final TaskView taskView = (TaskView) inflater.inflate(R.layout.task, this, false);
addView(taskView);
}
- while (getChildCount() > tasks.size()) {
+ while (getChildCount() > requiredChildCount) {
final TaskView taskView = (TaskView) getChildAt(getChildCount() - 1);
removeView(taskView);
loader.unloadTaskData(taskView.getTask());
@@ -174,14 +174,21 @@ public class RecentsView extends PagedView {
// Rebind all task views
for (int i = tasks.size() - 1; i >= 0; i--) {
final Task task = tasks.get(i);
- final TaskView taskView = (TaskView) getChildAt(tasks.size() - i - 1);
+ final TaskView taskView = (TaskView) getChildAt(tasks.size() - i - 1 + mFirstTaskIndex);
taskView.bind(task);
loader.loadTaskData(task);
}
}
+ public void initToPage(int pageNo) {
+ setCurrentPage(pageNo);
+ if (getPageAt(mCurrentPage) instanceof TaskView) {
+ ((TaskView) getPageAt(mCurrentPage)).setIconScale(0);
+ }
+ }
+
public void launchTaskWithId(int taskId) {
- for (int i = 0; i < getChildCount(); i++) {
+ for (int i = mFirstTaskIndex; i < getChildCount(); i++) {
final TaskView taskView = (TaskView) getChildAt(i);
if (taskView.getTask().key.id == taskId) {
taskView.launchTask(false /* animate */);
@@ -245,35 +252,50 @@ public class RecentsView extends PagedView {
}
final int halfScreenWidth = getMeasuredWidth() / 2;
final int screenCenter = halfScreenWidth + getScrollX();
- final int pageSpacing = getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
+ final int pageSpacing = mPageSpacing;
+ final int halfPageWidth = mScrollState.halfPageWidth = getNormalChildWidth() / 2;
+ mScrollState.lastScrollType = SCROLL_TYPE_NONE;
+
final int pageCount = getPageCount();
for (int i = 0; i < pageCount; i++) {
View page = getPageAt(i);
- int pageWidth = page.getMeasuredWidth();
- int halfPageWidth = pageWidth / 2;
int pageCenter = page.getLeft() + halfPageWidth;
- float distanceFromScreenCenter = Math.abs(pageCenter - screenCenter);
+ mScrollState.distanceFromScreenCenter = screenCenter - pageCenter;
float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing;
- float linearInterpolation = Math.min(1, distanceFromScreenCenter / distanceToReachEdge);
- float curveInterpolation = CURVE_INTERPOLATOR.getInterpolation(linearInterpolation);
- float scale = 1 - curveInterpolation * CURVE_FACTOR;
- page.setScaleX(scale);
- page.setScaleY(scale);
- // Make sure the biggest card (i.e. the one in front) shows on top of the adjacent ones.
- page.setTranslationZ(scale);
- page.setTranslationX((screenCenter - pageCenter) * curveInterpolation * CURVE_FACTOR);
- if (page instanceof TaskView) {
- TaskThumbnailView thumbnail = ((TaskView) page).getThumbnail();
- thumbnail.setDimAlpha(1 - curveInterpolation * MAX_PAGE_SCRIM_ALPHA);
- }
+ mScrollState.linearInterpolation = Math.min(1,
+ Math.abs(mScrollState.distanceFromScreenCenter) / distanceToReachEdge);
+ mScrollState.lastScrollType = ((PageCallbacks) page).onPageScroll(mScrollState);
}
}
public void onTaskDismissed(TaskView taskView) {
ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
removeView(taskView);
- if (getChildCount() == 0) {
+ if (getChildCount() == mFirstTaskIndex) {
Launcher.getLauncher(getContext()).getStateManager().goToState(LauncherState.NORMAL);
}
}
+
+ public interface PageCallbacks {
+
+ /**
+ * Updates the page UI based on scroll params and returns the type of scroll
+ * effect performed.
+ *
+ * @see #SCROLL_TYPE_NONE
+ * @see #SCROLL_TYPE_TASK
+ * @see #SCROLL_TYPE_WORKSPACE
+ */
+ int onPageScroll(ScrollState scrollState);
+ }
+
+ public static class ScrollState {
+
+ public boolean isRtl;
+ public int lastScrollType;
+
+ public int halfPageWidth;
+ public float distanceFromScreenCenter;
+ public float linearInterpolation;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/TaskView.java b/quickstep/src/com/android/quickstep/TaskView.java
index d834881f60..6b37adae66 100644
--- a/quickstep/src/com/android/quickstep/TaskView.java
+++ b/quickstep/src/com/android/quickstep/TaskView.java
@@ -16,9 +16,13 @@
package com.android.quickstep;
+import static com.android.quickstep.RecentsView.SCROLL_TYPE_TASK;
+import static com.android.quickstep.RecentsView.SCROLL_TYPE_WORKSPACE;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.app.ActivityOptions;
import android.content.Context;
@@ -35,6 +39,8 @@ import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.touch.SwipeDetector;
+import com.android.quickstep.RecentsView.PageCallbacks;
+import com.android.quickstep.RecentsView.ScrollState;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskCallbacks;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -49,7 +55,20 @@ import java.util.List;
/**
* A task in the Recents view.
*/
-public class TaskView extends FrameLayout implements TaskCallbacks, SwipeDetector.Listener {
+public class TaskView extends FrameLayout implements TaskCallbacks, SwipeDetector.Listener,
+ PageCallbacks {
+
+ /** Designates how "curvy" the carousel is from 0 to 1, where 0 is a straight line. */
+ private static final float CURVE_FACTOR = 0.25f;
+ /** A circular curve of x from 0 to 1, where 0 is the center of the screen and 1 is the edge. */
+ private static final TimeInterpolator CURVE_INTERPOLATOR
+ = x -> (float) (1 - Math.sqrt(1 - Math.pow(x, 2)));
+
+ /**
+ * The alpha of a black scrim on a page in the carousel as it leaves the screen.
+ * In the resting position of the carousel, the adjacent pages have about half this scrim.
+ */
+ private static final float MAX_PAGE_SCRIM_ALPHA = 0.8f;
private static final int SWIPE_DIRECTIONS = SwipeDetector.DIRECTION_POSITIVE;
@@ -288,4 +307,35 @@ public class TaskView extends FrameLayout implements TaskCallbacks, SwipeDetecto
mIconView.setScaleY(mIconScale);
}
}
+
+ @Override
+ public int onPageScroll(ScrollState scrollState) {
+ float curveInterpolation =
+ CURVE_INTERPOLATOR.getInterpolation(scrollState.linearInterpolation);
+ float scale = 1 - curveInterpolation * CURVE_FACTOR;
+ setScaleX(scale);
+ setScaleY(scale);
+
+ // Make sure the biggest card (i.e. the one in front) shows on top of the adjacent ones.
+ setTranslationZ(scale);
+
+ mSnapshotView.setDimAlpha(1 - curveInterpolation * MAX_PAGE_SCRIM_ALPHA);
+
+ float translation =
+ scrollState.distanceFromScreenCenter * curveInterpolation * CURVE_FACTOR;
+ setTranslationX(translation);
+
+ if (scrollState.lastScrollType == SCROLL_TYPE_WORKSPACE) {
+ // Make sure that the task cards do not overlap with the workspace card
+ float min = scrollState.halfPageWidth * (1 - scale);
+ if (scrollState.isRtl) {
+ setTranslationX(Math.min(translation, min));
+ } else {
+ setTranslationX(Math.max(translation, -min));
+ }
+ } else {
+ setTranslationX(translation);
+ }
+ return SCROLL_TYPE_TASK;
+ }
}
diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml
index f6bdd2580c..0a4fec1276 100644
--- a/res/layout-land/launcher.xml
+++ b/res/layout-land/launcher.xml
@@ -50,6 +50,10 @@
android:layout_gravity="bottom|left"
android:background="@drawable/all_apps_handle_landscape" />
+
+
-
-
+
+
-
-
+
+
-
-