Add app to overview anim for Recents Go.

This CL adds the first iteration of window animation to go from
the app back to recents provided that the view is ready to be
visible to the user.

Bug: 114136250
Test: Go to recents, launch app, press overview to go back to recents
Change-Id: Ic0567e7c87fa964bdad25d07eca61b78407a9ff5
This commit is contained in:
Kevin 2019-03-13 15:49:31 -07:00
parent 2ec9f290bf
commit 502847f7eb
7 changed files with 204 additions and 13 deletions

View File

@ -21,15 +21,15 @@
android:orientation="horizontal"> android:orientation="horizontal">
<FrameLayout <FrameLayout
android:id="@+id/task_icon_and_thumbnail" android:id="@+id/task_icon_and_thumbnail"
android:layout_width="@dimen/task_item_height" android:layout_width="@dimen/task_thumbnail_and_icon_view_size"
android:layout_height="@dimen/task_item_height" android:layout_height="@dimen/task_thumbnail_and_icon_view_size"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:layout_marginHorizontal="8dp" android:layout_marginHorizontal="8dp"
android:layout_marginVertical="@dimen/task_item_half_vert_margin"> android:layout_marginVertical="@dimen/task_item_half_vert_margin">
<ImageView <ImageView
android:id="@+id/task_thumbnail" android:id="@+id/task_thumbnail"
android:layout_width="wrap_content" android:layout_width="@dimen/task_thumbnail_width"
android:layout_height="match_parent" android:layout_height="@dimen/task_thumbnail_height"
android:layout_gravity="top|start"/> android:layout_gravity="top|start"/>
<ImageView <ImageView
android:id="@+id/task_icon" android:id="@+id/task_icon"

View File

@ -15,7 +15,9 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<dimen name="task_item_height">60dp</dimen>
<dimen name="task_item_half_vert_margin">8dp</dimen> <dimen name="task_item_half_vert_margin">8dp</dimen>
<dimen name="task_thumbnail_and_icon_view_size">60dp</dimen>
<dimen name="task_thumbnail_height">60dp</dimen>
<dimen name="task_thumbnail_width">36dp</dimen>
<dimen name="task_icon_size">36dp</dimen> <dimen name="task_icon_size">36dp</dimen>
</resources> </resources>

View File

