From e15e2a8267c677a41bd91eac4d79ee36d467d8d4 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Fri, 15 Dec 2017 13:05:42 -0800 Subject: [PATCH] Adding an empty page in Recents view corresponding to workspace The page is aligned to the workspace card and shows a widgets button in the empty region Change-Id: I479c47a2fbac4b3ef1aaf833d9fe82b5d7e10ddc --- .../res/drawable/bg_workspace_card_button.xml | 30 +++ quickstep/res/layout/overview_panel.xml | 34 +++- .../launcher3/uioverrides/OverviewState.java | 33 ++-- .../RecentsViewStateController.java | 29 ++- .../launcher3/uioverrides/WorkspaceCard.java | 186 ++++++++++++++++++ .../NavBarSwipeInteractionHandler.java | 1 + .../com/android/quickstep/RecentsView.java | 100 ++++++---- .../src/com/android/quickstep/TaskView.java | 52 ++++- res/layout-land/launcher.xml | 8 +- res/layout-port/launcher.xml | 8 +- res/layout-sw720dp/launcher.xml | 8 +- ..._bg.xml => widgets_bottom_sheet_scrim.xml} | 0 src/com/android/launcher3/CellLayout.java | 2 +- src/com/android/launcher3/PagedView.java | 2 +- .../android/launcher3/views/AllAppsScrim.java | 4 +- .../launcher3/widget/WidgetsBottomSheet.java | 2 +- 16 files changed, 424 insertions(+), 75 deletions(-) create mode 100644 quickstep/res/drawable/bg_workspace_card_button.xml create mode 100644 quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java rename res/layout/{gradient_bg.xml => widgets_bottom_sheet_scrim.xml} (100%) 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" /> + + - - + + - - + + - -