diff --git a/go/quickstep/src/com/android/quickstep/TaskAdapter.java b/go/quickstep/src/com/android/quickstep/TaskAdapter.java index 674fcae914..66c074bddf 100644 --- a/go/quickstep/src/com/android/quickstep/TaskAdapter.java +++ b/go/quickstep/src/com/android/quickstep/TaskAdapter.java @@ -94,7 +94,7 @@ public final class TaskAdapter extends Adapter { return; } Task task = tasks.get(position); - holder.bindTask(task); + holder.bindTask(task, false /* willAnimate */); mLoader.loadTaskIconAndLabel(task, () -> { // Ensure holder still has the same task. if (Objects.equals(task, holder.getTask())) { @@ -109,6 +109,13 @@ public final class TaskAdapter extends Adapter { }); } + @Override + public void onBindViewHolder(@NonNull TaskHolder holder, int position, + @NonNull List payloads) { + // TODO: Bind task in preparation for animation. For now, we apply UI changes immediately. + super.onBindViewHolder(holder, position, payloads); + } + @Override public void onViewAttachedToWindow(@NonNull TaskHolder holder) { if (holder.getTask() == null) { diff --git a/go/quickstep/src/com/android/quickstep/TaskHolder.java b/go/quickstep/src/com/android/quickstep/TaskHolder.java index 98dc989317..91a3534c00 100644 --- a/go/quickstep/src/com/android/quickstep/TaskHolder.java +++ b/go/quickstep/src/com/android/quickstep/TaskHolder.java @@ -15,6 +15,9 @@ */ package com.android.quickstep; +import android.graphics.Bitmap; + +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView.ViewHolder; @@ -40,13 +43,28 @@ public final class TaskHolder extends ViewHolder { } /** - * Bind a task to the holder, resetting the view and preparing it for content to load in. + * Bind the task model to the holder. This will take the current task content in the task + * object (i.e. icon, thumbnail, label) and either apply the content immediately or simply bind + * the content to animate to at a later time. If the task does not have all its content loaded, + * the view will prepare appropriate default placeholders and it is the callers responsibility + * to change them at a later time. + * + * Regardless of whether it is animating, input handlers will be bound immediately (see + * {@link TaskActionController}). * * @param task the task to bind to the view + * @param willAnimate true if UI should animate in later, false if it should apply immediately */ - public void bindTask(Task task) { + public void bindTask(@NonNull Task task, boolean willAnimate) { mTask = task; - mTaskItemView.resetTaskItemView(); + Bitmap thumbnail = (task.thumbnail != null) ? task.thumbnail.thumbnail : null; + if (willAnimate) { + mTaskItemView.startContentAnimation(task.icon, thumbnail, task.titleDescription); + } else { + mTaskItemView.setIcon(task.icon); + mTaskItemView.setThumbnail(thumbnail); + mTaskItemView.setLabel(task.titleDescription); + } } /** diff --git a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java index 0a3fba8405..a8fc78a339 100644 --- a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java +++ b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java @@ -21,6 +21,7 @@ import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; +import android.util.FloatProperty; import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; @@ -39,15 +40,37 @@ public final class TaskItemView extends LinearLayout { private static final String DEFAULT_LABEL = "..."; private final Drawable mDefaultIcon; private final Drawable mDefaultThumbnail; + private final TaskLayerDrawable mIconDrawable; + private final TaskLayerDrawable mThumbnailDrawable; private TextView mLabelView; private ImageView mIconView; private ImageView mThumbnailView; + private float mContentTransitionProgress; + + /** + * Property representing the content transition progress of the view. 1.0f represents that the + * currently bound icon, thumbnail, and label are fully animated in and visible. + */ + public static FloatProperty CONTENT_TRANSITION_PROGRESS = + new FloatProperty("taskContentTransitionProgress") { + @Override + public void setValue(TaskItemView view, float progress) { + view.setContentTransitionProgress(progress); + } + + @Override + public Float get(TaskItemView view) { + return view.mContentTransitionProgress; + } + }; public TaskItemView(Context context, AttributeSet attrs) { super(context, attrs); Resources res = context.getResources(); mDefaultIcon = res.getDrawable(android.R.drawable.sym_def_app_icon, context.getTheme()); mDefaultThumbnail = res.getDrawable(R.drawable.default_thumbnail, context.getTheme()); + mIconDrawable = new TaskLayerDrawable(context); + mThumbnailDrawable = new TaskLayerDrawable(context); } @Override @@ -56,6 +79,12 @@ public final class TaskItemView extends LinearLayout { mLabelView = findViewById(R.id.task_label); mThumbnailView = findViewById(R.id.task_thumbnail); mIconView = findViewById(R.id.task_icon); + + mThumbnailView.setImageDrawable(mThumbnailDrawable); + mIconView.setImageDrawable(mIconDrawable); + + resetTaskItemView(); + CONTENT_TRANSITION_PROGRESS.setValue(this, 1.0f); } /** @@ -74,6 +103,7 @@ public final class TaskItemView extends LinearLayout { */ public void setLabel(@Nullable String label) { mLabelView.setText(getSafeLabel(label)); + // TODO: Animation for label } /** @@ -86,7 +116,7 @@ public final class TaskItemView extends LinearLayout { // The icon proper is actually smaller than the drawable and has "padding" on the side for // the purpose of drawing the shadow, allowing the icon to pop up, so we need to scale the // view if we want the icon to be flush with the bottom of the thumbnail. - mIconView.setImageDrawable(getSafeIcon(icon)); + mIconDrawable.setCurrentDrawable(getSafeIcon(icon)); } /** @@ -95,13 +125,38 @@ public final class TaskItemView extends LinearLayout { * @param thumbnail task thumbnail for the task */ public void setThumbnail(@Nullable Bitmap thumbnail) { - mThumbnailView.setImageDrawable(getSafeThumbnail(thumbnail)); + mThumbnailDrawable.setCurrentDrawable(getSafeThumbnail(thumbnail)); } public View getThumbnailView() { return mThumbnailView; } + /** + * Start a new animation from the current task content to the specified new content. The caller + * is responsible for the actual animation control via the property + * {@link #CONTENT_TRANSITION_PROGRESS}. + * + * @param endIcon the icon to animate to + * @param endThumbnail the thumbnail to animate to + * @param endLabel the label to animate to + */ + public void startContentAnimation(@Nullable Drawable endIcon, @Nullable Bitmap endThumbnail, + @Nullable String endLabel) { + mIconDrawable.startNewTransition(getSafeIcon(endIcon)); + mThumbnailDrawable.startNewTransition(getSafeThumbnail(endThumbnail)); + // TODO: Animation for label + + setContentTransitionProgress(0.0f); + } + + private void setContentTransitionProgress(float progress) { + mContentTransitionProgress = progress; + mIconDrawable.setTransitionProgress(progress); + mThumbnailDrawable.setTransitionProgress(progress); + // TODO: Animation for label + } + private @NonNull Drawable getSafeIcon(@Nullable Drawable icon) { return (icon != null) ? icon : mDefaultIcon; }