From 6092a6ee1ef6a129525ab09acd008ce4d78add0a Mon Sep 17 00:00:00 2001 From: Kevin Date: Mon, 8 Apr 2019 17:09:19 -0700 Subject: [PATCH 01/25] Add drawable for default thumbnail for Recents Go This CL adds a drawable for the default thumbnail view and refactors the logic to get the default icon/thumbnail/label if null to separate methods for re-use in later CLs. Bug: 114136250 Test: Manual; Go to icon recents Change-Id: I511ea40ace040fc53ffc69c27149f24d69bda7b0 (cherry picked from commit 34ee30c4f92a29685a33a4bfd01f8efd10631142) --- .../res/drawable/default_thumbnail.xml | 22 ++++++++++ .../android/quickstep/views/TaskItemView.java | 43 ++++++++++--------- 2 files changed, 45 insertions(+), 20 deletions(-) create mode 100644 go/quickstep/res/drawable/default_thumbnail.xml diff --git a/go/quickstep/res/drawable/default_thumbnail.xml b/go/quickstep/res/drawable/default_thumbnail.xml new file mode 100644 index 0000000000..0a2dbf03de --- /dev/null +++ b/go/quickstep/res/drawable/default_thumbnail.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java index d831b206a6..0a3fba8405 100644 --- a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java +++ b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java @@ -16,8 +16,9 @@ package com.android.quickstep.views; import android.content.Context; +import android.content.res.Resources; import android.graphics.Bitmap; -import android.graphics.Color; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.View; @@ -25,6 +26,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.R; @@ -36,14 +38,16 @@ public final class TaskItemView extends LinearLayout { private static final String DEFAULT_LABEL = "..."; private final Drawable mDefaultIcon; + private final Drawable mDefaultThumbnail; private TextView mLabelView; private ImageView mIconView; private ImageView mThumbnailView; public TaskItemView(Context context, AttributeSet attrs) { super(context, attrs); - mDefaultIcon = context.getResources().getDrawable( - android.R.drawable.sym_def_app_icon, context.getTheme()); + 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()); } @Override @@ -69,11 +73,7 @@ public final class TaskItemView extends LinearLayout { * @param label task label */ public void setLabel(@Nullable String label) { - if (label == null) { - mLabelView.setText(DEFAULT_LABEL); - return; - } - mLabelView.setText(label); + mLabelView.setText(getSafeLabel(label)); } /** @@ -86,11 +86,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. - if (icon == null) { - mIconView.setImageDrawable(mDefaultIcon); - return; - } - mIconView.setImageDrawable(icon); + mIconView.setImageDrawable(getSafeIcon(icon)); } /** @@ -99,16 +95,23 @@ public final class TaskItemView extends LinearLayout { * @param thumbnail task thumbnail for the task */ public void setThumbnail(@Nullable Bitmap thumbnail) { - if (thumbnail == null) { - mThumbnailView.setImageBitmap(null); - mThumbnailView.setBackgroundColor(Color.GRAY); - return; - } - mThumbnailView.setBackgroundColor(Color.TRANSPARENT); - mThumbnailView.setImageBitmap(thumbnail); + mThumbnailView.setImageDrawable(getSafeThumbnail(thumbnail)); } public View getThumbnailView() { return mThumbnailView; } + + private @NonNull Drawable getSafeIcon(@Nullable Drawable icon) { + return (icon != null) ? icon : mDefaultIcon; + } + + private @NonNull Drawable getSafeThumbnail(@Nullable Bitmap thumbnail) { + return (thumbnail != null) ? new BitmapDrawable(getResources(), thumbnail) + : mDefaultThumbnail; + } + + private @NonNull String getSafeLabel(@Nullable String label) { + return (label != null) ? label : DEFAULT_LABEL; + } } From fbe9182b7502df6ed8db0446130cdde53bc84095 Mon Sep 17 00:00:00 2001 From: Kevin Date: Wed, 10 Apr 2019 14:04:43 -0700 Subject: [PATCH 02/25] Change layout anim from Animation => Animator Change the layout animation to use animators instead of the built-in animation-based layout animation. Animator-based animations are more flexible and can act on the view properties themselves, making it easier to deal with if we need to cancel the animation later from a conflicting animation (i.e. we find out we need to animate a view out). Bug: 114136250 Test: Go to recents, see items animate in Change-Id: Id8227cd50e81999cac98912ac58cd2d6864c40af (cherry picked from commit 26ad999b10c35a28585ffd3a5928d90446379c95) --- .../quickstep/views/IconRecentsView.java | 91 +++++++++++++++---- 1 file changed, 71 insertions(+), 20 deletions(-) diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java index c06b6ec416..7f77b6cc4d 100644 --- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java +++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java @@ -24,14 +24,12 @@ import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; import android.content.Context; +import android.util.ArraySet; import android.util.AttributeSet; import android.util.FloatProperty; import android.view.View; import android.view.ViewDebug; -import android.view.animation.AlphaAnimation; -import android.view.animation.Animation; -import android.view.animation.AnimationSet; -import android.view.animation.LayoutAnimationController; +import android.view.ViewTreeObserver; import android.widget.FrameLayout; import androidx.annotation.NonNull; @@ -40,6 +38,7 @@ import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver; +import androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener; import com.android.launcher3.R; import com.android.quickstep.RecentsToActivityHelper; @@ -90,7 +89,6 @@ public final class IconRecentsView extends FrameLayout { private final TaskListLoader mTaskLoader; private final TaskAdapter mTaskAdapter; private final TaskActionController mTaskActionController; - private final LayoutAnimationController mLayoutAnimation; private RecentsToActivityHelper mActivityHelper; private RecyclerView mTaskRecyclerView; @@ -98,6 +96,9 @@ public final class IconRecentsView extends FrameLayout { private View mContentView; private View mClearAllView; private boolean mTransitionedFromApp; + private AnimatorSet mLayoutAnimation; + private final ArraySet mLayingOutViews = new ArraySet<>(); + public IconRecentsView(Context context, AttributeSet attrs) { super(context, attrs); @@ -106,7 +107,6 @@ public final class IconRecentsView extends FrameLayout { mTaskAdapter = new TaskAdapter(mTaskLoader); mTaskActionController = new TaskActionController(mTaskLoader, mTaskAdapter); mTaskAdapter.setActionController(mTaskActionController); - mLayoutAnimation = createLayoutAnimation(); } @Override @@ -120,7 +120,20 @@ public final class IconRecentsView extends FrameLayout { ItemTouchHelper helper = new ItemTouchHelper( new TaskSwipeCallback(mTaskActionController)); helper.attachToRecyclerView(mTaskRecyclerView); - mTaskRecyclerView.setLayoutAnimation(mLayoutAnimation); + mTaskRecyclerView.addOnChildAttachStateChangeListener( + new OnChildAttachStateChangeListener() { + @Override + public void onChildViewAttachedToWindow(@NonNull View view) { + if (mLayoutAnimation != null && !mLayingOutViews.contains(view)) { + // Child view was added that is not part of current layout animation + // so restart the animation. + animateFadeInLayoutAnimation(); + } + } + + @Override + public void onChildViewDetachedFromWindow(@NonNull View view) { } + }); mEmptyView = findViewById(R.id.recent_task_empty_view); mContentView = findViewById(R.id.recent_task_content_view); @@ -165,8 +178,7 @@ public final class IconRecentsView extends FrameLayout { * becomes visible. */ public void onBeginTransitionToOverview() { - mTaskRecyclerView.scheduleLayoutAnimation(); - + scheduleFadeInLayoutAnimation(); // Load any task changes if (!mTaskLoader.needsToLoad()) { return; @@ -338,17 +350,56 @@ public final class IconRecentsView extends FrameLayout { }); } - private static LayoutAnimationController createLayoutAnimation() { - AnimationSet anim = new AnimationSet(false /* shareInterpolator */); + /** + * Schedule a one-shot layout animation on the next layout. Separate from + * {@link #scheduleLayoutAnimation()} as the animation is {@link Animator} based and acts on the + * view properties themselves, allowing more controllable behavior and making it easier to + * manage when the animation conflicts with another animation. + */ + private void scheduleFadeInLayoutAnimation() { + ViewTreeObserver viewTreeObserver = mTaskRecyclerView.getViewTreeObserver(); + viewTreeObserver.addOnGlobalLayoutListener( + new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + animateFadeInLayoutAnimation(); + viewTreeObserver.removeOnGlobalLayoutListener(this); + } + }); + } - Animation alphaAnim = new AlphaAnimation(0, 1); - alphaAnim.setDuration(LAYOUT_ITEM_ANIMATE_IN_DURATION); - anim.addAnimation(alphaAnim); - - LayoutAnimationController layoutAnim = new LayoutAnimationController(anim); - layoutAnim.setDelay( - (float) LAYOUT_ITEM_ANIMATE_IN_DELAY_BETWEEN / LAYOUT_ITEM_ANIMATE_IN_DURATION); - - return layoutAnim; + /** + * Start animating the layout animation where items fade in. + */ + private void animateFadeInLayoutAnimation() { + if (mLayoutAnimation != null) { + // If layout animation still in progress, cancel and restart. + mLayoutAnimation.cancel(); + } + TaskItemView[] views = getTaskViews(); + int delay = 0; + mLayoutAnimation = new AnimatorSet(); + for (TaskItemView view : views) { + view.setAlpha(0.0f); + Animator alphaAnim = ObjectAnimator.ofFloat(view, ALPHA, 0.0f, 1.0f); + alphaAnim.setDuration(LAYOUT_ITEM_ANIMATE_IN_DURATION).setStartDelay(delay); + alphaAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + view.setAlpha(1.0f); + mLayingOutViews.remove(view); + } + }); + delay += LAYOUT_ITEM_ANIMATE_IN_DELAY_BETWEEN; + mLayoutAnimation.play(alphaAnim); + mLayingOutViews.add(view); + } + mLayoutAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mLayoutAnimation = null; + } + }); + mLayoutAnimation.start(); } } From 7b42d2287adaf9cca56ac2493f240cd22213b170 Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 11 Apr 2019 11:16:06 -0700 Subject: [PATCH 03/25] Fix transition progress not applying to drawables TaskLayerDrawable should reapply UI changes from the transition progress when there is a new drawable. In addition, the transition should check if the drawable in the front and back are the same (i.e. on initialization when both are showing the empty drawable) so that it only applies the front-drawable alpha. Bug: 114136250 Test: Builds Change-Id: I74836b5043da555358742ba0a3f46f170f590904 (cherry picked from commit 1531982d1ed7c03cc1ec4c0a6d6069ff747de5b5) --- .../android/quickstep/views/TaskLayerDrawable.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/go/quickstep/src/com/android/quickstep/views/TaskLayerDrawable.java b/go/quickstep/src/com/android/quickstep/views/TaskLayerDrawable.java index 3a230482b7..98b66b995d 100644 --- a/go/quickstep/src/com/android/quickstep/views/TaskLayerDrawable.java +++ b/go/quickstep/src/com/android/quickstep/views/TaskLayerDrawable.java @@ -31,6 +31,7 @@ import com.android.launcher3.R; */ public final class TaskLayerDrawable extends LayerDrawable { private final Drawable mEmptyDrawable; + private float mProgress; public TaskLayerDrawable(Context context) { super(new Drawable[0]); @@ -50,6 +51,7 @@ public final class TaskLayerDrawable extends LayerDrawable { */ public void setCurrentDrawable(@NonNull Drawable drawable) { setDrawable(0, drawable); + applyTransitionProgress(mProgress); } /** @@ -82,9 +84,18 @@ public final class TaskLayerDrawable extends LayerDrawable { if (progress > 1 || progress < 0) { throw new IllegalArgumentException("Transition progress should be between 0 and 1"); } + mProgress = progress; + applyTransitionProgress(progress); + } + + private void applyTransitionProgress(float progress) { int drawableAlpha = (int) (progress * 255); getDrawable(0).setAlpha(drawableAlpha); - getDrawable(1).setAlpha(255 - drawableAlpha); + if (getDrawable(0) != getDrawable(1)) { + // Only do this if it's a different drawable so that it fades out. + // Otherwise, we'd just be overwriting the front drawable's alpha. + getDrawable(1).setAlpha(255 - drawableAlpha); + } invalidateSelf(); } } From 2ee78b7310e4592ffec08c805d15ae0ff0ae457a Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 11 Apr 2019 13:07:46 -0700 Subject: [PATCH 04/25] Add task content animation property Add API to animate task item content transition from one set of task content (icon, thumbnail) to another. To do this, we provide two things to the caller: startContentAnimation which allows the caller to set the icon, thumbnail, and label to animate to and the CONTENT_TRANSITION_PROGRESS property which the caller can use to control the transition progress. We will eventually hook this up to onBindViewHolder for the task adapter when there is a data change event to prepare to animate content in. Currently it still changes immediately. Bug: 114136250 Test: Builds Change-Id: I16e9b757ee91be54fe8cba6780b399e3cc313e3e (cherry picked from commit a3d80d102e108b17adce49c440c0f4b84d3867e5) --- .../com/android/quickstep/TaskAdapter.java | 9 ++- .../src/com/android/quickstep/TaskHolder.java | 24 +++++++- .../android/quickstep/views/TaskItemView.java | 59 ++++++++++++++++++- 3 files changed, 86 insertions(+), 6 deletions(-) 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; } From 6f927ecd8ef12f17f5814d453d136690988782fc Mon Sep 17 00:00:00 2001 From: Kevin Date: Mon, 8 Apr 2019 16:35:03 -0700 Subject: [PATCH 05/25] Differentiate empty UI from default in recents Go Use the "empty" drawable as the initial UI until the recents task list and order is loaded, then switch to default/filled UI. Bug: 114136250 Test: Manual test; see empty UI when loading takes time Change-Id: I85b72e6d40d7224b28217cbf4e05515ec1e9451b (cherry picked from commit a2c4200cde475cd5ab9de35945e2e8458589c39c) --- .../src/com/android/quickstep/TaskHolder.java | 5 +---- .../com/android/quickstep/views/TaskItemView.java | 13 +++++++------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/go/quickstep/src/com/android/quickstep/TaskHolder.java b/go/quickstep/src/com/android/quickstep/TaskHolder.java index 91a3534c00..a3fa5c1f2b 100644 --- a/go/quickstep/src/com/android/quickstep/TaskHolder.java +++ b/go/quickstep/src/com/android/quickstep/TaskHolder.java @@ -73,10 +73,7 @@ public final class TaskHolder extends ViewHolder { */ public void bindEmptyUi() { mTask = null; - // TODO: Set the task view to a loading, empty UI. - // Temporarily using the one below for visual confirmation but should be swapped out to new - // UI later. - mTaskItemView.resetTaskItemView(); + mTaskItemView.resetToEmptyUi(); } /** diff --git a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java index a8fc78a339..572747bfdd 100644 --- a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java +++ b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java @@ -37,6 +37,7 @@ import com.android.launcher3.R; */ public final class TaskItemView extends LinearLayout { + private static final String EMPTY_LABEL = ""; private static final String DEFAULT_LABEL = "..."; private final Drawable mDefaultIcon; private final Drawable mDefaultThumbnail; @@ -83,17 +84,17 @@ public final class TaskItemView extends LinearLayout { mThumbnailView.setImageDrawable(mThumbnailDrawable); mIconView.setImageDrawable(mIconDrawable); - resetTaskItemView(); + resetToEmptyUi(); CONTENT_TRANSITION_PROGRESS.setValue(this, 1.0f); } /** - * Resets task item view to default values. + * Resets task item view to empty, loading UI. */ - public void resetTaskItemView() { - setLabel(DEFAULT_LABEL); - setIcon(null); - setThumbnail(null); + public void resetToEmptyUi() { + mIconDrawable.resetDrawable(); + mThumbnailDrawable.resetDrawable(); + setLabel(EMPTY_LABEL); } /** From 1060a0d7fc41a61e7b7646265455bd2916ffbdc5 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 12 Apr 2019 09:57:25 -0700 Subject: [PATCH 06/25] Animate to bottom view in app => overview We always want to animate to the bottom so we should just do that if we have the view laid out even if at the time the app task doesn't actually match the view. Bug: 114136250 Test: Go to recents, press overview twice quickly, see it animate correctly Change-Id: I0516ef127ff6ef0f865c85314c9ffe4a7c6ef9e3 (cherry picked from commit 86957f28fff4c3b68f4ea81d48a29b42c53e8176) --- .../AppToOverviewAnimationProvider.java | 7 +++-- .../com/android/quickstep/TaskAdapter.java | 29 ------------------- .../quickstep/views/IconRecentsView.java | 11 ++++--- 3 files changed, 9 insertions(+), 38 deletions(-) diff --git a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java index d1d697c0cd..c228bb94fe 100644 --- a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java +++ b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java @@ -131,10 +131,11 @@ final class AppToOverviewAnimationProvider imple return anim; } - View thumbnailView = mRecentsView.getThumbnailViewForTask(mTargetTaskId); + View thumbnailView = mRecentsView.getBottomThumbnailView(); if (thumbnailView == null) { - // TODO: We should either 1) guarantee the view is loaded before attempting this - // or 2) have a backup animation. + // This can be null if there were previously 0 tasks and the recycler view has not had + // enough time to take in the data change, bind a new view, and lay out the new view. + // TODO: Have a fallback to animate to if (Log.isLoggable(TAG, Log.WARN)) { Log.w(TAG, "No thumbnail view for running task. Using stub animation."); } diff --git a/go/quickstep/src/com/android/quickstep/TaskAdapter.java b/go/quickstep/src/com/android/quickstep/TaskAdapter.java index 66c074bddf..02cbf4e010 100644 --- a/go/quickstep/src/com/android/quickstep/TaskAdapter.java +++ b/go/quickstep/src/com/android/quickstep/TaskAdapter.java @@ -15,12 +15,10 @@ */ package com.android.quickstep; -import android.util.ArrayMap; import android.view.LayoutInflater; import android.view.ViewGroup; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView.Adapter; import com.android.launcher3.R; @@ -39,7 +37,6 @@ public final class TaskAdapter extends Adapter { private static final int MAX_TASKS_TO_DISPLAY = 6; private static final String TAG = "TaskAdapter"; private final TaskListLoader mLoader; - private final ArrayMap mTaskIdToViewMap = new ArrayMap<>(); private TaskActionController mTaskActionController; private boolean mIsShowingLoadingUi; @@ -63,16 +60,6 @@ public final class TaskAdapter extends Adapter { mIsShowingLoadingUi = isShowingLoadingUi; } - /** - * 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 public TaskHolder onCreateViewHolder(ViewGroup parent, int viewType) { TaskItemView itemView = (TaskItemView) LayoutInflater.from(parent.getContext()) @@ -116,22 +103,6 @@ public final class TaskAdapter extends Adapter { super.onBindViewHolder(holder, position, payloads); } - @Override - public void onViewAttachedToWindow(@NonNull TaskHolder holder) { - if (holder.getTask() == null) { - return; - } - mTaskIdToViewMap.put(holder.getTask().key.id, (TaskItemView) holder.itemView); - } - - @Override - public void onViewDetachedFromWindow(@NonNull TaskHolder holder) { - if (holder.getTask() == null) { - return; - } - mTaskIdToViewMap.remove(holder.getTask().key.id); - } - @Override public int getItemCount() { if (mIsShowingLoadingUi) { diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java index 7f77b6cc4d..59755bcb39 100644 --- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java +++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java @@ -225,16 +225,15 @@ public final class IconRecentsView extends FrameLayout { } /** - * Get the thumbnail view associated with a task for the purposes of animation. + * Get the bottom most thumbnail view to animate to. * - * @param taskId task id of thumbnail view to get - * @return the thumbnail view for the task if attached, null otherwise + * @return the thumbnail view if laid out */ - public @Nullable View getThumbnailViewForTask(int taskId) { - TaskItemView view = mTaskAdapter.getTaskItemView(taskId); - if (view == null) { + public @Nullable View getBottomThumbnailView() { + if (mTaskRecyclerView.getChildCount() == 0) { return null; } + TaskItemView view = (TaskItemView) mTaskRecyclerView.getChildAt(0); return view.getThumbnailView(); } From 29cdac41f1352e7b36eb05f483417c5666048cbb Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 11 Apr 2019 16:40:29 -0700 Subject: [PATCH 07/25] Animate content fill animation to Recents Go This CL adds the animation that occurs when transitioning from a set of empty views to the actual task list after it is loaded. This is done by setting a one-shot item animator that animates changes, for item views that fade from empty to filled, and removes, for when we have too many empty views. Bug: 114136250 Test: Artificially increase task list load time and see animation fill Change-Id: Ibbc09db702e591063ceea61df2359f18a3fcf8f9 (cherry picked from commit 987799dfa15ebfd55c93e4c509cc200792b95fab) --- .../quickstep/ContentFillItemAnimator.java | 276 ++++++++++++++++++ .../com/android/quickstep/TaskAdapter.java | 33 ++- .../quickstep/views/IconRecentsView.java | 24 +- 3 files changed, 323 insertions(+), 10 deletions(-) create mode 100644 go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java diff --git a/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java b/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java new file mode 100644 index 0000000000..1b6f2e34d9 --- /dev/null +++ b/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2019 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. + */ +package com.android.quickstep; + +import static android.view.View.ALPHA; + +import static com.android.quickstep.TaskAdapter.CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT; +import static com.android.quickstep.views.TaskItemView.CONTENT_TRANSITION_PROGRESS; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView.ViewHolder; +import androidx.recyclerview.widget.SimpleItemAnimator; + +import com.android.quickstep.views.TaskItemView; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + * An item animator that is only set and used for the transition from the empty loading UI to + * the filled task content UI. The animation starts from the bottom to top, changing all valid + * empty item views to be filled and removing all extra empty views. + */ +public final class ContentFillItemAnimator extends SimpleItemAnimator { + + private static final class PendingAnimation { + ViewHolder viewHolder; + int animType; + + PendingAnimation(ViewHolder vh, int type) { + viewHolder = vh; + animType = type; + } + } + + private static final int ANIM_TYPE_REMOVE = 0; + private static final int ANIM_TYPE_CHANGE = 1; + + private static final int ITEM_BETWEEN_DELAY = 40; + private static final int ITEM_CHANGE_DURATION = 150; + private static final int ITEM_REMOVE_DURATION = 150; + + /** + * Animations that have been registered to occur together at the next call of + * {@link #runPendingAnimations()} but have not started. + */ + private final ArrayList mPendingAnims = new ArrayList<>(); + + /** + * Animations that have started and are running. + */ + private final ArrayList mRunningAnims = new ArrayList<>(); + + private Runnable mOnFinishRunnable; + + /** + * Set runnable to run after the content fill animation is fully completed. + * + * @param runnable runnable to run on end + */ + public void setOnAnimationFinishedRunnable(Runnable runnable) { + mOnFinishRunnable = runnable; + } + + @Override + public void setChangeDuration(long changeDuration) { + throw new UnsupportedOperationException("Cascading item animator cannot have animation " + + "duration changed."); + } + + @Override + public void setRemoveDuration(long removeDuration) { + throw new UnsupportedOperationException("Cascading item animator cannot have animation " + + "duration changed."); + } + + @Override + public boolean animateRemove(ViewHolder holder) { + PendingAnimation pendAnim = new PendingAnimation(holder, ANIM_TYPE_REMOVE); + mPendingAnims.add(pendAnim); + return true; + } + + private void animateRemoveImpl(ViewHolder holder, long startDelay) { + final View view = holder.itemView; + if (holder.itemView.getAlpha() == 0) { + // View is already visually removed. We can just get rid of it now. + view.setAlpha(1.0f); + dispatchRemoveFinished(holder); + dispatchFinishedWhenDone(); + return; + } + final ObjectAnimator anim = ObjectAnimator.ofFloat( + holder.itemView, ALPHA, holder.itemView.getAlpha(), 0.0f); + anim.setDuration(ITEM_REMOVE_DURATION).setStartDelay(startDelay); + anim.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + dispatchRemoveStarting(holder); + } + + @Override + public void onAnimationEnd(Animator animation) { + view.setAlpha(1); + dispatchRemoveFinished(holder); + mRunningAnims.remove(anim); + dispatchFinishedWhenDone(); + } + } + ); + anim.start(); + mRunningAnims.add(anim); + } + + @Override + public boolean animateAdd(ViewHolder holder) { + dispatchAddFinished(holder); + return false; + } + + @Override + public boolean animateMove(ViewHolder holder, int fromX, int fromY, int toX, + int toY) { + dispatchMoveFinished(holder); + return false; + } + + @Override + public boolean animateChange(ViewHolder oldHolder, + ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) { + // Only support changes where the holders are the same + if (oldHolder == newHolder) { + PendingAnimation pendAnim = new PendingAnimation(oldHolder, ANIM_TYPE_CHANGE); + mPendingAnims.add(pendAnim); + return true; + } + dispatchChangeFinished(oldHolder, true /* oldItem */); + dispatchChangeFinished(newHolder, false /* oldItem */); + return false; + } + + private void animateChangeImpl(ViewHolder viewHolder, long startDelay) { + TaskItemView itemView = (TaskItemView) viewHolder.itemView; + final ObjectAnimator anim = + ObjectAnimator.ofFloat(itemView, CONTENT_TRANSITION_PROGRESS, 0.0f, 1.0f); + anim.setDuration(ITEM_CHANGE_DURATION).setStartDelay(startDelay); + anim.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + dispatchChangeStarting(viewHolder, true /* oldItem */); + } + + @Override + public void onAnimationEnd(Animator animation) { + dispatchChangeFinished(viewHolder, true /* oldItem */); + mRunningAnims.remove(anim); + dispatchFinishedWhenDone(); + } + } + ); + anim.start(); + mRunningAnims.add(anim); + } + + @Override + public void runPendingAnimations() { + // Run animations bottom to top. + mPendingAnims.sort(Comparator.comparingInt(o -> -o.viewHolder.itemView.getBottom())); + int delay = 0; + while (!mPendingAnims.isEmpty()) { + PendingAnimation curAnim = mPendingAnims.remove(0); + ViewHolder vh = curAnim.viewHolder; + switch (curAnim.animType) { + case ANIM_TYPE_REMOVE: + animateRemoveImpl(vh, delay); + break; + case ANIM_TYPE_CHANGE: + animateChangeImpl(vh, delay); + break; + default: + break; + } + delay += ITEM_BETWEEN_DELAY; + } + } + + @Override + public void endAnimation(@NonNull ViewHolder item) { + for (int i = mPendingAnims.size() - 1; i >= 0; i--) { + PendingAnimation pendAnim = mPendingAnims.get(i); + if (pendAnim.viewHolder == item) { + mPendingAnims.remove(i); + switch (pendAnim.animType) { + case ANIM_TYPE_REMOVE: + dispatchRemoveFinished(item); + break; + case ANIM_TYPE_CHANGE: + dispatchChangeFinished(item, true /* oldItem */); + break; + default: + break; + } + } + } + dispatchFinishedWhenDone(); + } + + @Override + public void endAnimations() { + for (int i = mPendingAnims.size() - 1; i >= 0; i--) { + PendingAnimation pendAnim = mPendingAnims.get(i); + ViewHolder item = pendAnim.viewHolder; + switch (pendAnim.animType) { + case ANIM_TYPE_REMOVE: + dispatchRemoveFinished(item); + break; + case ANIM_TYPE_CHANGE: + dispatchChangeFinished(item, true /* oldItem */); + break; + default: + break; + } + mPendingAnims.remove(i); + } + for (int i = 0; i < mRunningAnims.size(); i++) { + ObjectAnimator anim = mRunningAnims.get(i); + anim.end(); + } + dispatchAnimationsFinished(); + } + + @Override + public boolean isRunning() { + return !mPendingAnims.isEmpty() || !mRunningAnims.isEmpty(); + } + + @Override + public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder, + @NonNull List payloads) { + if (!payloads.isEmpty() + && (int) payloads.get(0) == CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT) { + return true; + } + return super.canReuseUpdatedViewHolder(viewHolder, payloads); + } + + private void dispatchFinishedWhenDone() { + if (!isRunning()) { + dispatchAnimationsFinished(); + if (mOnFinishRunnable != null) { + mOnFinishRunnable.run(); + } + } + } +} diff --git a/go/quickstep/src/com/android/quickstep/TaskAdapter.java b/go/quickstep/src/com/android/quickstep/TaskAdapter.java index 02cbf4e010..5e0e8ff8b1 100644 --- a/go/quickstep/src/com/android/quickstep/TaskAdapter.java +++ b/go/quickstep/src/com/android/quickstep/TaskAdapter.java @@ -34,6 +34,8 @@ import java.util.Objects; */ public final class TaskAdapter extends Adapter { + public static final int CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT = 0; + private static final int MAX_TASKS_TO_DISPLAY = 6; private static final String TAG = "TaskAdapter"; private final TaskListLoader mLoader; @@ -71,6 +73,28 @@ public final class TaskAdapter extends Adapter { @Override public void onBindViewHolder(TaskHolder holder, int position) { + onBindViewHolderInternal(holder, position, false /* willAnimate */); + } + + @Override + public void onBindViewHolder(@NonNull TaskHolder holder, int position, + @NonNull List payloads) { + if (payloads.isEmpty()) { + super.onBindViewHolder(holder, position, payloads); + return; + } + int changeType = (int) payloads.get(0); + if (changeType == CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT) { + // Bind in preparation for animation + onBindViewHolderInternal(holder, position, true /* willAnimate */); + } else { + throw new IllegalArgumentException("Payload content is not a valid change event type: " + + changeType); + } + } + + private void onBindViewHolderInternal(@NonNull TaskHolder holder, int position, + boolean willAnimate) { if (mIsShowingLoadingUi) { holder.bindEmptyUi(); return; @@ -81,7 +105,7 @@ public final class TaskAdapter extends Adapter { return; } Task task = tasks.get(position); - holder.bindTask(task, false /* willAnimate */); + holder.bindTask(task, willAnimate /* willAnimate */); mLoader.loadTaskIconAndLabel(task, () -> { // Ensure holder still has the same task. if (Objects.equals(task, holder.getTask())) { @@ -96,13 +120,6 @@ 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 int getItemCount() { if (mIsShowingLoadingUi) { diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java index 59755bcb39..41f25105ca 100644 --- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java +++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java @@ -17,6 +17,8 @@ package com.android.quickstep.views; import static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL; +import static com.android.quickstep.TaskAdapter.CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; @@ -34,6 +36,7 @@ import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -41,6 +44,7 @@ import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver; import androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener; import com.android.launcher3.R; +import com.android.quickstep.ContentFillItemAnimator; import com.android.quickstep.RecentsToActivityHelper; import com.android.quickstep.TaskActionController; import com.android.quickstep.TaskAdapter; @@ -89,6 +93,9 @@ public final class IconRecentsView extends FrameLayout { private final TaskListLoader mTaskLoader; private final TaskAdapter mTaskAdapter; private final TaskActionController mTaskActionController; + private final DefaultItemAnimator mDefaultItemAnimator = new DefaultItemAnimator(); + private final ContentFillItemAnimator mLoadingContentItemAnimator = + new ContentFillItemAnimator(); private RecentsToActivityHelper mActivityHelper; private RecyclerView mTaskRecyclerView; @@ -134,6 +141,9 @@ public final class IconRecentsView extends FrameLayout { @Override public void onChildViewDetachedFromWindow(@NonNull View view) { } }); + mTaskRecyclerView.setItemAnimator(mDefaultItemAnimator); + mLoadingContentItemAnimator.setOnAnimationFinishedRunnable( + () -> mTaskRecyclerView.setItemAnimator(new DefaultItemAnimator())); mEmptyView = findViewById(R.id.recent_task_empty_view); mContentView = findViewById(R.id.recent_task_content_view); @@ -186,9 +196,19 @@ public final class IconRecentsView extends FrameLayout { mTaskAdapter.setIsShowingLoadingUi(true); mTaskAdapter.notifyDataSetChanged(); mTaskLoader.loadTaskList(tasks -> { + int numEmptyItems = mTaskAdapter.getItemCount(); mTaskAdapter.setIsShowingLoadingUi(false); - // TODO: Animate the loading UI out and the loaded data in. - mTaskAdapter.notifyDataSetChanged(); + int numActualItems = mTaskAdapter.getItemCount(); + if (numEmptyItems < numActualItems) { + throw new IllegalStateException("There are less empty item views than the number " + + "of items to animate to."); + } + // Set item animator for content filling animation. The item animator will switch back + // to the default on completion. + mTaskRecyclerView.setItemAnimator(mLoadingContentItemAnimator); + mTaskAdapter.notifyItemRangeRemoved(numActualItems, numEmptyItems - numActualItems); + mTaskAdapter.notifyItemRangeChanged( + 0, numActualItems, CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT); }); } From e47543d03f4eb0df5532e7dcc99492f8a3d76432 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 12 Apr 2019 15:41:49 -0700 Subject: [PATCH 08/25] Update Go recents visibility based off adapter View visibility should be based off adapter since that's what the layout items are based off of. Bug: 130440957 Test: Loading UI is shown when going to recents Go Change-Id: I1f167553b6fdce757865c739b9793b63a20e7f57 (cherry picked from commit f1a47a0fe6e3b6f2071f8a30ac24d7522040d76c) --- .../src/com/android/quickstep/views/IconRecentsView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java index 41f25105ca..47244068cd 100644 --- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java +++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java @@ -334,7 +334,7 @@ public final class IconRecentsView extends FrameLayout { * of tasks. */ private void updateContentViewVisibility() { - int taskListSize = mTaskLoader.getCurrentTaskList().size(); + int taskListSize = mTaskAdapter.getItemCount(); if (mEmptyView.getVisibility() != VISIBLE && taskListSize == 0) { crossfadeViews(mEmptyView, mContentView); mActivityHelper.leaveRecents(); From f17917fab3ce44e89a5ba3aafda8987a80b86f62 Mon Sep 17 00:00:00 2001 From: Kevin Date: Mon, 15 Apr 2019 16:21:59 -0700 Subject: [PATCH 09/25] Change layout size to be dependent on device size This CL changes the layout sizes to be dependent on device height as opposed to static values so that it works on different decides out of the box. Bug: 114136250 Test: Tested how layout looks on marlin_svelte and walleye Change-Id: Ie000bc797d7dd2e38cd705d54f3e09c79e1a2176 (cherry picked from c06522c82661501cd09c305f51f0e2b07e904563) --- .../res/layout/icon_recents_root_view.xml | 5 +- go/quickstep/res/layout/task_item_view.xml | 15 ++--- go/quickstep/res/values/dimens.xml | 26 -------- .../com/android/quickstep/TaskAdapter.java | 17 ++++- .../quickstep/views/IconRecentsView.java | 20 +++++- .../android/quickstep/views/TaskItemView.java | 32 +++++++++ .../quickstep/views/TaskLayoutUtils.java | 66 +++++++++++++++++++ 7 files changed, 141 insertions(+), 40 deletions(-) delete mode 100644 go/quickstep/res/values/dimens.xml create mode 100644 go/quickstep/src/com/android/quickstep/views/TaskLayoutUtils.java diff --git a/go/quickstep/res/layout/icon_recents_root_view.xml b/go/quickstep/res/layout/icon_recents_root_view.xml index fddb1d347e..630088236a 100644 --- a/go/quickstep/res/layout/icon_recents_root_view.xml +++ b/go/quickstep/res/layout/icon_recents_root_view.xml @@ -33,10 +33,9 @@ android:scrollbars="none"/> diff --git a/go/quickstep/res/layout/icon_recents_root_view.xml b/go/quickstep/res/layout/icon_recents_root_view.xml index 6dc293f748..6fb7e19d13 100644 --- a/go/quickstep/res/layout/icon_recents_root_view.xml +++ b/go/quickstep/res/layout/icon_recents_root_view.xml @@ -19,29 +19,11 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> - - -