<!-- The radius of the rounded corners on a task view. -->
<dimen name="recents_task_view_rounded_corners_radius">3dp</dimen>
+ <!-- The radius of the rounded corners on a task view's shadow. -->
+ <dimen name="recents_task_view_shadow_rounded_corners_radius">18dp</dimen>
<!-- The fraction of the screen height where the clock on the Keyguard has its center. The
max value is used when no notifications are displaying, and the min value is when the
<!-- The radius of the rounded corners on a task view. -->
<dimen name="recents_task_view_rounded_corners_radius">2dp</dimen>
+ <!-- The radius of the rounded corners on a task view's shadow. -->
+ <dimen name="recents_task_view_shadow_rounded_corners_radius">12dp</dimen>
<!-- The min translation in the Z index for the last task. -->
- <dimen name="recents_task_view_z_min">16dp</dimen>
+ <dimen name="recents_task_view_z_min">3dp</dimen>
<!-- The max translation in the Z index for the last task. -->
- <dimen name="recents_task_view_z_max">48dp</dimen>
+ <dimen name="recents_task_view_z_max">24dp</dimen>
<!-- The amount to translate when animating the removal of a task. -->
<dimen name="recents_task_view_remove_anim_translation_x">100dp</dimen>
<!-- The amount to allow the stack to overscroll. -->
<dimen name="recents_stack_overscroll">24dp</dimen>
- <!-- The size of the peek area at the top of the stack. -->
+ <!-- The size of the peek area at the top of the stack (below the status bar). -->
<dimen name="recents_layout_focused_top_peek_size">@dimen/recents_history_button_height</dimen>
- <!-- The size of the peek area at the bottom of the stack. -->
- <dimen name="recents_layout_focused_bottom_peek_size">@dimen/recents_history_button_height</dimen>
+ <!-- The size of each task peek area at the bottom of the stack (above the nav bar). -->
+ <dimen name="recents_layout_focused_bottom_task_peek_size">16dp</dimen>
<!-- The height of the history button. -->
<dimen name="recents_history_button_height">48dp</dimen>
}
}
+ /**
+ * @return the outline alpha.
+ */
+ public float getAlpha() {
+ return mAlpha;
+ }
+
/** Sets the top clip. */
public void setClipTop(int top) {
mClipRect.top = top;
transformOut.alpha = 1f;
transformOut.translationZ = stackLayout.mMaxTranslationZ;
transformOut.dimAlpha = 0f;
+ transformOut.viewOutlineAlpha = TaskStackLayoutAlgorithm.OUTLINE_ALPHA_MAX_VALUE;
transformOut.rect.set(ffRect);
transformOut.rect.offset(stackLayout.mFreeformRect.left, stackLayout.mFreeformRect.top);
transformOut.visible = true;
mStackView.cancelDeferredTaskViewLayoutAnimation();
// Get the final set of task transforms
- mStackView.getLayoutTaskTransforms(newScroll, stackTasks, mTmpFinalTaskTransforms);
+ mStackView.getLayoutTaskTransforms(newScroll, stackLayout.getFocusState(), stackTasks,
+ mTmpFinalTaskTransforms);
// Focus the task view
TaskView newFocusedTaskView = mStackView.getChildViewForTask(newFocusedTask);
// The scale factor to apply to the user movement in the stack to unfocus it
private static final float UNFOCUS_MULTIPLIER = 0.8f;
+ // The distribution of view bounds alpha
+ // XXX: This is a hack because you can currently set the max alpha to be > 1f
+ public static final float OUTLINE_ALPHA_MIN_VALUE = 0f;
+ public static final float OUTLINE_ALPHA_MAX_VALUE = 2f;
+
// The distribution of dim to apply to tasks in the stack
- public static final float DIM_MAX_VALUE = 0.35f;
+ private static final float DIM_MAX_VALUE = 0.35f;
private static final Path UNFOCUSED_DIM_PATH = new Path();
private static final Path FOCUSED_DIM_PATH = new Path();
static {
@ViewDebug.ExportedProperty(category="recents")
private int mFocusedTopPeekHeight;
@ViewDebug.ExportedProperty(category="recents")
- private int mFocusedBottomPeekHeight;
+ private int mFocusedBottomTaskPeekHeight;
// The offset from the top of the stack to the top of the bounds when the stack is scrolled to
// the end
mFocusState = getDefaultFocusState();
mFocusedTopPeekHeight =
res.getDimensionPixelSize(R.dimen.recents_layout_focused_top_peek_size);
- mFocusedBottomPeekHeight =
- res.getDimensionPixelSize(R.dimen.recents_layout_focused_bottom_peek_size);
+ mFocusedBottomTaskPeekHeight =
+ res.getDimensionPixelSize(R.dimen.recents_layout_focused_bottom_task_peek_size);
mMinTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_min);
mMaxTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_max);
boolean isFrontMostTaskInGroup = task.group == null || task.group.isFrontMostTask(task);
if (isFrontMostTaskInGroup) {
- getStackTransform(taskProgress, mInitialScrollP, tmpTransform, null,
+ getStackTransform(taskProgress, mInitialScrollP, mFocusState, tmpTransform, null,
false /* ignoreSingleTaskCase */, false /* forceUpdate */);
float screenY = tmpTransform.rect.top;
boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
*/
public TaskViewTransform getStackTransform(Task task, float stackScroll,
TaskViewTransform transformOut, TaskViewTransform frontTransform) {
- return getStackTransform(task, stackScroll, transformOut, frontTransform,
+ return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform,
false /* forceUpdate */);
}
- public TaskViewTransform getStackTransform(Task task, float stackScroll,
+ public TaskViewTransform getStackTransform(Task task, float stackScroll, float focusState,
TaskViewTransform transformOut, TaskViewTransform frontTransform, boolean forceUpdate) {
if (mFreeformLayoutAlgorithm.isTransformAvailable(task, this)) {
mFreeformLayoutAlgorithm.getTransform(task, transformOut, this);
transformOut.reset();
return transformOut;
}
- getStackTransform(mTaskIndexMap.get(task.key), stackScroll, transformOut,
+ getStackTransform(mTaskIndexMap.get(task.key), stackScroll, focusState, transformOut,
frontTransform, false /* ignoreSingleTaskCase */, forceUpdate);
return transformOut;
}
* internally to ensure that we can calculate the transform for any
* position in the stack.
*/
- public void getStackTransform(float taskProgress, float stackScroll,
+ public void getStackTransform(float taskProgress, float stackScroll, float focusState,
TaskViewTransform transformOut, TaskViewTransform frontTransform,
boolean ignoreSingleTaskCase, boolean forceUpdate) {
SystemServicesProxy ssp = Recents.getSystemServices();
int y;
float z;
float dimAlpha;
+ float viewOutlineAlpha;
if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1 && !ignoreSingleTaskCase) {
// When there is exactly one task, then decouple the task from the stack and just move
// in screen space
y = centerYOffset + getYForDeltaP(tmpP, 0);
z = mMaxTranslationZ;
dimAlpha = 0f;
+ viewOutlineAlpha = (OUTLINE_ALPHA_MIN_VALUE + OUTLINE_ALPHA_MAX_VALUE) / 2f;
} else {
// Otherwise, update the task to the stack layout
float focusedDim = 1f - FOCUSED_DIM_INTERPOLATOR.getInterpolation(focusedRangeX);
y = (mStackRect.top - mTaskRect.top) +
- (int) Utilities.mapRange(mFocusState, unfocusedY, focusedY);
- z = Utilities.clamp01(unfocusedRangeX) * mMaxTranslationZ;
- dimAlpha = Utilities.mapRange(mFocusState, unfocusedDim, focusedDim);
+ (int) Utilities.mapRange(focusState, unfocusedY, focusedY);
+ z = Utilities.mapRange(Utilities.clamp01(unfocusedRangeX), mMinTranslationZ,
+ mMaxTranslationZ);
+ dimAlpha = DIM_MAX_VALUE * Utilities.mapRange(focusState, unfocusedDim, focusedDim);
+ viewOutlineAlpha = Utilities.mapRange(Utilities.clamp01(unfocusedRangeX),
+ OUTLINE_ALPHA_MIN_VALUE, OUTLINE_ALPHA_MAX_VALUE);
}
// Fill out the transform
transformOut.scale = 1f;
transformOut.alpha = 1f;
transformOut.translationZ = z;
- transformOut.dimAlpha = DIM_MAX_VALUE * dimAlpha;
+ transformOut.dimAlpha = dimAlpha;
+ transformOut.viewOutlineAlpha = viewOutlineAlpha;
transformOut.rect.set(mTaskRect);
transformOut.rect.offset(x, y);
Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
// linear pieces that goes from (0,1) through (0.5, peek height offset),
// (0.5, bottom task offsets), and (1,0).
float topPeekHeightPct = (float) mFocusedTopPeekHeight / mStackRect.height();
- float bottomPeekHeightPct = (float) Math.max(mFocusedBottomPeekHeight, mStackRect.bottom -
- mTaskRect.bottom) / mStackRect.height();
+ float bottomPeekHeightPct = Math.max(
+ mSystemInsets.bottom + mFocusedRange.relativeMax * mFocusedBottomTaskPeekHeight,
+ mStackBottomOffset + mFocusedBottomTaskPeekHeight) / mStackRect.height();
Path p = new Path();
p.moveTo(0f, 1f);
p.lineTo(0.5f, 1f - topPeekHeightPct);
mFocusedRange.relativeMin);
float max = Utilities.mapRange(mFocusState, mUnfocusedRange.relativeMax,
mFocusedRange.relativeMax);
- getStackTransform(min, 0f, mBackOfStackTransform, null, true /* ignoreSingleTaskCase */,
- true /* forceUpdate */);
- getStackTransform(max, 0f, mFrontOfStackTransform, null, true /* ignoreSingleTaskCase */,
- true /* forceUpdate */);
+ getStackTransform(min, 0f, mFocusState, mBackOfStackTransform, null,
+ true /* ignoreSingleTaskCase */, true /* forceUpdate */);
+ getStackTransform(max, 0f, mFocusState, mFrontOfStackTransform, null,
+ true /* ignoreSingleTaskCase */, true /* forceUpdate */);
mBackOfStackTransform.visible = true;
mFrontOfStackTransform.visible = true;
}
public void getCurrentTaskTransforms(ArrayList<Task> tasks,
ArrayList<TaskViewTransform> transformsOut) {
Utilities.matchTaskListSize(tasks, transformsOut);
+ float focusState = mLayoutAlgorithm.getFocusState();
for (int i = tasks.size() - 1; i >= 0; i--) {
Task task = tasks.get(i);
TaskViewTransform transform = transformsOut.get(i);
transform.fillIn(tv);
} else {
mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(),
- transform, null, true /* forceUpdate */);
+ focusState, transform, null, true /* forceUpdate */);
}
transform.visible = true;
}
/**
* Returns the task transforms for all the tasks in the stack if the stack was at the given
- * {@param stackScroll}.
+ * {@param stackScroll} and {@param focusState}.
*/
- public void getLayoutTaskTransforms(float stackScroll, ArrayList<Task> tasks,
+ public void getLayoutTaskTransforms(float stackScroll, float focusState, ArrayList<Task> tasks,
ArrayList<TaskViewTransform> transformsOut) {
Utilities.matchTaskListSize(tasks, transformsOut);
for (int i = tasks.size() - 1; i >= 0; i--) {
Task task = tasks.get(i);
TaskViewTransform transform = transformsOut.get(i);
- mLayoutAlgorithm.getStackTransform(task, stackScroll, transform, null,
+ mLayoutAlgorithm.getStackTransform(task, stackScroll, focusState, transform, null,
true /* forceUpdate */);
transform.visible = true;
}
/**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/
@Override
- public void onScrollChanged(float prevScroll, float curScroll, AnimationProps animation) {
+ public void onStackScrollChanged(float prevScroll, float curScroll, AnimationProps animation) {
mUIDozeTrigger.poke();
if (animation != null) {
relayoutTaskViewsOnNextFrame(animation);
private static final boolean DEBUG = false;
public interface TaskStackViewScrollerCallbacks {
- void onScrollChanged(float prevScroll, float curScroll, AnimationProps animation);
+ void onStackScrollChanged(float prevScroll, float curScroll, AnimationProps animation);
}
/**
float prevStackScroll = mStackScrollP;
mStackScrollP = s;
if (mCb != null) {
- mCb.onScrollChanged(prevStackScroll, mStackScrollP, animation);
+ mCb.onStackScrollChanged(prevStackScroll, mStackScrollP, animation);
}
}
mCurrentTasks = mSv.getStack().getStackTasks();
MutableBoolean isFrontMostTask = new MutableBoolean(false);
Task anchorTask = mSv.findAnchorTask(mCurrentTasks, isFrontMostTask);
+ TaskStackLayoutAlgorithm layoutAlgorithm = mSv.getStackAlgorithm();
TaskStackViewScroller stackScroller = mSv.getScroller();
if (anchorTask != null) {
// Get the current set of task transforms
float prevAnchorTaskScroll = 0;
boolean pullStackForward = mCurrentTasks.size() > 0;
if (pullStackForward) {
- prevAnchorTaskScroll = mSv.getStackAlgorithm().getStackScrollForTask(anchorTask);
+ prevAnchorTaskScroll = layoutAlgorithm.getStackScrollForTask(anchorTask);
}
// Calculate where the views would be without the deleting tasks
newStackScroll = stackScroller.getBoundedStackScroll(newStackScroll);
} else if (pullStackForward) {
// Otherwise, offset the scroll by the movement of the anchor task
- float anchorTaskScroll = mSv.getStackAlgorithm().getStackScrollForTask(anchorTask);
+ float anchorTaskScroll = layoutAlgorithm.getStackScrollForTask(anchorTask);
float stackScrollOffset = (anchorTaskScroll - prevAnchorTaskScroll);
- if (mSv.getStackAlgorithm().getFocusState() !=
+ if (layoutAlgorithm.getFocusState() !=
TaskStackLayoutAlgorithm.STATE_FOCUSED) {
// If we are focused, we don't want the front task to move, but otherwise, we
// allow the back task to move up, and the front task to move back
mSv.bindVisibleTaskViews(newStackScroll);
// Get the final set of task transforms (with task removed)
- mSv.getLayoutTaskTransforms(newStackScroll, mCurrentTasks, mFinalTaskTransforms);
+ mSv.getLayoutTaskTransforms(newStackScroll, TaskStackLayoutAlgorithm.STATE_UNFOCUSED,
+ mCurrentTasks, mFinalTaskTransforms);
// Set the target to scroll towards upon dismissal
mTargetStackScroll = newStackScroll;
* Post condition: All views that will be visible as a part of the gesture are retrieved
* and at their initial positions. The stack is still at the current
* scroll, but the layout is updated without the task currently being
- * dismissed.
+ * dismissed. The final layout is in the unfocused stack state, which
+ * will be applied when the current task is dismissed.
*/
}
}
tv.setTouchEnabled(true);
// Update the scroll to the final scroll position from onBeginDrag()
mSv.getScroller().setStackScroll(mTargetStackScroll, null);
+ // Update the focus state to the final focus state
+ mSv.getStackAlgorithm().setFocusState(TaskStackLayoutAlgorithm.STATE_UNFOCUSED);
// Remove the task view from the stack
EventBus.getDefault().send(new TaskViewDismissedEvent(tv.getTask(), tv));
// Stop tracking this deletion animation
fromTransform.rect, toTransform.rect));
mTmpTransform.dimAlpha = fromTransform.dimAlpha + (toTransform.dimAlpha -
fromTransform.dimAlpha) * dismissFraction;
+ mTmpTransform.viewOutlineAlpha = fromTransform.viewOutlineAlpha +
+ (toTransform.viewOutlineAlpha - fromTransform.viewOutlineAlpha) *
+ dismissFraction;
mTmpTransform.translationZ = fromTransform.translationZ +
(toTransform.translationZ - fromTransform.translationZ) * dismissFraction;
* launching) needs to be animated independently of the task progress.
*/
public static final Property<TaskView, Float> DIM_ALPHA =
- new FloatProperty<TaskView>("dim") {
+ new FloatProperty<TaskView>("dimAlpha") {
@Override
public void setValue(TaskView tv, float dimAlpha) {
tv.setDimAlpha(dimAlpha);
}
};
+ /**
+ * The dim overlay is generally calculated from the task progress, but occasionally (like when
+ * launching) needs to be animated independently of the task progress.
+ */
+ public static final Property<TaskView, Float> VIEW_OUTLINE_ALPHA =
+ new FloatProperty<TaskView>("viewOutlineAlpha") {
+ @Override
+ public void setValue(TaskView tv, float alpha) {
+ tv.getViewBounds().setAlpha(alpha);
+ }
+
+ @Override
+ public Float get(TaskView tv) {
+ return tv.getViewBounds().getAlpha();
+ }
+ };
+
@ViewDebug.ExportedProperty(category="recents")
float mDimAlpha;
PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
RecentsConfiguration config = Recents.getConfiguration();
Resources res = context.getResources();
mViewBounds = new AnimateableViewBounds(this, res.getDimensionPixelSize(
- R.dimen.recents_task_view_rounded_corners_radius));
+ R.dimen.recents_task_view_shadow_rounded_corners_radius));
if (config.fakeShadows) {
setBackground(new FakeShadowDrawable(res, config));
}
if (Float.compare(getDimAlpha(), toTransform.dimAlpha) != 0) {
setDimAlpha(toTransform.dimAlpha);
}
+ if (Float.compare(mViewBounds.getAlpha(), toTransform.viewOutlineAlpha) != 0) {
+ mViewBounds.setAlpha(toTransform.viewOutlineAlpha);
+ }
// Manually call back to the animator listener and update callback
if (toAnimation.getListener() != null) {
toAnimation.getListener().onAnimationEnd(null);
toTransform.dimAlpha);
mTmpAnimators.add(toAnimation.apply(AnimationProps.BOUNDS, anim));
}
+ if (Float.compare(mViewBounds.getAlpha(), toTransform.viewOutlineAlpha) != 0) {
+ ObjectAnimator anim = ObjectAnimator.ofFloat(this, VIEW_OUTLINE_ALPHA,
+ mViewBounds.getAlpha(), toTransform.viewOutlineAlpha);
+ mTmpAnimators.add(toAnimation.apply(AnimationProps.BOUNDS, anim));
+ }
if (updateCallback != null) {
ValueAnimator updateCallbackAnim = ValueAnimator.ofInt(0, 1);
updateCallbackAnim.addUpdateListener(updateCallback);
int dimAlphaInt = (int) (dimAlpha * 255);
mDimAlpha = dimAlpha;
- mViewBounds.setAlpha(1f - (dimAlpha / TaskStackLayoutAlgorithm.DIM_MAX_VALUE));
if (config.useHardwareLayers) {
// Defer setting hardware layers if we have not yet measured, or there is no dim to draw
if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) {
public float scale = 1f;
public float alpha = 1f;
public float dimAlpha = 0f;
+ public float viewOutlineAlpha = 0f;
public boolean visible = false;
alpha = tv.getAlpha();
visible = true;
dimAlpha = tv.getDimAlpha();
+ viewOutlineAlpha = tv.getViewBounds().getAlpha();
rect.set(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom());
}
alpha = other.alpha;
visible = other.visible;
dimAlpha = other.dimAlpha;
+ viewOutlineAlpha = other.viewOutlineAlpha;
rect.set(other.rect);
}
scale = 1f;
alpha = 1f;
dimAlpha = 0f;
+ viewOutlineAlpha = 0f;
visible = false;
rect.setEmpty();
}