Merge "Implementing Recents focus states. (Bug 16950262)" into lmp-dev
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2014 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.
|
||||
-->
|
||||
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="?android:attr/colorControlHighlight">
|
||||
<item android:drawable="@android:color/transparent" />
|
||||
</ripple>
|
||||
@@ -26,8 +26,7 @@
|
||||
android:id="@+id/task_view_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/recents_task_bar_height"
|
||||
android:layout_gravity="top|center_horizontal"
|
||||
android:background="@color/recents_task_bar_default_background_color">
|
||||
android:layout_gravity="top|center_horizontal">
|
||||
<com.android.systemui.recents.views.FixedSizeImageView
|
||||
android:id="@+id/application_icon"
|
||||
android:layout_width="@dimen/recents_task_view_application_icon_size"
|
||||
@@ -69,7 +68,6 @@
|
||||
android:layout_gravity="bottom|right"
|
||||
android:layout_marginRight="15dp"
|
||||
android:layout_marginBottom="15dp"
|
||||
android:translationZ="50dp"
|
||||
android:contentDescription="@string/recents_lock_to_app_button_label"
|
||||
android:background="@drawable/recents_lock_to_task_button_bg">
|
||||
<ImageView
|
||||
|
||||
@@ -147,6 +147,8 @@
|
||||
<integer name="recents_max_task_stack_view_dim">96</integer>
|
||||
<!-- The number of tasks that RecentsTaskLoader should load. -->
|
||||
<integer name="recents_max_num_tasks_to_load">50</integer>
|
||||
<!-- The delay to enforce between each alt-tab key press. -->
|
||||
<integer name="recents_alt_tab_key_delay">200</integer>
|
||||
<!-- Transposes the recents layout in landscape. -->
|
||||
<bool name="recents_transpose_layout_with_orientation">true</bool>
|
||||
|
||||
|
||||
@@ -345,8 +345,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
|
||||
* Creates the activity options for an app->recents transition. If this method sets the static
|
||||
* screenshot, then we will use that for the transition.
|
||||
*/
|
||||
ActivityOptions getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo topTask) {
|
||||
|
||||
ActivityOptions getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo topTask,
|
||||
boolean isTopTaskHome) {
|
||||
if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
|
||||
// Recycle the last screenshot
|
||||
consumeLastScreenshot();
|
||||
@@ -365,7 +365,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
|
||||
Bitmap firstThumbnail = mSystemServicesProxy.getTaskThumbnail(topTask.id);
|
||||
if (firstThumbnail != null) {
|
||||
// Update the destination rect
|
||||
Rect toTaskRect = getThumbnailTransitionRect(topTask.id);
|
||||
Rect toTaskRect = getThumbnailTransitionRect(topTask.id, isTopTaskHome);
|
||||
if (toTaskRect.width() > 0 && toTaskRect.height() > 0) {
|
||||
// Create the new thumbnail for the animation down
|
||||
// XXX: We should find a way to optimize this so we don't need to create a new bitmap
|
||||
@@ -389,7 +389,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
|
||||
}
|
||||
|
||||
/** Returns the transition rect for the given task id. */
|
||||
Rect getThumbnailTransitionRect(int runningTaskId) {
|
||||
Rect getThumbnailTransitionRect(int runningTaskId, boolean isTopTaskHome) {
|
||||
// Get the stack of tasks that we are animating into
|
||||
TaskStack stack = RecentsTaskLoader.getShallowTaskStack(mSystemServicesProxy, -1);
|
||||
if (stack.getTaskCount() == 0) {
|
||||
@@ -401,7 +401,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
|
||||
TaskStackViewLayoutAlgorithm algo = tsv.getStackAlgorithm();
|
||||
Rect taskStackBounds = new Rect(mTaskStackBounds);
|
||||
taskStackBounds.bottom -= mSystemInsets.bottom;
|
||||
tsv.computeRects(mWindowRect.width(), mWindowRect.height(), taskStackBounds, mTriggeredFromAltTab);
|
||||
tsv.computeRects(mWindowRect.width(), mWindowRect.height(), taskStackBounds,
|
||||
mTriggeredFromAltTab, isTopTaskHome);
|
||||
tsv.getScroller().setStackScrollToInitialState();
|
||||
|
||||
// Find the running task in the TaskStack
|
||||
@@ -442,7 +443,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
|
||||
|
||||
if (useThumbnailTransition) {
|
||||
// Try starting with a thumbnail transition
|
||||
ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask);
|
||||
ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, isTopTaskHome);
|
||||
if (opts != null) {
|
||||
if (sLastScreenshot != null) {
|
||||
startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_APP_FULL_SCREENSHOT);
|
||||
|
||||
@@ -61,6 +61,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
|
||||
|
||||
RecentsConfiguration mConfig;
|
||||
boolean mVisible;
|
||||
long mLastTabKeyEventTime;
|
||||
|
||||
// Top level views
|
||||
RecentsView mRecentsView;
|
||||
@@ -512,17 +513,33 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_TAB) {
|
||||
// Focus the next task in the stack
|
||||
final boolean backward = event.isShiftPressed();
|
||||
mRecentsView.focusNextTask(!backward);
|
||||
return true;
|
||||
} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
|
||||
mRecentsView.focusNextTask(true);
|
||||
return true;
|
||||
} else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
|
||||
mRecentsView.focusNextTask(false);
|
||||
return true;
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_TAB: {
|
||||
boolean hasRepKeyTimeElapsed = (System.currentTimeMillis() -
|
||||
mLastTabKeyEventTime) > mConfig.altTabKeyDelay;
|
||||
if (event.getRepeatCount() <= 0 || hasRepKeyTimeElapsed) {
|
||||
// Focus the next task in the stack
|
||||
final boolean backward = event.isShiftPressed();
|
||||
mRecentsView.focusNextTask(!backward);
|
||||
mLastTabKeyEventTime = System.currentTimeMillis();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case KeyEvent.KEYCODE_DPAD_UP: {
|
||||
mRecentsView.focusNextTask(true);
|
||||
return true;
|
||||
}
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN: {
|
||||
mRecentsView.focusNextTask(false);
|
||||
return true;
|
||||
}
|
||||
case KeyEvent.KEYCODE_DEL:
|
||||
case KeyEvent.KEYCODE_FORWARD_DEL: {
|
||||
mRecentsView.dismissFocusedTask();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// Pass through the debug trigger
|
||||
mDebugTrigger.onKeyEvent(keyCode);
|
||||
|
||||
@@ -112,6 +112,9 @@ public class RecentsConfiguration {
|
||||
public boolean launchedFromHome;
|
||||
public int launchedToTaskId;
|
||||
|
||||
/** Misc **/
|
||||
public int altTabKeyDelay;
|
||||
|
||||
/** Dev options and global settings */
|
||||
public boolean lockToAppEnabled;
|
||||
public boolean developerOptionsEnabled;
|
||||
@@ -250,6 +253,9 @@ public class RecentsConfiguration {
|
||||
// Nav bar scrim
|
||||
navBarScrimEnterDuration =
|
||||
res.getInteger(R.integer.recents_nav_bar_scrim_enter_duration);
|
||||
|
||||
// Misc
|
||||
altTabKeyDelay = res.getInteger(R.integer.recents_alt_tab_key_delay);
|
||||
}
|
||||
|
||||
/** Updates the system insets */
|
||||
|
||||
@@ -89,6 +89,17 @@ public class Utilities {
|
||||
return Math.abs((fgL + 0.05f) / (bgL + 0.05f));
|
||||
}
|
||||
|
||||
/** Returns the base color overlaid with another overlay color with a specified alpha. */
|
||||
public static int getColorWithOverlay(int baseColor, int overlayColor, float overlayAlpha) {
|
||||
return Color.rgb(
|
||||
(int) (overlayAlpha * Color.red(baseColor) +
|
||||
(1f - overlayAlpha) * Color.red(overlayColor)),
|
||||
(int) (overlayAlpha * Color.green(baseColor) +
|
||||
(1f - overlayAlpha) * Color.green(overlayColor)),
|
||||
(int) (overlayAlpha * Color.blue(baseColor) +
|
||||
(1f - overlayAlpha) * Color.blue(overlayColor)));
|
||||
}
|
||||
|
||||
/** Sets some private shadow properties. */
|
||||
public static void setShadowProperty(String property, String value)
|
||||
throws IllegalAccessException, InvocationTargetException {
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.graphics.Color;
|
||||
import com.android.systemui.recents.Constants;
|
||||
import com.android.systemui.recents.RecentsConfiguration;
|
||||
import com.android.systemui.recents.misc.NamedCounter;
|
||||
import com.android.systemui.recents.misc.Utilities;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -433,10 +434,8 @@ public class TaskStack {
|
||||
float alpha = 1f;
|
||||
for (int j = 0; j < taskCount; j++) {
|
||||
Task t = tasksMap.get(group.mTaskKeys.get(j));
|
||||
t.colorPrimary = Color.rgb(
|
||||
(int) (alpha * Color.red(affiliationColor) + (1f - alpha) * 0xFF),
|
||||
(int) (alpha * Color.green(affiliationColor) + (1f - alpha) * 0xFF),
|
||||
(int) (alpha * Color.blue(affiliationColor) + (1f - alpha) * 0xFF));
|
||||
t.colorPrimary = Utilities.getColorWithOverlay(affiliationColor, Color.WHITE,
|
||||
alpha);
|
||||
alpha -= alphaStep;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,12 +316,11 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
|
||||
/** Notifies each task view of the user interaction. */
|
||||
public void onUserInteraction() {
|
||||
// Get the first stack view
|
||||
TaskStackView stackView = null;
|
||||
int childCount = getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
View child = getChildAt(i);
|
||||
if (child != mSearchBar) {
|
||||
stackView = (TaskStackView) child;
|
||||
TaskStackView stackView = (TaskStackView) child;
|
||||
stackView.onUserInteraction();
|
||||
}
|
||||
}
|
||||
@@ -330,18 +329,28 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
|
||||
/** Focuses the next task in the first stack view */
|
||||
public void focusNextTask(boolean forward) {
|
||||
// Get the first stack view
|
||||
TaskStackView stackView = null;
|
||||
int childCount = getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
View child = getChildAt(i);
|
||||
if (child != mSearchBar) {
|
||||
stackView = (TaskStackView) child;
|
||||
TaskStackView stackView = (TaskStackView) child;
|
||||
stackView.focusNextTask(forward);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stackView != null) {
|
||||
stackView.focusNextTask(forward);
|
||||
/** Dismisses the focused task. */
|
||||
public void dismissFocusedTask() {
|
||||
// Get the first stack view
|
||||
int childCount = getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
View child = getChildAt(i);
|
||||
if (child != mSearchBar) {
|
||||
TaskStackView stackView = (TaskStackView) child;
|
||||
stackView.dismissFocusedTask();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -349,9 +349,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
|
||||
}
|
||||
|
||||
/** Updates the min and max virtual scroll bounds */
|
||||
void updateMinMaxScroll(boolean boundScrollToNewMinMax, boolean launchedWithAltTab) {
|
||||
void updateMinMaxScroll(boolean boundScrollToNewMinMax, boolean launchedWithAltTab,
|
||||
boolean launchedFromHome) {
|
||||
// Compute the min and max scroll values
|
||||
mLayoutAlgorithm.computeMinMaxScroll(mStack.getTasks(), launchedWithAltTab);
|
||||
mLayoutAlgorithm.computeMinMaxScroll(mStack.getTasks(), launchedWithAltTab, launchedFromHome);
|
||||
|
||||
// Debug logging
|
||||
if (boundScrollToNewMinMax) {
|
||||
@@ -366,6 +367,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
|
||||
|
||||
/** Focuses the task at the specified index in the stack */
|
||||
void focusTask(int taskIndex, boolean scrollToNewPosition) {
|
||||
// Return early if the task is already focused
|
||||
if (taskIndex == mFocusedTaskIndex) return;
|
||||
|
||||
if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) {
|
||||
mFocusedTaskIndex = taskIndex;
|
||||
|
||||
@@ -406,14 +410,24 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
|
||||
void focusNextTask(boolean forward) {
|
||||
// Find the next index to focus
|
||||
int numTasks = mStack.getTaskCount();
|
||||
if (mFocusedTaskIndex < 0) {
|
||||
mFocusedTaskIndex = numTasks - 1;
|
||||
}
|
||||
if (numTasks == 0) return;
|
||||
|
||||
int nextFocusIndex = numTasks - 1;
|
||||
if (0 <= mFocusedTaskIndex && mFocusedTaskIndex < numTasks) {
|
||||
mFocusedTaskIndex = Math.max(0, Math.min(numTasks - 1,
|
||||
nextFocusIndex = Math.max(0, Math.min(numTasks - 1,
|
||||
mFocusedTaskIndex + (forward ? -1 : 1)));
|
||||
}
|
||||
focusTask(mFocusedTaskIndex, true);
|
||||
focusTask(nextFocusIndex, true);
|
||||
}
|
||||
|
||||
/** Dismisses the focused task. */
|
||||
public void dismissFocusedTask() {
|
||||
// Return early if there is no focused task index
|
||||
if (mFocusedTaskIndex < 0) return;
|
||||
|
||||
Task t = mStack.getTasks().get(mFocusedTaskIndex);
|
||||
TaskView tv = getChildViewForTask(t);
|
||||
tv.dismissTask();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -436,12 +450,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
|
||||
|
||||
/** Computes the stack and task rects */
|
||||
public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds,
|
||||
boolean launchedWithAltTab) {
|
||||
boolean launchedWithAltTab, boolean launchedFromHome) {
|
||||
// Compute the rects in the stack algorithm
|
||||
mLayoutAlgorithm.computeRects(windowWidth, windowHeight, taskStackBounds);
|
||||
|
||||
// Update the scroll bounds
|
||||
updateMinMaxScroll(false, launchedWithAltTab);
|
||||
updateMinMaxScroll(false, launchedWithAltTab, launchedFromHome);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -456,7 +470,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
|
||||
// Compute our stack/task rects
|
||||
Rect taskStackBounds = new Rect(mTaskStackBounds);
|
||||
taskStackBounds.bottom -= mConfig.systemInsets.bottom;
|
||||
computeRects(width, height, taskStackBounds, mConfig.launchedWithAltTab);
|
||||
computeRects(width, height, taskStackBounds, mConfig.launchedWithAltTab,
|
||||
mConfig.launchedFromHome);
|
||||
|
||||
// If this is the first layout, then scroll to the front of the stack and synchronize the
|
||||
// stack views immediately to load all the views
|
||||
@@ -546,7 +561,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
|
||||
|
||||
// When Alt-Tabbing, we scroll to and focus the previous task
|
||||
if (mConfig.launchedWithAltTab) {
|
||||
focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
|
||||
if (mConfig.launchedFromHome) {
|
||||
focusTask(Math.max(0, mStack.getTaskCount() - 1), false);
|
||||
} else {
|
||||
focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -661,7 +680,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
|
||||
mCb.onTaskViewDismissed(removedTask);
|
||||
|
||||
// Update the min/max scroll and animate other task views into their new positions
|
||||
updateMinMaxScroll(true, mConfig.launchedWithAltTab);
|
||||
updateMinMaxScroll(true, mConfig.launchedWithAltTab, mConfig.launchedFromHome);
|
||||
requestSynchronizeStackViewsWithModel(200);
|
||||
|
||||
// Update the new front most task
|
||||
@@ -859,11 +878,23 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
|
||||
@Override
|
||||
public void onTaskViewDismissed(TaskView tv) {
|
||||
Task task = tv.getTask();
|
||||
int taskIndex = mStack.indexOfTask(task);
|
||||
boolean taskWasFocused = tv.isFocusedTask();
|
||||
// Announce for accessibility
|
||||
tv.announceForAccessibility(getContext().getString(R.string.accessibility_recents_item_dismissed,
|
||||
tv.getTask().activityLabel));
|
||||
// Remove the task from the view
|
||||
mStack.removeTask(task);
|
||||
// If the dismissed task was focused, then we should focus the next task in front
|
||||
if (taskWasFocused) {
|
||||
ArrayList<Task> tasks = mStack.getTasks();
|
||||
int nextTaskIndex = Math.min(tasks.size() - 1, taskIndex);
|
||||
if (nextTaskIndex >= 0) {
|
||||
Task nextTask = tasks.get(nextTaskIndex);
|
||||
TaskView nextTv = getChildViewForTask(nextTask);
|
||||
nextTv.setFocusedTask();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -876,6 +907,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
|
||||
requestSynchronizeStackViewsWithModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskViewFocusChanged(TaskView tv, boolean focused) {
|
||||
if (focused) {
|
||||
mFocusedTaskIndex = mStack.indexOfTask(tv.getTask());
|
||||
}
|
||||
}
|
||||
|
||||
/**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/
|
||||
|
||||
@Override
|
||||
|
||||
@@ -91,7 +91,8 @@ public class TaskStackViewLayoutAlgorithm {
|
||||
|
||||
/** Computes the minimum and maximum scroll progress values. This method may be called before
|
||||
* the RecentsConfiguration is set, so we need to pass in the alt-tab state. */
|
||||
void computeMinMaxScroll(ArrayList<Task> tasks, boolean launchedWithAltTab) {
|
||||
void computeMinMaxScroll(ArrayList<Task> tasks, boolean launchedWithAltTab,
|
||||
boolean launchedFromHome) {
|
||||
// Clear the progress map
|
||||
mTaskProgressMap.clear();
|
||||
|
||||
@@ -101,10 +102,16 @@ public class TaskStackViewLayoutAlgorithm {
|
||||
return;
|
||||
}
|
||||
|
||||
// Note that we should account for the scale difference of the offsets at the screen bottom
|
||||
int taskHeight = mTaskRect.height();
|
||||
float pAtBottomOfStackRect = screenYToCurveProgress(mStackVisibleRect.bottom);
|
||||
float pWithinAffiliateOffset = pAtBottomOfStackRect -
|
||||
screenYToCurveProgress(mStackVisibleRect.bottom - mWithinAffiliationOffset);
|
||||
float pWithinAffiliateTop = screenYToCurveProgress(mStackVisibleRect.bottom -
|
||||
mWithinAffiliationOffset);
|
||||
float scale = curveProgressToScale(pWithinAffiliateTop);
|
||||
int scaleYOffset = (int) (((1f - scale) * taskHeight) / 2);
|
||||
pWithinAffiliateTop = screenYToCurveProgress(mStackVisibleRect.bottom -
|
||||
mWithinAffiliationOffset + scaleYOffset);
|
||||
float pWithinAffiliateOffset = pAtBottomOfStackRect - pWithinAffiliateTop;
|
||||
float pBetweenAffiliateOffset = pAtBottomOfStackRect -
|
||||
screenYToCurveProgress(mStackVisibleRect.bottom - mBetweenAffiliationOffset);
|
||||
float pTaskHeightOffset = pAtBottomOfStackRect -
|
||||
@@ -133,8 +140,13 @@ public class TaskStackViewLayoutAlgorithm {
|
||||
mMaxScrollP = pAtFrontMostCardTop - ((1f - pTaskHeightOffset - pNavBarOffset));
|
||||
mMinScrollP = tasks.size() == 1 ? Math.max(mMaxScrollP, 0f) : 0f;
|
||||
if (launchedWithAltTab) {
|
||||
// Center the second most task, since that will be focused first
|
||||
mInitialScrollP = pAtSecondFrontMostCardTop - 0.5f;
|
||||
if (launchedFromHome) {
|
||||
// Center the top most task, since that will be focused first
|
||||
mInitialScrollP = pAtSecondFrontMostCardTop - 0.5f;
|
||||
} else {
|
||||
// Center the second top most task, since that will be focused first
|
||||
mInitialScrollP = pAtSecondFrontMostCardTop - 0.5f;
|
||||
}
|
||||
} else {
|
||||
mInitialScrollP = pAtFrontMostCardTop - 0.825f;
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ public class TaskStackViewScroller {
|
||||
stopBoundScrollAnimation();
|
||||
|
||||
mScrollAnimator = ObjectAnimator.ofFloat(this, "stackScroll", curScroll, newScroll);
|
||||
mScrollAnimator.setDuration(250);
|
||||
mScrollAnimator.setDuration(200);
|
||||
// We would have to project the difference into the screen coords, and then use that as the
|
||||
// duration
|
||||
// mScrollAnimator.setDuration(Utilities.calculateTranslationAnimationDuration(newScroll -
|
||||
|
||||
@@ -48,6 +48,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
public void onTaskViewDismissed(TaskView tv);
|
||||
public void onTaskViewClipStateChanged(TaskView tv);
|
||||
public void onTaskViewFullScreenTransitionCompleted();
|
||||
public void onTaskViewFocusChanged(TaskView tv, boolean focused);
|
||||
}
|
||||
|
||||
RecentsConfiguration mConfig;
|
||||
@@ -62,13 +63,14 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
Task mTask;
|
||||
boolean mTaskDataLoaded;
|
||||
boolean mIsFocused;
|
||||
boolean mFocusAnimationsEnabled;
|
||||
boolean mIsFullScreenView;
|
||||
boolean mClipViewInStack;
|
||||
AnimateableViewBounds mViewBounds;
|
||||
Paint mLayerPaint = new Paint();
|
||||
|
||||
TaskViewThumbnail mThumbnailView;
|
||||
TaskViewHeader mBarView;
|
||||
TaskViewHeader mHeaderView;
|
||||
TaskViewFooter mFooterView;
|
||||
View mActionButtonView;
|
||||
TaskViewCallbacks mCb;
|
||||
@@ -124,7 +126,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
// Bind the views
|
||||
mBarView = (TaskViewHeader) findViewById(R.id.task_view_bar);
|
||||
mHeaderView = (TaskViewHeader) findViewById(R.id.task_view_bar);
|
||||
mThumbnailView = (TaskViewThumbnail) findViewById(R.id.task_view_thumbnail);
|
||||
mActionButtonView = findViewById(R.id.lock_to_app_fab);
|
||||
if (mFooterView != null) {
|
||||
@@ -138,7 +140,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
int height = MeasureSpec.getSize(heightMeasureSpec);
|
||||
|
||||
// Measure the bar view, thumbnail, and footer
|
||||
mBarView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||
mHeaderView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY));
|
||||
if (mFooterView != null) {
|
||||
mFooterView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||
@@ -218,11 +220,11 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
|
||||
/** Prepares this task view for the enter-recents animations. This is called earlier in the
|
||||
* first layout because the actual animation into recents may take a long time. */
|
||||
public void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask,
|
||||
void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask,
|
||||
boolean occludesLaunchTarget, int offscreenY) {
|
||||
if (mConfig.launchedFromAppWithScreenshot) {
|
||||
if (isTaskViewLaunchTargetTask) {
|
||||
mBarView.prepareEnterRecentsAnimation();
|
||||
mHeaderView.prepareEnterRecentsAnimation();
|
||||
// Hide the footer during the transition in, and animate it out afterwards?
|
||||
if (mFooterView != null) {
|
||||
mFooterView.animateFooterVisibility(false, 0);
|
||||
@@ -234,7 +236,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
} else if (mConfig.launchedFromAppWithThumbnail) {
|
||||
if (isTaskViewLaunchTargetTask) {
|
||||
// Hide the front most task bar view so we can animate it in
|
||||
mBarView.prepareEnterRecentsAnimation();
|
||||
mHeaderView.prepareEnterRecentsAnimation();
|
||||
// Hide the action button if it exists
|
||||
mActionButtonView.setAlpha(0f);
|
||||
// Set the dim to 0 so we can animate it in
|
||||
@@ -256,8 +258,9 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
}
|
||||
|
||||
/** Animates this task view as it enters recents */
|
||||
public void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) {
|
||||
void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) {
|
||||
final TaskViewTransform transform = ctx.currentTaskTransform;
|
||||
int startDelay = 0;
|
||||
|
||||
if (mConfig.launchedFromAppWithScreenshot) {
|
||||
if (mTask.isLaunchTarget) {
|
||||
@@ -269,6 +272,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
float scaledWindowInsetTop = (int) (taskScale * windowInsetTop);
|
||||
float scaledTranslationY = taskRect.top + transform.translationY -
|
||||
(scaledWindowInsetTop + scaledYOffset);
|
||||
startDelay = mConfig.taskViewEnterFromHomeDelay;
|
||||
|
||||
// Animate the top clip
|
||||
mViewBounds.animateClipTop(windowInsetTop, duration,
|
||||
@@ -276,7 +280,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animation) {
|
||||
int y = (Integer) animation.getAnimatedValue();
|
||||
mBarView.setTranslationY(y);
|
||||
mHeaderView.setTranslationY(y);
|
||||
}
|
||||
});
|
||||
// Animate the bottom or right clip
|
||||
@@ -287,7 +291,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
mViewBounds.animateClipBottom(getMeasuredHeight() - (windowInsetTop + size), duration);
|
||||
}
|
||||
// Animate the task bar of the first task view
|
||||
mBarView.startEnterRecentsAnimation(0, null);
|
||||
mHeaderView.startEnterRecentsAnimation(0, null);
|
||||
animate()
|
||||
.scaleX(taskScale)
|
||||
.scaleY(taskScale)
|
||||
@@ -304,9 +308,9 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
mViewBounds.setClipBottom(0);
|
||||
mViewBounds.setClipRight(0);
|
||||
// Reset the bar translation
|
||||
mBarView.setTranslationY(0);
|
||||
mHeaderView.setTranslationY(0);
|
||||
// Enable the thumbnail clip
|
||||
mThumbnailView.enableTaskBarClip(mBarView);
|
||||
mThumbnailView.enableTaskBarClip(mHeaderView);
|
||||
// Animate the footer into view (if it is the front most task)
|
||||
animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration);
|
||||
|
||||
@@ -324,7 +328,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
.start();
|
||||
} else {
|
||||
// Otherwise, just enable the thumbnail clip
|
||||
mThumbnailView.enableTaskBarClip(mBarView);
|
||||
mThumbnailView.enableTaskBarClip(mHeaderView);
|
||||
|
||||
// Animate the footer into view
|
||||
animateFooterVisibility(true, 0);
|
||||
@@ -334,8 +338,8 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
} else if (mConfig.launchedFromAppWithThumbnail) {
|
||||
if (mTask.isLaunchTarget) {
|
||||
// Animate the task bar of the first task view
|
||||
mBarView.startEnterRecentsAnimation(mConfig.taskBarEnterAnimDelay,
|
||||
mThumbnailView.enableTaskBarClipAsRunnable(mBarView));
|
||||
mHeaderView.startEnterRecentsAnimation(mConfig.taskBarEnterAnimDelay,
|
||||
mThumbnailView.enableTaskBarClipAsRunnable(mHeaderView));
|
||||
|
||||
// Animate the dim into view as well
|
||||
ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", getDimFromTaskProgress());
|
||||
@@ -364,7 +368,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
.start();
|
||||
} else {
|
||||
// Enable the task bar clip
|
||||
mThumbnailView.enableTaskBarClip(mBarView);
|
||||
mThumbnailView.enableTaskBarClip(mHeaderView);
|
||||
// Animate the task up if it was occluding the launch target
|
||||
if (ctx.currentTaskOccludesLaunchTarget) {
|
||||
setTranslationY(transform.translationY + mConfig.taskViewAffiliateGroupEnterOffsetPx);
|
||||
@@ -378,7 +382,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
.withEndAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mThumbnailView.enableTaskBarClip(mBarView);
|
||||
mThumbnailView.enableTaskBarClip(mHeaderView);
|
||||
// Decrement the post animation trigger
|
||||
ctx.postAnimationTrigger.decrement();
|
||||
}
|
||||
@@ -387,6 +391,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
ctx.postAnimationTrigger.increment();
|
||||
}
|
||||
}
|
||||
startDelay = mConfig.taskBarEnterAnimDelay;
|
||||
|
||||
} else if (mConfig.launchedFromHome) {
|
||||
// Animate the tasks up
|
||||
@@ -407,7 +412,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
.withEndAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mThumbnailView.enableTaskBarClip(mBarView);
|
||||
mThumbnailView.enableTaskBarClip(mHeaderView);
|
||||
// Decrement the post animation trigger
|
||||
ctx.postAnimationTrigger.decrement();
|
||||
}
|
||||
@@ -417,18 +422,28 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
|
||||
// Animate the footer into view
|
||||
animateFooterVisibility(true, mConfig.taskViewEnterFromHomeDuration);
|
||||
startDelay = delay;
|
||||
|
||||
} else {
|
||||
// Otherwise, just enable the thumbnail clip
|
||||
mThumbnailView.enableTaskBarClip(mBarView);
|
||||
mThumbnailView.enableTaskBarClip(mHeaderView);
|
||||
|
||||
// Animate the footer into view
|
||||
animateFooterVisibility(true, 0);
|
||||
}
|
||||
|
||||
// Enable the focus animations from this point onwards so that they aren't affected by the
|
||||
// window transitions
|
||||
postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
enableFocusAnimations();
|
||||
}
|
||||
}, (startDelay / 2));
|
||||
}
|
||||
|
||||
/** Animates this task view as it leaves recents by pressing home. */
|
||||
public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
|
||||
void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
|
||||
animate()
|
||||
.translationY(ctx.offscreenTranslationY)
|
||||
.setStartDelay(0)
|
||||
@@ -441,11 +456,11 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
}
|
||||
|
||||
/** Animates this task view as it exits recents */
|
||||
public void startLaunchTaskAnimation(final Runnable r, boolean isLaunchingTask,
|
||||
boolean occludesLaunchTarget) {
|
||||
void startLaunchTaskAnimation(final Runnable r, boolean isLaunchingTask,
|
||||
boolean occludesLaunchTarget) {
|
||||
if (isLaunchingTask) {
|
||||
// Disable the thumbnail clip and animate the bar out
|
||||
mBarView.startLaunchTaskAnimation(mThumbnailView.disableTaskBarClipAsRunnable(), r);
|
||||
mHeaderView.startLaunchTaskAnimation(mThumbnailView.disableTaskBarClipAsRunnable(), r);
|
||||
|
||||
// Animate the dim
|
||||
if (mDim > 0) {
|
||||
@@ -464,7 +479,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
.start();
|
||||
} else {
|
||||
// Hide the dismiss button
|
||||
mBarView.startLaunchTaskDismissAnimation();
|
||||
mHeaderView.startLaunchTaskDismissAnimation();
|
||||
// If this is another view in the task grouping and is in front of the launch task,
|
||||
// animate it away first
|
||||
if (occludesLaunchTarget) {
|
||||
@@ -480,7 +495,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
}
|
||||
|
||||
/** Animates the deletion of this task view */
|
||||
public void startDeleteTaskAnimation(final Runnable r) {
|
||||
void startDeleteTaskAnimation(final Runnable r) {
|
||||
// Disabling clipping with the stack while the view is animating away
|
||||
setClipViewInStack(false);
|
||||
|
||||
@@ -508,19 +523,33 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
}
|
||||
|
||||
/** Animates this task view if the user does not interact with the stack after a certain time. */
|
||||
public void startNoUserInteractionAnimation() {
|
||||
mBarView.startNoUserInteractionAnimation();
|
||||
void startNoUserInteractionAnimation() {
|
||||
mHeaderView.startNoUserInteractionAnimation();
|
||||
}
|
||||
|
||||
/** Mark this task view that the user does has not interacted with the stack after a certain time. */
|
||||
public void setNoUserInteractionState() {
|
||||
mBarView.setNoUserInteractionState();
|
||||
void setNoUserInteractionState() {
|
||||
mHeaderView.setNoUserInteractionState();
|
||||
}
|
||||
|
||||
/** Dismisses this task. */
|
||||
void dismissTask() {
|
||||
// Animate out the view and call the callback
|
||||
final TaskView tv = this;
|
||||
startDeleteTaskAnimation(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mCb.onTaskViewDismissed(tv);
|
||||
}
|
||||
});
|
||||
// Hide the footer
|
||||
animateFooterVisibility(false, mConfig.taskViewRemoveAnimDuration);
|
||||
}
|
||||
|
||||
/** Sets whether this task view is full screen or not. */
|
||||
void setIsFullScreen(boolean isFullscreen) {
|
||||
mIsFullScreenView = isFullscreen;
|
||||
mBarView.setIsFullscreen(isFullscreen);
|
||||
mHeaderView.setIsFullscreen(isFullscreen);
|
||||
if (isFullscreen) {
|
||||
// If we are full screen, then disable the bottom outline clip for the footer
|
||||
mViewBounds.setOutlineClipBottom(0);
|
||||
@@ -614,6 +643,12 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
*/
|
||||
public void setFocusedTask() {
|
||||
mIsFocused = true;
|
||||
if (mFocusAnimationsEnabled) {
|
||||
// Focus the header bar
|
||||
mHeaderView.onTaskViewFocusChanged(true);
|
||||
}
|
||||
// Call the callback
|
||||
mCb.onTaskViewFocusChanged(this, true);
|
||||
// Workaround, we don't always want it focusable in touch mode, but we want the first task
|
||||
// to be focused after the enter-recents animation, which can be triggered from either touch
|
||||
// or keyboard
|
||||
@@ -631,6 +666,12 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
|
||||
if (!gainFocus) {
|
||||
mIsFocused = false;
|
||||
if (mFocusAnimationsEnabled) {
|
||||
// Un-focus the header bar
|
||||
mHeaderView.onTaskViewFocusChanged(false);
|
||||
}
|
||||
// Call the callback
|
||||
mCb.onTaskViewFocusChanged(this, false);
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
@@ -642,6 +683,16 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
return mIsFocused || isFocused();
|
||||
}
|
||||
|
||||
/** Enables all focus animations. */
|
||||
void enableFocusAnimations() {
|
||||
boolean wasFocusAnimationsEnabled = mFocusAnimationsEnabled;
|
||||
mFocusAnimationsEnabled = true;
|
||||
if (mIsFocused && !wasFocusAnimationsEnabled) {
|
||||
// Re-notify the header if we were focused and animations were not previously enabled
|
||||
mHeaderView.onTaskViewFocusChanged(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**** TaskCallbacks Implementation ****/
|
||||
|
||||
/** Binds this task view to the task */
|
||||
@@ -662,24 +713,24 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
|
||||
@Override
|
||||
public void onTaskDataLoaded() {
|
||||
if (mThumbnailView != null && mBarView != null) {
|
||||
if (mThumbnailView != null && mHeaderView != null) {
|
||||
// Bind each of the views to the new task data
|
||||
if (mIsFullScreenView) {
|
||||
mThumbnailView.bindToScreenshot(AlternateRecentsComponent.getLastScreenshot());
|
||||
} else {
|
||||
mThumbnailView.rebindToTask(mTask);
|
||||
}
|
||||
mBarView.rebindToTask(mTask);
|
||||
mHeaderView.rebindToTask(mTask);
|
||||
// Rebind any listeners
|
||||
mBarView.mApplicationIcon.setOnClickListener(this);
|
||||
mBarView.mDismissButton.setOnClickListener(this);
|
||||
mHeaderView.mApplicationIcon.setOnClickListener(this);
|
||||
mHeaderView.mDismissButton.setOnClickListener(this);
|
||||
if (mFooterView != null) {
|
||||
mFooterView.setOnClickListener(this);
|
||||
}
|
||||
mActionButtonView.setOnClickListener(this);
|
||||
if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
|
||||
if (mConfig.developerOptionsEnabled) {
|
||||
mBarView.mApplicationIcon.setOnLongClickListener(this);
|
||||
mHeaderView.mApplicationIcon.setOnLongClickListener(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -688,20 +739,20 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
|
||||
@Override
|
||||
public void onTaskDataUnloaded() {
|
||||
if (mThumbnailView != null && mBarView != null) {
|
||||
if (mThumbnailView != null && mHeaderView != null) {
|
||||
// Unbind each of the views from the task data and remove the task callback
|
||||
mTask.setCallbacks(null);
|
||||
mThumbnailView.unbindFromTask();
|
||||
mBarView.unbindFromTask();
|
||||
mHeaderView.unbindFromTask();
|
||||
// Unbind any listeners
|
||||
mBarView.mApplicationIcon.setOnClickListener(null);
|
||||
mBarView.mDismissButton.setOnClickListener(null);
|
||||
mHeaderView.mApplicationIcon.setOnClickListener(null);
|
||||
mHeaderView.mDismissButton.setOnClickListener(null);
|
||||
if (mFooterView != null) {
|
||||
mFooterView.setOnClickListener(null);
|
||||
}
|
||||
mActionButtonView.setOnClickListener(null);
|
||||
if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
|
||||
mBarView.mApplicationIcon.setOnLongClickListener(null);
|
||||
mHeaderView.mApplicationIcon.setOnLongClickListener(null);
|
||||
}
|
||||
}
|
||||
mTaskDataLoaded = false;
|
||||
@@ -734,18 +785,10 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (Constants.DebugFlags.App.EnableTaskFiltering && v == mBarView.mApplicationIcon) {
|
||||
if (Constants.DebugFlags.App.EnableTaskFiltering && v == mHeaderView.mApplicationIcon) {
|
||||
mCb.onTaskViewAppIconClicked(tv);
|
||||
} else if (v == mBarView.mDismissButton) {
|
||||
// Animate out the view and call the callback
|
||||
startDeleteTaskAnimation(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mCb.onTaskViewDismissed(tv);
|
||||
}
|
||||
});
|
||||
// Hide the footer
|
||||
tv.animateFooterVisibility(false, mConfig.taskViewRemoveAnimDuration);
|
||||
} else if (v == mHeaderView.mDismissButton) {
|
||||
dismissTask();
|
||||
} else {
|
||||
mCb.onTaskViewClicked(tv, tv.getTask(),
|
||||
(v == mFooterView || v == mActionButtonView));
|
||||
@@ -758,7 +801,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
if (v == mBarView.mApplicationIcon) {
|
||||
if (v == mHeaderView.mApplicationIcon) {
|
||||
mCb.onTaskViewAppInfoClicked(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -16,9 +16,16 @@
|
||||
|
||||
package com.android.systemui.recents.views;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
@@ -28,12 +35,14 @@ import android.graphics.drawable.RippleDrawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.recents.Constants;
|
||||
import com.android.systemui.recents.RecentsConfiguration;
|
||||
import com.android.systemui.recents.misc.Utilities;
|
||||
import com.android.systemui.recents.model.Task;
|
||||
|
||||
|
||||
@@ -46,10 +55,15 @@ class TaskViewHeader extends FrameLayout {
|
||||
ImageView mApplicationIcon;
|
||||
TextView mActivityDescription;
|
||||
|
||||
RippleDrawable mBackground;
|
||||
ColorDrawable mBackgroundColor;
|
||||
Drawable mLightDismissDrawable;
|
||||
Drawable mDarkDismissDrawable;
|
||||
ValueAnimator mBackgroundColorAnimator;
|
||||
|
||||
boolean mIsFullscreen;
|
||||
boolean mCurrentPrimaryColorIsDark;
|
||||
int mCurrentPrimaryColor;
|
||||
|
||||
static Paint sHighlightPaint;
|
||||
|
||||
@@ -69,6 +83,13 @@ class TaskViewHeader extends FrameLayout {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
mConfig = RecentsConfiguration.getInstance();
|
||||
setWillNotDraw(false);
|
||||
setClipToOutline(true);
|
||||
setOutlineProvider(new ViewOutlineProvider() {
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline) {
|
||||
outline.setRect(0, 0, getMeasuredWidth(), getMeasuredHeight());
|
||||
}
|
||||
});
|
||||
|
||||
// Load the dismiss resources
|
||||
Resources res = context.getResources();
|
||||
@@ -107,6 +128,15 @@ class TaskViewHeader extends FrameLayout {
|
||||
mApplicationIcon.setBackground(null);
|
||||
}
|
||||
}
|
||||
|
||||
mBackgroundColor = new ColorDrawable(0);
|
||||
// Copy the ripple drawable since we are going to be manipulating it
|
||||
mBackground = (RippleDrawable)
|
||||
getResources().getDrawable(R.drawable.recents_task_view_header_bg);
|
||||
mBackground = (RippleDrawable) mBackground.mutate().getConstantState().newDrawable();
|
||||
mBackground.setColor(ColorStateList.valueOf(0));
|
||||
mBackground.setDrawableByLayerId(mBackground.getId(0), mBackgroundColor);
|
||||
setBackground(mBackground);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -130,6 +160,12 @@ class TaskViewHeader extends FrameLayout {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns the secondary color for a primary color. */
|
||||
int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) {
|
||||
int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK;
|
||||
return Utilities.getColorWithOverlay(primaryColor, overlayColor, 0.8f);
|
||||
}
|
||||
|
||||
/** Binds the bar view to the task */
|
||||
void rebindToTask(Task t) {
|
||||
// If an activity icon is defined, then we use that as the primary icon to show in the bar,
|
||||
@@ -147,8 +183,10 @@ class TaskViewHeader extends FrameLayout {
|
||||
int existingBgColor = (getBackground() instanceof ColorDrawable) ?
|
||||
((ColorDrawable) getBackground()).getColor() : 0;
|
||||
if (existingBgColor != t.colorPrimary) {
|
||||
setBackgroundColor(t.colorPrimary);
|
||||
mBackgroundColor.setColor(t.colorPrimary);
|
||||
}
|
||||
mCurrentPrimaryColor = t.colorPrimary;
|
||||
mCurrentPrimaryColorIsDark = t.useLightOnPrimaryColor;
|
||||
mActivityDescription.setTextColor(t.useLightOnPrimaryColor ?
|
||||
mConfig.taskBarViewLightTextColor : mConfig.taskBarViewDarkTextColor);
|
||||
mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
|
||||
@@ -165,12 +203,12 @@ class TaskViewHeader extends FrameLayout {
|
||||
|
||||
/** Prepares this task view for the enter-recents animations. This is called earlier in the
|
||||
* first layout because the actual animation into recents may take a long time. */
|
||||
public void prepareEnterRecentsAnimation() {
|
||||
void prepareEnterRecentsAnimation() {
|
||||
setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
/** Animates this task bar as it enters recents */
|
||||
public void startEnterRecentsAnimation(int delay, Runnable postAnimRunnable) {
|
||||
void startEnterRecentsAnimation(int delay, Runnable postAnimRunnable) {
|
||||
// Animate the task bar of the first task view
|
||||
setVisibility(View.VISIBLE);
|
||||
setTranslationY(-getMeasuredHeight());
|
||||
@@ -184,7 +222,7 @@ class TaskViewHeader extends FrameLayout {
|
||||
}
|
||||
|
||||
/** Animates this task bar as it exits recents */
|
||||
public void startLaunchTaskAnimation(Runnable preAnimRunnable, final Runnable postAnimRunnable) {
|
||||
void startLaunchTaskAnimation(Runnable preAnimRunnable, final Runnable postAnimRunnable) {
|
||||
// Animate the task bar out of the first task view
|
||||
animate()
|
||||
.translationY(-getMeasuredHeight())
|
||||
@@ -202,7 +240,7 @@ class TaskViewHeader extends FrameLayout {
|
||||
}
|
||||
|
||||
/** Animates this task bar dismiss button when launching a task. */
|
||||
public void startLaunchTaskDismissAnimation() {
|
||||
void startLaunchTaskDismissAnimation() {
|
||||
if (mDismissButton.getVisibility() == View.VISIBLE) {
|
||||
mDismissButton.animate().cancel();
|
||||
mDismissButton.animate()
|
||||
@@ -216,7 +254,7 @@ class TaskViewHeader extends FrameLayout {
|
||||
}
|
||||
|
||||
/** Animates this task bar if the user does not interact with the stack after a certain time. */
|
||||
public void startNoUserInteractionAnimation() {
|
||||
void startNoUserInteractionAnimation() {
|
||||
mDismissButton.setVisibility(View.VISIBLE);
|
||||
mDismissButton.setAlpha(0f);
|
||||
mDismissButton.animate()
|
||||
@@ -229,11 +267,78 @@ class TaskViewHeader extends FrameLayout {
|
||||
}
|
||||
|
||||
/** Mark this task view that the user does has not interacted with the stack after a certain time. */
|
||||
public void setNoUserInteractionState() {
|
||||
void setNoUserInteractionState() {
|
||||
if (mDismissButton.getVisibility() != View.VISIBLE) {
|
||||
mDismissButton.animate().cancel();
|
||||
mDismissButton.setVisibility(View.VISIBLE);
|
||||
mDismissButton.setAlpha(1f);
|
||||
}
|
||||
}
|
||||
|
||||
/** Notifies the associated TaskView has been focused. */
|
||||
void onTaskViewFocusChanged(boolean focused) {
|
||||
boolean isRunning = false;
|
||||
if (mBackgroundColorAnimator != null) {
|
||||
isRunning = mBackgroundColorAnimator.isRunning();
|
||||
mBackgroundColorAnimator.removeAllUpdateListeners();
|
||||
mBackgroundColorAnimator.cancel();
|
||||
}
|
||||
if (focused) {
|
||||
int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
|
||||
int[][] states = new int[][] {
|
||||
new int[] { android.R.attr.state_enabled },
|
||||
new int[] { android.R.attr.state_pressed }
|
||||
};
|
||||
int[] newStates = new int[]{
|
||||
android.R.attr.state_enabled,
|
||||
android.R.attr.state_pressed
|
||||
};
|
||||
int[] colors = new int[] {
|
||||
secondaryColor,
|
||||
secondaryColor
|
||||
};
|
||||
mBackground.setColor(new ColorStateList(states, colors));
|
||||
mBackground.setState(newStates);
|
||||
// Pulse the background color
|
||||
int currentColor = mBackgroundColor.getColor();
|
||||
int lightPrimaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
|
||||
mBackgroundColorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), lightPrimaryColor,
|
||||
currentColor);
|
||||
mBackgroundColorAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
mBackground.setState(new int[] {});
|
||||
}
|
||||
});
|
||||
mBackgroundColorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animation) {
|
||||
mBackgroundColor.setColor((Integer) animation.getAnimatedValue());
|
||||
}
|
||||
});
|
||||
mBackgroundColorAnimator.setRepeatCount(ValueAnimator.INFINITE);
|
||||
mBackgroundColorAnimator.setRepeatMode(ValueAnimator.REVERSE);
|
||||
mBackgroundColorAnimator.setStartDelay(750);
|
||||
mBackgroundColorAnimator.setDuration(750);
|
||||
mBackgroundColorAnimator.start();
|
||||
} else {
|
||||
if (isRunning) {
|
||||
// Restore the background color
|
||||
int currentColor = mBackgroundColor.getColor();
|
||||
mBackgroundColorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), currentColor,
|
||||
mCurrentPrimaryColor);
|
||||
mBackgroundColorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animation) {
|
||||
mBackgroundColor.setColor((Integer) animation.getAnimatedValue());
|
||||
}
|
||||
});
|
||||
mBackgroundColorAnimator.setRepeatCount(0);
|
||||
mBackgroundColorAnimator.setDuration(150);
|
||||
mBackgroundColorAnimator.start();
|
||||
} else {
|
||||
mBackground.setState(new int[] {});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user