@ -15,15 +15,29 @@
*/ */
package com.android.quickstep; package com.android.quickstep;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.quickstep.util.RemoteAnimationProvider.getLayer;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
import android.animation.AnimatorSet; import android.animation.AnimatorSet;
import android.animation.ValueAnimator; import android.animation.ValueAnimator;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.util.Log; import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.BaseDraggingActivity;
import com.android.quickstep.util.MultiValueUpdateListener;
import com.android.quickstep.util.RemoteAnimationProvider; import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.views.IconRecentsView;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
/** /**
* Provider for the atomic remote window animation from the app to the overview. * Provider for the atomic remote window animation from the app to the overview.
@ -33,13 +47,17 @@ import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> implements final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> implements
RemoteAnimationProvider { RemoteAnimationProvider {
private static final long RECENTS_LAUNCH_DURATION = 250; private static final long APP_TO_THUMBNAIL_FADE_DURATION = 50;
private static final long APP_SCALE_DOWN_DURATION = 400;
private static final String TAG = "AppToOverviewAnimationProvider"; private static final String TAG = "AppToOverviewAnimationProvider";
private final ActivityControlHelper<T> mHelper; private final ActivityControlHelper<T> mHelper;
private final int mTargetTaskId;
private IconRecentsView mRecentsView;
AppToOverviewAnimationProvider(ActivityControlHelper<T> helper, int targetTaskId) { AppToOverviewAnimationProvider(ActivityControlHelper<T> helper, int targetTaskId) {
mHelper = helper; mHelper = helper;
mTargetTaskId = targetTaskId;
} }
/** /**
@ -54,35 +72,157 @@ final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> imple
false /* animate activity */, (controller) -> { false /* animate activity */, (controller) -> {
controller.dispatchOnStart(); controller.dispatchOnStart();
ValueAnimator anim = controller.getAnimationPlayer() ValueAnimator anim = controller.getAnimationPlayer()
.setDuration(RECENTS_LAUNCH_DURATION); .setDuration(getRecentsLaunchDuration());
anim.setInterpolator(FAST_OUT_SLOW_IN); anim.setInterpolator(FAST_OUT_SLOW_IN);
anim.start(); anim.start();
}); });
factory.onRemoteAnimationReceived(null); factory.onRemoteAnimationReceived(null);
factory.createActivityController(RECENTS_LAUNCH_DURATION); factory.createActivityController(getRecentsLaunchDuration());
mRecentsView = activity.getOverviewPanel();
return false; return false;
} }
/** /**
* Create remote window animation from the currently running app to the overview panel. * Create remote window animation from the currently running app to the overview panel. Should
* be called after {@link #onActivityReady}.
* *
* @param targetCompats the target apps * @param targetCompats the target apps
* @return animation from app to overview * @return animation from app to overview
*/ */
@Override @Override
public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targetCompats) { public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targetCompats) {
//TODO: Implement the overview to app window animation for Go.
AnimatorSet anim = new AnimatorSet(); AnimatorSet anim = new AnimatorSet();
anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION)); if (mRecentsView == null) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "No recents view. Using stub animation.");
}
anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration()));
return anim;
}
RemoteAnimationTargetCompat recentsTarget = null;
RemoteAnimationTargetCompat closingAppTarget = null;
for (RemoteAnimationTargetCompat target : targetCompats) {
if (target.mode == MODE_OPENING) {
recentsTarget = target;
} else if (target.mode == MODE_CLOSING && target.taskId == mTargetTaskId) {
closingAppTarget = target;
}
}
if (closingAppTarget == null) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "No closing app target. Using stub animation.");
}
anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration()));
return anim;
}
if (recentsTarget == null) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "No recents target. Using stub animation.");
}
anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration()));
return anim;
}
View thumbnailView = mRecentsView.getThumbnailViewForTask(mTargetTaskId);
if (thumbnailView == null) {
// TODO: We should either 1) guarantee the view is loaded before attempting this
// or 2) have a backup animation.
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "No thumbnail view for running task. Using stub animation.");
}
anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration()));
return anim;
}
playAppScaleDownAnim(anim, closingAppTarget, recentsTarget, thumbnailView);
return anim; return anim;
} }
/**
* Animate a closing app to scale down to the location of the thumbnail view in recents.
*
* @param anim animator set
* @param appTarget the app surface thats closing
* @param recentsTarget the surface containing recents
* @param thumbnailView the thumbnail view to animate to
*/
private void playAppScaleDownAnim(@NonNull AnimatorSet anim,
@NonNull RemoteAnimationTargetCompat appTarget,
@NonNull RemoteAnimationTargetCompat recentsTarget, @NonNull View thumbnailView) {
// Identify where the entering remote app should animate to.
Rect endRect = new Rect();
thumbnailView.getGlobalVisibleRect(endRect);
Rect appBounds = appTarget.sourceContainerBounds;
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 1);
valueAnimator.setDuration(APP_SCALE_DOWN_DURATION);
SyncRtSurfaceTransactionApplierCompat surfaceApplier =
new SyncRtSurfaceTransactionApplierCompat(thumbnailView);
// Keep recents visible throughout the animation.
SurfaceParams[] params = new SurfaceParams[2];
params[0] = new SurfaceParams(recentsTarget.leash, 1f, null /* matrix */,
null /* windowCrop */, getLayer(recentsTarget, MODE_OPENING), 0 /* cornerRadius */);
valueAnimator.addUpdateListener(new MultiValueUpdateListener() {
private final FloatProp mScaleX;
private final FloatProp mScaleY;
private final FloatProp mTranslationX;
private final FloatProp mTranslationY;
private final FloatProp mAlpha;
{
// Scale down and move to view location.
float endScaleX = ((float) endRect.width()) / appBounds.width();
mScaleX = new FloatProp(1f, endScaleX, 0, APP_SCALE_DOWN_DURATION,
ACCEL_DEACCEL);
float endScaleY = ((float) endRect.height()) / appBounds.height();
mScaleY = new FloatProp(1f, endScaleY, 0, APP_SCALE_DOWN_DURATION,
ACCEL_DEACCEL);
float endTranslationX = endRect.left -
(appBounds.width() - thumbnailView.getWidth()) / 2.0f;
mTranslationX = new FloatProp(0, endTranslationX, 0, APP_SCALE_DOWN_DURATION,
ACCEL_DEACCEL);
float endTranslationY = endRect.top -
(appBounds.height() - thumbnailView.getHeight()) / 2.0f;
mTranslationY = new FloatProp(0, endTranslationY, 0, APP_SCALE_DOWN_DURATION,
ACCEL_DEACCEL);
// Fade out quietly near the end to be replaced by the real view.
mAlpha = new FloatProp(1.0f, 0,
APP_SCALE_DOWN_DURATION - APP_TO_THUMBNAIL_FADE_DURATION,
APP_TO_THUMBNAIL_FADE_DURATION, ACCEL_2);
}
@Override
public void onUpdate(float percent) {
Matrix m = new Matrix();
m.setScale(mScaleX.value, mScaleY.value,
appBounds.width() / 2.0f, appBounds.height() / 2.0f);
m.postTranslate(mTranslationX.value, mTranslationY.value);
params[1] = new SurfaceParams(appTarget.leash, mAlpha.value, m,
null /* windowCrop */, getLayer(appTarget, MODE_CLOSING),
0 /* cornerRadius */);
surfaceApplier.scheduleApply(params);
}
});
anim.play(valueAnimator);
}
/** /**
* Get duration of animation from app to overview. * Get duration of animation from app to overview.
* *
* @return duration of animation * @return duration of animation
*/ */
long getRecentsLaunchDuration() { long getRecentsLaunchDuration() {
return RECENTS_LAUNCH_DURATION; return APP_SCALE_DOWN_DURATION;
} }
} }

View File

