Merge "Implement swipe to dismiss recent tasks" into ub-launcher3-master
diff --git a/quickstep/res/drawable/task_thumbnail_background.xml b/quickstep/res/drawable/task_thumbnail_background.xml
index 603380e..f1f48ac 100644
--- a/quickstep/res/drawable/task_thumbnail_background.xml
+++ b/quickstep/res/drawable/task_thumbnail_background.xml
@@ -14,6 +14,5 @@
limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
- <solid android:color="#FF000000" />
<corners android:radius="2dp" />
</shape>
diff --git a/quickstep/src/com/android/quickstep/RecentsView.java b/quickstep/src/com/android/quickstep/RecentsView.java
index c0fd2cf..a107343 100644
--- a/quickstep/src/com/android/quickstep/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/RecentsView.java
@@ -16,6 +16,7 @@
package com.android.quickstep;
+import android.animation.LayoutTransition;
import android.animation.TimeInterpolator;
import android.content.Context;
import android.graphics.Rect;
@@ -25,6 +26,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
import com.android.launcher3.PagedView;
import com.android.launcher3.R;
import com.android.launcher3.dragndrop.DragLayer;
@@ -58,6 +60,7 @@
private boolean mOverviewStateEnabled;
private boolean mTaskStackListenerRegistered;
+ private LayoutTransition mLayoutTransition;
private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
@Override
@@ -87,6 +90,18 @@
setWillNotDraw(false);
setPageSpacing((int) getResources().getDimension(R.dimen.recents_page_spacing));
enableFreeScroll(true);
+ setupLayoutTransition();
+ }
+
+ private void setupLayoutTransition() {
+ // We want to show layout transitions when pages are deleted, to close the gap.
+ mLayoutTransition = new LayoutTransition();
+ mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
+ mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
+
+ mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
+ mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
+ setLayoutTransition(mLayoutTransition);
}
@Override
@@ -141,6 +156,7 @@
// necessary)
final LayoutInflater inflater = LayoutInflater.from(getContext());
final ArrayList<Task> tasks = stack.getTasks();
+ setLayoutTransition(null);
for (int i = getChildCount(); i < tasks.size(); i++) {
final TaskView taskView = (TaskView) inflater.inflate(R.layout.task, this, false);
addView(taskView);
@@ -150,6 +166,7 @@
removeView(taskView);
loader.unloadTaskData(taskView.getTask());
}
+ setLayoutTransition(mLayoutTransition);
// Rebind all task views
for (int i = tasks.size() - 1; i >= 0; i--) {
@@ -248,4 +265,12 @@
}
}
}
+
+ public void onTaskDismissed(TaskView taskView) {
+ ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
+ removeView(taskView);
+ if (getChildCount() == 0) {
+ Launcher.getLauncher(getContext()).getStateManager().goToState(LauncherState.NORMAL);
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/TaskView.java b/quickstep/src/com/android/quickstep/TaskView.java
index ac9a778..a0ad618 100644
--- a/quickstep/src/com/android/quickstep/TaskView.java
+++ b/quickstep/src/com/android/quickstep/TaskView.java
@@ -16,15 +16,25 @@
package com.android.quickstep;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.app.ActivityOptions;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.util.Property;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.android.launcher3.R;
-import com.android.launcher3.uioverrides.OverviewState;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.touch.SwipeDetector;
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;
@@ -39,11 +49,38 @@
/**
* A task in the Recents view.
*/
-public class TaskView extends FrameLayout implements TaskCallbacks {
+public class TaskView extends FrameLayout implements TaskCallbacks, SwipeDetector.Listener {
+
+ private static final int SWIPE_DIRECTIONS = SwipeDetector.DIRECTION_POSITIVE;
+
+ /**
+ * The task will appear fully dismissed when the distance swiped
+ * reaches this percentage of the card height.
+ */
+ private static final float SWIPE_DISTANCE_HEIGHT_PERCENTAGE = 0.38f;
+
+ private static final Property<TaskView, Float> PROPERTY_SWIPE_PROGRESS =
+ new Property<TaskView, Float>(Float.class, "swipe_progress") {
+
+ @Override
+ public Float get(TaskView taskView) {
+ return taskView.mSwipeProgress;
+ }
+
+ @Override
+ public void set(TaskView taskView, Float progress) {
+ taskView.setSwipeProgress(progress);
+ }
+ };
private Task mTask;
private TaskThumbnailView mSnapshotView;
private ImageView mIconView;
+ private SwipeDetector mSwipeDetector;
+ private float mSwipeDistance;
+ private float mSwipeProgress;
+ private Interpolator mAlphaInterpolator;
+ private Interpolator mSwipeAnimInterpolator;
public TaskView(Context context) {
this(context, null);
@@ -58,6 +95,11 @@
setOnClickListener((view) -> {
launchTask(true /* animate */);
});
+
+ mSwipeDetector = new SwipeDetector(getContext(), this, SwipeDetector.VERTICAL);
+ mSwipeDetector.setDetectableScrollConditions(SWIPE_DIRECTIONS, false);
+ mAlphaInterpolator = Interpolators.ACCEL_1_5;
+ mSwipeAnimInterpolator = Interpolators.SCROLL_CUBIC;
}
@Override
@@ -67,6 +109,15 @@
mIconView = findViewById(R.id.icon);
}
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ View p = (View) getParent();
+ mSwipeDistance = (getMeasuredHeight() - p.getPaddingTop() - p.getPaddingBottom())
+ * SWIPE_DISTANCE_HEIGHT_PERCENTAGE;
+ }
+
/**
* Updates this task view to the given {@param task}.
*/
@@ -134,4 +185,78 @@
public void onTaskWindowingModeChanged() {
// Do nothing
}
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ mSwipeDetector.onTouchEvent(ev);
+ return super.onInterceptTouchEvent(ev);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ mSwipeDetector.onTouchEvent(event);
+ return mSwipeDetector.isDraggingOrSettling() || super.onTouchEvent(event);
+ }
+
+ // Swipe detector methods
+
+ @Override
+ public void onDragStart(boolean start) {
+ getParent().requestDisallowInterceptTouchEvent(true);
+ }
+
+ @Override
+ public boolean onDrag(float displacement, float velocity) {
+ setSwipeProgress(Utilities.boundToRange(displacement / mSwipeDistance,
+ allowsSwipeUp() ? -1 : 0, allowsSwipeDown() ? 1 : 0));
+ return true;
+ }
+
+ /**
+ * Indicates the page is being removed.
+ * @param progress Ranges from -1 (fading upwards) to 1 (fading downwards).
+ */
+ private void setSwipeProgress(float progress) {
+ mSwipeProgress = progress;
+ float translationY = mSwipeProgress * mSwipeDistance;
+ float alpha = 1f - mAlphaInterpolator.getInterpolation(Math.abs(mSwipeProgress));
+ // Only change children to avoid changing our properties while dragging.
+ mIconView.setTranslationY(translationY);
+ mSnapshotView.setTranslationY(translationY);
+ mIconView.setAlpha(alpha);
+ mSnapshotView.setAlpha(alpha);
+ }
+
+ private boolean allowsSwipeUp() {
+ return (SWIPE_DIRECTIONS & SwipeDetector.DIRECTION_POSITIVE) != 0;
+ }
+
+ private boolean allowsSwipeDown() {
+ return (SWIPE_DIRECTIONS & SwipeDetector.DIRECTION_NEGATIVE) != 0;
+ }
+
+ @Override
+ public void onDragEnd(float velocity, boolean fling) {
+ boolean movingAwayFromCenter = velocity < 0 == mSwipeProgress < 0;
+ boolean flingAway = fling && movingAwayFromCenter
+ && (allowsSwipeUp() && velocity < 0 || allowsSwipeDown() && velocity > 0);
+ final boolean shouldRemove = flingAway || (!fling && Math.abs(mSwipeProgress) > 0.5f);
+ float fromProgress = mSwipeProgress;
+ float toProgress = !shouldRemove ? 0f : mSwipeProgress < 0 ? -1f : 1f;
+ ValueAnimator swipeAnimator = ObjectAnimator.ofFloat(this, PROPERTY_SWIPE_PROGRESS,
+ fromProgress, toProgress);
+ swipeAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (shouldRemove) {
+ ((RecentsView) getParent()).onTaskDismissed(TaskView.this);
+ }
+ mSwipeDetector.finishedScrolling();
+ }
+ });
+ swipeAnimator.setDuration(SwipeDetector.calculateDuration(velocity,
+ Math.abs(toProgress - fromProgress)));
+ swipeAnimator.setInterpolator(mSwipeAnimInterpolator);
+ swipeAnimator.start();
+ }
}