@ -21,6 +21,7 @@ import static com.android.launcher3.LauncherState.OVERVIEW;
import com.android.launcher3.Launcher; import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherInitListener; import com.android.launcher3.LauncherInitListener;
import com.android.launcher3.LauncherState;
import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.quickstep.views.IconRecentsView; import com.android.quickstep.views.IconRecentsView;
@ -38,10 +39,14 @@ public final class LauncherActivityControllerHelper extends GoActivityControlHel
public AnimationFactory prepareRecentsUI(Launcher activity, public AnimationFactory prepareRecentsUI(Launcher activity,
boolean activityVisible, boolean animateActivity, boolean activityVisible, boolean animateActivity,
Consumer<AnimatorPlaybackController> callback) { Consumer<AnimatorPlaybackController> callback) {
LauncherState fromState = activity.getStateManager().getState();
//TODO: Implement this based off where the recents view needs to be for app => recents anim. //TODO: Implement this based off where the recents view needs to be for app => recents anim.
return new AnimationFactory() { return new AnimationFactory() {
@Override @Override
public void createActivityController(long transitionLength) {} public void createActivityController(long transitionLength) {
callback.accept(activity.getStateManager().createAnimationToNewWorkspace(
fromState, OVERVIEW, transitionLength));
}
@Override @Override
public void onTransitionCancelled() {} public void onTransitionCancelled() {}

View File

@ -15,10 +15,12 @@
*/ */
package com.android.quickstep; package com.android.quickstep;
import android.util.ArrayMap;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView.Adapter; import androidx.recyclerview.widget.RecyclerView.Adapter;
import com.android.launcher3.R; import com.android.launcher3.R;
@ -36,6 +38,7 @@ public final class TaskAdapter extends Adapter<TaskHolder> {
private static final int MAX_TASKS_TO_DISPLAY = 6; private static final int MAX_TASKS_TO_DISPLAY = 6;
private static final String TAG = "TaskAdapter"; private static final String TAG = "TaskAdapter";
private final TaskListLoader mLoader; private final TaskListLoader mLoader;
private final ArrayMap<Integer, TaskItemView> mTaskIdToViewMap = new ArrayMap<>();
private TaskInputController mInputController; private TaskInputController mInputController;
public TaskAdapter(@NonNull TaskListLoader loader) { public TaskAdapter(@NonNull TaskListLoader loader) {
@ -46,6 +49,16 @@ public final class TaskAdapter extends Adapter<TaskHolder> {
mInputController = inputController; mInputController = inputController;
} }
/**
* Get task item view for a given task id if it's attached to the view.
*
* @param taskId task id to search for
* @return corresponding task item view if it's attached, null otherwise
*/
public @Nullable TaskItemView getTaskItemView(int taskId) {
return mTaskIdToViewMap.get(taskId);
}
@Override @Override
public TaskHolder onCreateViewHolder(ViewGroup parent, int viewType) { public TaskHolder onCreateViewHolder(ViewGroup parent, int viewType) {
TaskItemView itemView = (TaskItemView) LayoutInflater.from(parent.getContext()) TaskItemView itemView = (TaskItemView) LayoutInflater.from(parent.getContext())
@ -63,6 +76,17 @@ public final class TaskAdapter extends Adapter<TaskHolder> {
return; return;
} }
holder.bindTask(tasks.get(position)); holder.bindTask(tasks.get(position));
}
@Override
public void onViewAttachedToWindow(@NonNull TaskHolder holder) {
mTaskIdToViewMap.put(holder.getTask().key.id, (TaskItemView) holder.itemView);
}
@Override
public void onViewDetachedFromWindow(@NonNull TaskHolder holder) {
mTaskIdToViewMap.remove(holder.getTask().key.id);
} }
@Override @Override

View File

@ -27,6 +27,7 @@ import android.view.ViewDebug;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -155,6 +156,20 @@ public final class IconRecentsView extends FrameLayout {
}); });
} }
/**
* Get the thumbnail view associated with a task for the purposes of animation.
*
* @param taskId task id of thumbnail view to get
* @return the thumbnail view for the task if attached, null otherwise
*/
public @Nullable View getThumbnailViewForTask(int taskId) {
TaskItemView view = mTaskAdapter.getTaskItemView(taskId);
if (view == null) {
return null;
}
return view.getThumbnailView();
}
public void setTranslationYFactor(float translationFactor) { public void setTranslationYFactor(float translationFactor) {
mTranslationYFactor = translationFactor; mTranslationYFactor = translationFactor;
setTranslationY(computeTranslationYForFactor(mTranslationYFactor)); setTranslationY(computeTranslationYForFactor(mTranslationYFactor));

View File

@ -19,6 +19,7 @@ import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
@ -76,4 +77,8 @@ public final class TaskItemView extends LinearLayout {
public void setThumbnail(Bitmap thumbnail) { public void setThumbnail(Bitmap thumbnail) {
mThumbnailView.setImageBitmap(thumbnail); mThumbnailView.setImageBitmap(thumbnail);
} }
public View getThumbnailView() {
return mThumbnailView;
}
} }