diff --git a/quickstep/res/drawable/ic_all_set.xml b/quickstep/res/drawable/ic_all_set.xml index a6852aa78e..656c59669c 100644 --- a/quickstep/res/drawable/ic_all_set.xml +++ b/quickstep/res/drawable/ic_all_set.xml @@ -15,10 +15,10 @@ --> + android:viewportHeight="42"> diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml index d61a8952a7..dfa17d6cc4 100644 --- a/quickstep/res/layout/taskbar.xml +++ b/quickstep/res/layout/taskbar.xml @@ -15,6 +15,7 @@ --> + + \ No newline at end of file diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index d8899a6e8e..4f62b34a5f 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -153,4 +153,7 @@ 16dp 16dp 48dp + 24dp + 220dp + 6dp diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index 66ccee6aea..2d1e304325 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -497,8 +497,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener view -> viewsToAnimate.add(((CellLayout) view).getShortcutsAndWidgets())); viewsToAnimate.add(mLauncher.getHotseat()); - // Add QSB - viewsToAnimate.add(mLauncher.findViewById(R.id.search_container_all_apps)); viewsToAnimate.forEach(view -> { view.setLayerType(View.LAYER_TYPE_HARDWARE, null); diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java index 12ac0f5692..06fd66021d 100644 --- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java @@ -50,21 +50,23 @@ public class LauncherTaskbarUIController extends TaskbarUIController { private final TaskbarHotseatController mHotseatController; private final TaskbarActivityContext mContext; - final TaskbarDragLayer mTaskbarDragLayer; - final TaskbarView mTaskbarView; + private final TaskbarDragLayer mTaskbarDragLayer; + private final TaskbarView mTaskbarView; private final AnimatedFloat mIconAlignmentForResumedState = new AnimatedFloat(this::onIconAlignmentRatioChanged); private final AnimatedFloat mIconAlignmentForGestureState = new AnimatedFloat(this::onIconAlignmentRatioChanged); + // Initialized in init. + private TaskbarControllers mControllers; private AnimatedFloat mTaskbarBackgroundAlpha; private AlphaProperty mIconAlphaForHome; - private boolean mIsAnimatingToLauncher; + private boolean mIsAnimatingToLauncherViaResume; + private boolean mIsAnimatingToLauncherViaGesture; private TaskbarKeyguardController mKeyguardController; private LauncherState mTargetStateOverride = null; - private TaskbarControllers mControllers; public LauncherTaskbarUIController( BaseQuickstepLauncher launcher, TaskbarActivityContext context) { @@ -80,13 +82,14 @@ public class LauncherTaskbarUIController extends TaskbarUIController { @Override protected void init(TaskbarControllers taskbarControllers) { - mTaskbarBackgroundAlpha = taskbarControllers.taskbarDragLayerController - .getTaskbarBackgroundAlpha(); - MultiValueAlpha taskbarIconAlpha = taskbarControllers.taskbarViewController - .getTaskbarIconAlpha(); - mIconAlphaForHome = taskbarIconAlpha.getProperty(ALPHA_INDEX_HOME); mControllers = taskbarControllers; + mTaskbarBackgroundAlpha = mControllers.taskbarDragLayerController + .getTaskbarBackgroundAlpha(); + + MultiValueAlpha taskbarIconAlpha = mControllers.taskbarViewController.getTaskbarIconAlpha(); + mIconAlphaForHome = taskbarIconAlpha.getProperty(ALPHA_INDEX_HOME); + mHotseatController.init(); mLauncher.setTaskbarUIController(this); mKeyguardController = taskbarControllers.taskbarKeyguardController; @@ -109,19 +112,17 @@ public class LauncherTaskbarUIController extends TaskbarUIController { @Override protected boolean isTaskbarTouchable() { - return !mIsAnimatingToLauncher && mTargetStateOverride == null; + return !isAnimatingToLauncher() && !mControllers.taskbarStashController.isStashed(); + } + + private boolean isAnimatingToLauncher() { + return mIsAnimatingToLauncherViaResume || mIsAnimatingToLauncherViaGesture; } @Override protected void updateContentInsets(Rect outContentInsets) { - // TaskbarDragLayer provides insets to other apps based on contentInsets. These - // insets should stay consistent even if we expand TaskbarDragLayer's bounds, e.g. - // to show a floating view like Folder. Thus, we set the contentInsets to be where - // mTaskbarView is, since its position never changes and insets rather than overlays. - outContentInsets.left = mTaskbarView.getLeft(); - outContentInsets.top = mTaskbarView.getTop(); - outContentInsets.right = mTaskbarDragLayer.getWidth() - mTaskbarView.getRight(); - outContentInsets.bottom = mTaskbarDragLayer.getHeight() - mTaskbarView.getBottom(); + int contentHeight = mControllers.taskbarStashController.getContentHeight(); + outContentInsets.top = mTaskbarDragLayer.getHeight() - contentHeight; } /** @@ -137,13 +138,20 @@ public class LauncherTaskbarUIController extends TaskbarUIController { } } + long duration = QuickstepTransitionManager.CONTENT_ALPHA_DURATION; ObjectAnimator anim = mIconAlignmentForResumedState.animateToValue( getCurrentIconAlignmentRatio(), isResumed ? 1 : 0) - .setDuration(QuickstepTransitionManager.CONTENT_ALPHA_DURATION); + .setDuration(duration); - anim.addListener(AnimatorListeners.forEndCallback(() -> mIsAnimatingToLauncher = false)); + anim.addListener(AnimatorListeners.forEndCallback( + () -> mIsAnimatingToLauncherViaResume = false)); anim.start(); - mIsAnimatingToLauncher = isResumed; + mIsAnimatingToLauncherViaResume = isResumed; + + if (!isResumed) { + TaskbarStashController stashController = mControllers.taskbarStashController; + stashController.animateToIsStashed(stashController.isStashedInApp(), duration); + } } /** @@ -155,36 +163,48 @@ public class LauncherTaskbarUIController extends TaskbarUIController { public Animator createAnimToLauncher(@NonNull LauncherState toState, @NonNull RecentsAnimationCallbacks callbacks, long duration) { + TaskbarStashController stashController = mControllers.taskbarStashController; ObjectAnimator animator = mIconAlignmentForGestureState - .animateToValue(mIconAlignmentForGestureState.value, 1) + .animateToValue(1) .setDuration(duration); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mTargetStateOverride = null; + animator.removeListener(this); } @Override public void onAnimationStart(Animator animation) { mTargetStateOverride = toState; + mIsAnimatingToLauncherViaGesture = true; + // TODO: base this on launcher state + stashController.animateToIsStashed(false, duration); } }); callbacks.addListener(new RecentsAnimationListener() { @Override public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) { - endGestureStateOverride(); + endGestureStateOverride(true); } @Override public void onRecentsAnimationFinished(RecentsAnimationController controller) { - endGestureStateOverride(); + endGestureStateOverride(!controller.getFinishTargetIsLauncher()); } - private void endGestureStateOverride() { + private void endGestureStateOverride(boolean finishedToApp) { callbacks.removeListener(this); + mIsAnimatingToLauncherViaGesture = false; + mIconAlignmentForGestureState - .animateToValue(mIconAlignmentForGestureState.value, 0) + .animateToValue(0) .start(); + + if (finishedToApp) { + // We only need this for the exiting live tile case. + stashController.animateToIsStashed(stashController.isStashedInApp()); + } } }); return animator; @@ -215,6 +235,11 @@ public class LauncherTaskbarUIController extends TaskbarUIController { } } + @Override + public boolean onLongPressToUnstashTaskbar() { + return mControllers.taskbarStashController.onLongPressToUnstashTaskbar(); + } + /** * Should be called when one or more items in the Hotseat have changed. */ diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java new file mode 100644 index 0000000000..8c14ff6d16 --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2021 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.launcher3.taskbar; + +import android.animation.Animator; +import android.content.res.Resources; +import android.graphics.Outline; +import android.graphics.Rect; +import android.view.View; +import android.view.ViewOutlineProvider; + +import androidx.annotation.Nullable; + +import com.android.launcher3.R; +import com.android.launcher3.anim.RevealOutlineAnimation; +import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; +import com.android.quickstep.AnimatedFloat; + +/** + * Handles properties/data collection, then passes the results to our stashed handle View to render. + */ +public class StashedHandleViewController { + + private final TaskbarActivityContext mActivity; + private final View mStashedHandleView; + private final int mStashedHandleWidth; + private final int mStashedHandleHeight; + private final AnimatedFloat mTaskbarStashedHandleAlpha = new AnimatedFloat( + this::updateStashedHandleAlpha); + + // Initialized in init. + private TaskbarControllers mControllers; + + // The bounds we want to clip to in the settled state when showing the stashed handle. + private final Rect mStashedHandleBounds = new Rect(); + private float mStashedHandleRadius; + + private boolean mIsAtStashedRevealBounds = true; + + public StashedHandleViewController(TaskbarActivityContext activity, View stashedHandleView) { + mActivity = activity; + mStashedHandleView = stashedHandleView; + final Resources resources = mActivity.getResources(); + mStashedHandleWidth = resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width); + mStashedHandleHeight = resources.getDimensionPixelSize( + R.dimen.taskbar_stashed_handle_height); + } + + public void init(TaskbarControllers controllers) { + mControllers = controllers; + mStashedHandleView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize; + + updateStashedHandleAlpha(); + + final int stashedTaskbarHeight = mControllers.taskbarStashController.getStashedHeight(); + mStashedHandleView.setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + final int stashedCenterX = view.getWidth() / 2; + final int stashedCenterY = view.getHeight() - stashedTaskbarHeight / 2; + mStashedHandleBounds.set( + stashedCenterX - mStashedHandleWidth / 2, + stashedCenterY - mStashedHandleHeight / 2, + stashedCenterX + mStashedHandleWidth / 2, + stashedCenterY + mStashedHandleHeight / 2); + mStashedHandleRadius = view.getHeight() / 2f; + outline.setRoundRect(mStashedHandleBounds, mStashedHandleRadius); + } + }); + } + + public AnimatedFloat getStashedHandleAlpha() { + return mTaskbarStashedHandleAlpha; + } + + /** + * Creates and returns a {@link RevealOutlineAnimation} Animator that updates the stashed handle + * shape and size. When stashed, the shape is a thin rounded pill. When unstashed, the shape + * morphs into the size of where the taskbar icons will be. + */ + public @Nullable Animator createRevealAnimToIsStashed(boolean isStashed) { + if (mIsAtStashedRevealBounds == isStashed) { + return null; + } + mIsAtStashedRevealBounds = isStashed; + final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider( + mStashedHandleRadius, mStashedHandleRadius, + mControllers.taskbarViewController.getIconLayoutBounds(), mStashedHandleBounds); + return handleRevealProvider.createRevealAnimator(mStashedHandleView, !isStashed); + } + + protected void updateStashedHandleAlpha() { + mStashedHandleView.setAlpha(mTaskbarStashedHandleAlpha.value); + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index 41426108e5..f4703d3a99 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -115,6 +115,7 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ R.layout.taskbar, null, false); TaskbarView taskbarView = mDragLayer.findViewById(R.id.taskbar_view); FrameLayout navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view); + View stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle); // Construct controllers. mControllers = new TaskbarControllers(this, @@ -125,7 +126,9 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ R.color.popup_color_primary_light), new TaskbarDragLayerController(this, mDragLayer), new TaskbarViewController(this, taskbarView), - new TaskbarKeyguardController(this)); + new TaskbarKeyguardController(this), + new StashedHandleViewController(this, stashedHandleView), + new TaskbarStashController(this)); Display display = windowContext.getDisplay(); Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java index c48c28b9e5..8279a470f0 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java @@ -32,6 +32,8 @@ public class TaskbarControllers { public final TaskbarDragLayerController taskbarDragLayerController; public final TaskbarViewController taskbarViewController; public final TaskbarKeyguardController taskbarKeyguardController; + public final StashedHandleViewController stashedHandleViewController; + public final TaskbarStashController taskbarStashController; /** Do not store this controller, as it may change at runtime. */ @NonNull public TaskbarUIController uiController = TaskbarUIController.DEFAULT; @@ -43,7 +45,9 @@ public class TaskbarControllers { RotationButtonController rotationButtonController, TaskbarDragLayerController taskbarDragLayerController, TaskbarViewController taskbarViewController, - TaskbarKeyguardController taskbarKeyguardController) { + TaskbarKeyguardController taskbarKeyguardController, + StashedHandleViewController stashedHandleViewController, + TaskbarStashController taskbarStashController) { this.taskbarActivityContext = taskbarActivityContext; this.taskbarDragController = taskbarDragController; this.navButtonController = navButtonController; @@ -52,6 +56,8 @@ public class TaskbarControllers { this.taskbarDragLayerController = taskbarDragLayerController; this.taskbarViewController = taskbarViewController; this.taskbarKeyguardController = taskbarKeyguardController; + this.stashedHandleViewController = stashedHandleViewController; + this.taskbarStashController = taskbarStashController; } /** @@ -67,6 +73,8 @@ public class TaskbarControllers { taskbarDragLayerController.init(this); taskbarViewController.init(this); taskbarKeyguardController.init(navbarButtonsViewController); + stashedHandleViewController.init(this); + taskbarStashController.init(this); } /** diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java index ac121ab5fa..cd1baf726d 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java @@ -40,10 +40,11 @@ import com.android.systemui.shared.system.ViewTreeObserverWrapper.OnComputeInset public class TaskbarDragLayer extends BaseDragLayer { private final Paint mTaskbarBackgroundPaint; + private final OnComputeInsetsListener mTaskbarInsetsComputer = this::onComputeTaskbarInsets; private TaskbarDragLayerController.TaskbarDragLayerCallbacks mControllerCallbacks; - private final OnComputeInsetsListener mTaskbarInsetsComputer = this::onComputeTaskbarInsets; + private float mTaskbarBackgroundOffset; public TaskbarDragLayer(@NonNull Context context) { this(context, null); @@ -118,8 +119,10 @@ public class TaskbarDragLayer extends BaseDragLayer { @Override protected void dispatchDraw(Canvas canvas) { - canvas.drawRect(0, canvas.getHeight() - mControllerCallbacks.getTaskbarBackgroundHeight(), - canvas.getWidth(), canvas.getHeight(), mTaskbarBackgroundPaint); + float backgroundHeight = mControllerCallbacks.getTaskbarBackgroundHeight() + * (1f - mTaskbarBackgroundOffset); + canvas.drawRect(0, canvas.getHeight() - backgroundHeight, canvas.getWidth(), + canvas.getHeight(), mTaskbarBackgroundPaint); super.dispatchDraw(canvas); } @@ -132,6 +135,15 @@ public class TaskbarDragLayer extends BaseDragLayer { invalidate(); } + /** + * Sets the translation of the background color behind all the Taskbar contents. + * @param offset 0 is fully onscreen, 1 is fully offscreen. + */ + protected void setTaskbarBackgroundOffset(float offset) { + mTaskbarBackgroundOffset = offset; + invalidate(); + } + @Override public boolean dispatchTouchEvent(MotionEvent ev) { TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java index db5c387770..e15e9ffa71 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java @@ -39,6 +39,8 @@ public class TaskbarDragLayerController { // Alpha properties for taskbar background. private final AnimatedFloat mBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha); private final AnimatedFloat mBgNavbar = new AnimatedFloat(this::updateBackgroundAlpha); + // Translation property for taskbar background. + private final AnimatedFloat mBgOffset = new AnimatedFloat(this::updateBackgroundOffset); // Initialized in init. private TaskbarControllers mControllers; @@ -78,10 +80,18 @@ public class TaskbarDragLayerController { return mBgNavbar; } + public AnimatedFloat getTaskbarBackgroundOffset() { + return mBgOffset; + } + private void updateBackgroundAlpha() { mTaskbarDragLayer.setTaskbarBackgroundAlpha(Math.max(mBgNavbar.value, mBgTaskbar.value)); } + private void updateBackgroundOffset() { + mTaskbarDragLayer.setTaskbarBackgroundOffset(mBgOffset.value); + } + /** * Callbacks for {@link TaskbarDragLayer} to interact with its controller. */ diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java new file mode 100644 index 0000000000..57600d7356 --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2021 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.launcher3.taskbar; + +import static android.view.HapticFeedbackConstants.LONG_PRESS; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.annotation.Nullable; +import android.content.SharedPreferences; +import android.content.res.Resources; + +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; +import com.android.quickstep.AnimatedFloat; + +/** + * Coordinates between controllers such as TaskbarViewController and StashedHandleViewController to + * create a cohesive animation between stashed/unstashed states. + */ +public class TaskbarStashController { + + /** + * How long to stash/unstash when manually invoked via long press. + */ + private static final long TASKBAR_STASH_DURATION = 300; + + /** + * The scale TaskbarView animates to when being stashed. + */ + private static final float STASHED_TASKBAR_SCALE = 0.5f; + + /** + * The SharedPreferences key for whether user has manually stashed the taskbar. + */ + private static final String SHARED_PREFS_STASHED_KEY = "taskbar_is_stashed"; + + /** + * Whether taskbar should be stashed out of the box. + */ + private static final boolean DEFAULT_STASHED_PREF = false; + + private final TaskbarActivityContext mActivity; + private final SharedPreferences mPrefs; + private final int mStashedHeight; + private final int mUnstashedHeight; + + // Initialized in init. + private TaskbarControllers mControllers; + // Taskbar background properties. + private AnimatedFloat mTaskbarBackgroundOffset; + // TaskbarView icon properties. + private AlphaProperty mIconAlphaForStash; + private AnimatedFloat mIconScaleForStash; + private AnimatedFloat mIconTranslationYForStash; + // Stashed handle properties. + private AnimatedFloat mTaskbarStashedHandleAlpha; + + /** Whether the user has manually invoked taskbar stashing, which we persist. */ + private boolean mIsStashedInApp; + /** Whether we are currently visually stashed (might change based on launcher state). */ + private boolean mIsStashed = false; + + private @Nullable AnimatorSet mAnimator; + + public TaskbarStashController(TaskbarActivityContext activity) { + mActivity = activity; + mPrefs = Utilities.getPrefs(mActivity); + final Resources resources = mActivity.getResources(); + mStashedHeight = resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size); + mUnstashedHeight = mActivity.getDeviceProfile().taskbarSize; + } + + public void init(TaskbarControllers controllers) { + mControllers = controllers; + + TaskbarDragLayerController dragLayerController = controllers.taskbarDragLayerController; + mTaskbarBackgroundOffset = dragLayerController.getTaskbarBackgroundOffset(); + + TaskbarViewController taskbarViewController = controllers.taskbarViewController; + mIconAlphaForStash = taskbarViewController.getTaskbarIconAlpha().getProperty( + TaskbarViewController.ALPHA_INDEX_STASH); + mIconScaleForStash = taskbarViewController.getTaskbarIconScaleForStash(); + mIconTranslationYForStash = taskbarViewController.getTaskbarIconTranslationYForStash(); + + StashedHandleViewController stashedHandleController = + controllers.stashedHandleViewController; + mTaskbarStashedHandleAlpha = stashedHandleController.getStashedHandleAlpha(); + + mIsStashedInApp = supportsStashing() + && mPrefs.getBoolean(SHARED_PREFS_STASHED_KEY, DEFAULT_STASHED_PREF); + } + + /** + * Returns whether the user can manually stash the taskbar based on the current device state. + */ + private boolean supportsStashing() { + return !mActivity.isThreeButtonNav(); + } + + /** + * Returns whether the taskbar is currently visually stashed. + */ + public boolean isStashed() { + return mIsStashed; + } + + /** + * Returns whether the user has manually stashed the taskbar in apps. + */ + public boolean isStashedInApp() { + return mIsStashedInApp; + } + + public int getContentHeight() { + return isStashed() ? mStashedHeight : mUnstashedHeight; + } + + public int getStashedHeight() { + return mStashedHeight; + } + + /** + * Should be called when long pressing the nav region when taskbar is present. + * @return Whether taskbar was stashed and now is unstashed. + */ + public boolean onLongPressToUnstashTaskbar() { + if (!isStashed()) { + // We only listen for long press on the nav region to unstash the taskbar. To stash the + // taskbar, we use an OnLongClickListener on TaskbarView instead. + return false; + } + if (updateAndAnimateIsStashedInApp(false)) { + mControllers.taskbarActivityContext.getDragLayer().performHapticFeedback(LONG_PRESS); + return true; + } + return false; + } + + /** + * Updates whether we should stash the taskbar when in apps, and animates to the changed state. + * @return Whether we started an animation to either be newly stashed or unstashed. + */ + public boolean updateAndAnimateIsStashedInApp(boolean isStashedInApp) { + if (!supportsStashing()) { + return false; + } + if (mIsStashedInApp != isStashedInApp) { + boolean wasStashed = mIsStashedInApp; + mIsStashedInApp = isStashedInApp; + mPrefs.edit().putBoolean(SHARED_PREFS_STASHED_KEY, mIsStashedInApp).apply(); + boolean isStashed = mIsStashedInApp; + if (wasStashed != isStashed) { + createAnimToIsStashed(isStashed, TASKBAR_STASH_DURATION).start(); + return true; + } + } + return false; + } + + /** + * Starts an animation to the new stashed state with a default duration. + */ + public void animateToIsStashed(boolean isStashed) { + animateToIsStashed(isStashed, TASKBAR_STASH_DURATION); + } + + /** + * Starts an animation to the new stashed state with the specified duration. + */ + public void animateToIsStashed(boolean isStashed, long duration) { + createAnimToIsStashed(isStashed, duration).start(); + } + + private Animator createAnimToIsStashed(boolean isStashed, long duration) { + AnimatorSet fullLengthAnimatorSet = new AnimatorSet(); + // Not exactly half and may overlap. See [first|second]HalfDurationScale below. + AnimatorSet firstHalfAnimatorSet = new AnimatorSet(); + AnimatorSet secondHalfAnimatorSet = new AnimatorSet(); + + final float firstHalfDurationScale; + final float secondHalfDurationScale; + + if (isStashed) { + firstHalfDurationScale = 0.75f; + secondHalfDurationScale = 0.5f; + final float stashTranslation = (mUnstashedHeight - mStashedHeight) / 2f; + + fullLengthAnimatorSet.playTogether( + mTaskbarBackgroundOffset.animateToValue(1), + mIconTranslationYForStash.animateToValue(stashTranslation) + ); + firstHalfAnimatorSet.playTogether( + mIconAlphaForStash.animateToValue(0), + mIconScaleForStash.animateToValue(STASHED_TASKBAR_SCALE) + ); + secondHalfAnimatorSet.playTogether( + mTaskbarStashedHandleAlpha.animateToValue(1) + ); + } else { + firstHalfDurationScale = 0.5f; + secondHalfDurationScale = 0.75f; + + fullLengthAnimatorSet.playTogether( + mTaskbarBackgroundOffset.animateToValue(0), + mIconScaleForStash.animateToValue(1), + mIconTranslationYForStash.animateToValue(0) + ); + firstHalfAnimatorSet.playTogether( + mTaskbarStashedHandleAlpha.animateToValue(0) + ); + secondHalfAnimatorSet.playTogether( + mIconAlphaForStash.animateToValue(1) + ); + } + + Animator stashedHandleRevealAnim = mControllers.stashedHandleViewController + .createRevealAnimToIsStashed(isStashed); + if (stashedHandleRevealAnim != null) { + fullLengthAnimatorSet.play(stashedHandleRevealAnim); + } + + fullLengthAnimatorSet.setDuration(duration); + firstHalfAnimatorSet.setDuration((long) (duration * firstHalfDurationScale)); + secondHalfAnimatorSet.setDuration((long) (duration * secondHalfDurationScale)); + secondHalfAnimatorSet.setStartDelay((long) (duration * (1 - secondHalfDurationScale))); + + if (mAnimator != null) { + mAnimator.cancel(); + } + mAnimator = new AnimatorSet(); + mAnimator.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet, + secondHalfAnimatorSet); + mAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mIsStashed = isStashed; + } + + @Override + public void onAnimationEnd(Animator animation) { + mAnimator = null; + } + }); + return mAnimator; + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java index 260cedc706..6d0e3c6baf 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java @@ -33,4 +33,8 @@ public class TaskbarUIController { } protected void updateContentInsets(Rect outContentInsets) { } + + protected boolean onLongPressToUnstashTaskbar() { + return false; + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java index 7753f966dc..820d40af27 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java @@ -94,8 +94,10 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar protected void init(TaskbarViewController.TaskbarViewCallbacks callbacks) { mControllerCallbacks = callbacks; - mIconClickListener = mControllerCallbacks.getOnClickListener(); - mIconLongClickListener = mControllerCallbacks.getOnLongClickListener(); + mIconClickListener = mControllerCallbacks.getIconOnClickListener(); + mIconLongClickListener = mControllerCallbacks.getIconOnLongClickListener(); + + setOnLongClickListener(mControllerCallbacks.getBackgroundOnLongClickListener()); } private void removeAndRecycle(View view) { @@ -235,6 +237,10 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar return isShown() && mIconLayoutBounds.contains(xInOurCoordinates, yInOurCoorindates); } + public Rect getIconLayoutBounds() { + return mIconLayoutBounds; + } + // FolderIconParent implemented methods. @Override diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java index c7ac4a4f5b..50c26b30b2 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java @@ -17,8 +17,8 @@ package com.android.launcher3.taskbar; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; -import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.quickstep.AnimatedFloat.VALUE; import android.graphics.Rect; import android.view.View; @@ -28,6 +28,7 @@ import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.util.MultiValueAlpha; +import com.android.quickstep.AnimatedFloat; /** * Handles properties/data collection, then passes the results to TaskbarView to render. @@ -38,10 +39,16 @@ public class TaskbarViewController { public static final int ALPHA_INDEX_HOME = 0; public static final int ALPHA_INDEX_IME = 1; public static final int ALPHA_INDEX_KEYGUARD = 2; + public static final int ALPHA_INDEX_STASH = 3; private final TaskbarActivityContext mActivity; private final TaskbarView mTaskbarView; private final MultiValueAlpha mTaskbarIconAlpha; + private final AnimatedFloat mTaskbarIconScaleForStash = new AnimatedFloat(this::updateScale); + private final AnimatedFloat mTaskbarIconTranslationYForHome = new AnimatedFloat( + this::updateTranslationY); + private final AnimatedFloat mTaskbarIconTranslationYForStash = new AnimatedFloat( + this::updateTranslationY); // Initialized in init. private TaskbarControllers mControllers; @@ -54,7 +61,7 @@ public class TaskbarViewController { public TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView) { mActivity = activity; mTaskbarView = taskbarView; - mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, 3); + mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, 4); mTaskbarIconAlpha.setUpdateVisibility(true); } @@ -62,6 +69,8 @@ public class TaskbarViewController { mControllers = controllers; mTaskbarView.init(new TaskbarViewCallbacks()); mTaskbarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize; + + mTaskbarIconScaleForStash.updateValue(1f); } public boolean areIconsVisible() { @@ -86,6 +95,32 @@ public class TaskbarViewController { mTaskbarView.setClickAndLongClickListenersForIcon(icon); } + public Rect getIconLayoutBounds() { + return mTaskbarView.getIconLayoutBounds(); + } + + public AnimatedFloat getTaskbarIconScaleForStash() { + return mTaskbarIconScaleForStash; + } + + public AnimatedFloat getTaskbarIconTranslationYForStash() { + return mTaskbarIconTranslationYForStash; + } + + /** + * Applies scale properties for the entire TaskbarView (rather than individual icons). + */ + private void updateScale() { + float scale = mTaskbarIconScaleForStash.value; + mTaskbarView.setScaleX(scale); + mTaskbarView.setScaleY(scale); + } + + private void updateTranslationY() { + mTaskbarView.setTranslationY(mTaskbarIconTranslationYForHome.value + + mTaskbarIconTranslationYForStash.value); + } + /** * Sets the taskbar icon alignment relative to Launcher hotseat icons * @param alignmentRatio [0, 1] @@ -116,7 +151,7 @@ public class TaskbarViewController { / launcherDp.numShownHotseatIcons; int offsetY = launcherDp.getTaskbarOffsetY(); - setter.setFloat(mTaskbarView, VIEW_TRANSLATE_Y, -offsetY, LINEAR); + setter.setFloat(mTaskbarIconTranslationYForHome, VALUE, -offsetY, LINEAR); int collapsedHeight = mActivity.getDeviceProfile().taskbarSize; int expandedHeight = collapsedHeight + offsetY; @@ -144,12 +179,16 @@ public class TaskbarViewController { * Callbacks for {@link TaskbarView} to interact with its controller. */ public class TaskbarViewCallbacks { - public View.OnClickListener getOnClickListener() { + public View.OnClickListener getIconOnClickListener() { return mActivity::onTaskbarIconClicked; } - public View.OnLongClickListener getOnLongClickListener() { + public View.OnLongClickListener getIconOnLongClickListener() { return mControllers.taskbarDragController::startDragOnLongClick; } + + public View.OnLongClickListener getBackgroundOnLongClickListener() { + return view -> mControllers.taskbarStashController.updateAndAnimateIsStashedInApp(true); + } } } diff --git a/quickstep/src/com/android/quickstep/AnimatedFloat.java b/quickstep/src/com/android/quickstep/AnimatedFloat.java index f7e8781573..95c871099b 100644 --- a/quickstep/src/com/android/quickstep/AnimatedFloat.java +++ b/quickstep/src/com/android/quickstep/AnimatedFloat.java @@ -53,6 +53,16 @@ public class AnimatedFloat { mUpdateCallback = updateCallback; } + /** + * Returns an animation from the current value to the given value. + */ + public ObjectAnimator animateToValue(float end) { + return animateToValue(value, end); + } + + /** + * Returns an animation from the given start value to the given end value. + */ public ObjectAnimator animateToValue(float start, float end) { cancelAnimation(); mValueAnimator = ObjectAnimator.ofFloat(this, VALUE, start, end); diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java index 2699b0795b..1412b1add6 100644 --- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java +++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java @@ -363,6 +363,14 @@ public abstract class BaseActivityInterface TYPE_NO_OP; diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java index 799a4c2516..09474a1ac2 100644 --- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java +++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java @@ -302,6 +302,15 @@ public final class LauncherActivityInterface extends } } + @Override + public boolean onLongPressToUnstashTaskbar() { + LauncherTaskbarUIController taskbarController = getTaskbarController(); + if (taskbarController == null) { + return super.onLongPressToUnstashTaskbar(); + } + return taskbarController.onLongPressToUnstashTaskbar(); + } + @Override protected int getOverviewScrimColorForState(BaseQuickstepLauncher launcher, LauncherState state) { diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java index 0ebe13be8d..9e69ef9b52 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java @@ -46,6 +46,8 @@ public class RecentsAnimationController { private boolean mUseLauncherSysBarFlags = false; private boolean mSplitScreenMinimized = false; private boolean mFinishRequested = false; + // Only valid when mFinishRequested == true. + private boolean mFinishTargetIsLauncher; private RunnableList mPendingFinishCallbacks = new RunnableList(); public RecentsAnimationController(RecentsAnimationControllerCompat controller, @@ -145,6 +147,7 @@ public class RecentsAnimationController { // Finish not yet requested mFinishRequested = true; + mFinishTargetIsLauncher = toRecents; mOnFinishedListener.accept(this); mPendingFinishCallbacks.add(callback); UI_HELPER_EXECUTOR.execute(() -> { @@ -201,4 +204,12 @@ public class RecentsAnimationController { public RecentsAnimationControllerCompat getController() { return mController; } + + /** + * RecentsAnimationListeners can check this in onRecentsAnimationFinished() to determine whether + * the animation was finished to launcher vs an app. + */ + public boolean getFinishTargetIsLauncher() { + return mFinishTargetIsLauncher; + } } diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index 47ca3d2024..37cb979a0d 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -95,6 +95,7 @@ import com.android.quickstep.inputconsumers.OverviewWithoutFocusInputConsumer; import com.android.quickstep.inputconsumers.ResetGestureInputConsumer; import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer; import com.android.quickstep.inputconsumers.SysUiOverlayInputConsumer; +import com.android.quickstep.inputconsumers.TaskbarStashInputConsumer; import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.util.AssistantUtilities; import com.android.quickstep.util.ProtoTracer; @@ -673,6 +674,14 @@ public class TouchInteractionService extends Service implements PluginListener diff --git a/res/layout/widgets_table_container.xml b/res/layout/widgets_table_container.xml index 4f70a05374..fac21fdcf0 100644 --- a/res/layout/widgets_table_container.xml +++ b/res/layout/widgets_table_container.xml @@ -18,5 +18,4 @@ android:id="@+id/widgets_table" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginHorizontal="16dp" - android:layout_marginBottom="@dimen/widget_list_entry_bottom_margin"/> + android:layout_marginHorizontal="16dp"/> diff --git a/res/values/dimens.xml b/res/values/dimens.xml index f4a0a3df6e..f5c08c50b7 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -147,7 +147,7 @@ 4dp 20dp - 2dp + 2dp 0.5dp 1dp diff --git a/res/values/id.xml b/res/values/id.xml index d941716f0a..0e2dff0f72 100644 --- a/res/values/id.xml +++ b/res/values/id.xml @@ -16,6 +16,7 @@ --> + diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java index 7d8b82a8eb..85ca280ba2 100644 --- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java +++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java @@ -278,12 +278,19 @@ public class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateL } } - public void dispatchOnStart() { + public AnimatorPlaybackController dispatchOnStart() { callListenerCommandRecursively(mAnim, AnimatorListener::onAnimationStart); + return this; } - public void dispatchOnCancel() { + public AnimatorPlaybackController dispatchOnCancel() { callListenerCommandRecursively(mAnim, AnimatorListener::onAnimationCancel); + return this; + } + + public AnimatorPlaybackController dispatchOnEnd() { + callListenerCommandRecursively(mAnim, AnimatorListener::onAnimationEnd); + return this; } public void dispatchSetInterpolator(TimeInterpolator interpolator) { @@ -328,7 +335,7 @@ public class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateL public void onAnimationSuccess(Animator animator) { // We wait for the spring (if any) to finish running before completing the end callback. if (!mDispatched) { - callListenerCommandRecursively(mAnim, AnimatorListener::onAnimationEnd); + dispatchOnEnd(); if (mEndAction != null) { mEndAction.run(); } diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java index 3abcc2b826..7091d2b143 100644 --- a/src/com/android/launcher3/model/data/ItemInfo.java +++ b/src/com/android/launcher3/model/data/ItemInfo.java @@ -168,6 +168,8 @@ public class ItemInfo { cellY = info.cellY; spanX = info.spanX; spanY = info.spanY; + minSpanX = info.minSpanX; + minSpanY = info.minSpanY; rank = info.rank; screenId = info.screenId; itemType = info.itemType; diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java index dbd9c281a2..915e140aaf 100644 --- a/src/com/android/launcher3/settings/SettingsActivity.java +++ b/src/com/android/launcher3/settings/SettingsActivity.java @@ -113,12 +113,6 @@ public class SettingsActivity extends FragmentActivity Utilities.getPrefs(getApplicationContext()).registerOnSharedPreferenceChangeListener(this); } - @Override - protected void onStop() { - super.onStop(); - finish(); - } - /** * Obtains the preference fragment to instantiate in this activity. * diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java index 13d6568f78..b34af975bd 100644 --- a/src/com/android/launcher3/statemanager/StateManager.java +++ b/src/com/android/launcher3/statemanager/StateManager.java @@ -209,7 +209,7 @@ public class StateManager> { // Cancel the current animation. This will reset mState to mCurrentStableState, so store it. STATE_TYPE fromState = mState; - mConfig.reset(); + cancelAnimation(); if (!animated) { mAtomicAnimationFactory.cancelAllStateElementAnimation(); @@ -303,7 +303,7 @@ public class StateManager> { public AnimatorPlaybackController createAnimationToNewWorkspace(STATE_TYPE state, StateAnimationConfig config) { config.userControlled = true; - mConfig.reset(); + cancelAnimation(); config.copyTo(mConfig); mConfig.playbackController = createAnimationToNewWorkspaceInternal(state) .createPlaybackController(); @@ -393,6 +393,11 @@ public class StateManager> { */ public void cancelAnimation() { mConfig.reset(); + // It could happen that a new animation is set as a result of an endListener on the + // existing animation. + while (mConfig.currentAnimation != null || mConfig.playbackController != null) { + mConfig.reset(); + } } public void setCurrentUserControlledAnimation(AnimatorPlaybackController controller) { @@ -508,14 +513,19 @@ public class StateManager> { * Cancels the current animation and resets config variables. */ public void reset() { + AnimatorSet anim = currentAnimation; + AnimatorPlaybackController pc = playbackController; + DEFAULT.copyTo(this); targetState = null; + currentAnimation = null; + playbackController = null; + changeId++; - if (playbackController != null) { - playbackController.getAnimationPlayer().cancel(); - playbackController.dispatchOnCancel(); - } else if (currentAnimation != null) { - AnimatorSet anim = currentAnimation; + if (pc != null) { + pc.getAnimationPlayer().cancel(); + pc.dispatchOnCancel().dispatchOnEnd(); + } else if (anim != null) { anim.setDuration(0); if (!anim.isStarted()) { // If the animation is not started the listeners do not get notified, @@ -525,10 +535,6 @@ public class StateManager> { } anim.cancel(); } - - currentAnimation = null; - playbackController = null; - changeId++; } @Override diff --git a/src/com/android/launcher3/util/MultiValueAlpha.java b/src/com/android/launcher3/util/MultiValueAlpha.java index 5be95292b4..8591872cdc 100644 --- a/src/com/android/launcher3/util/MultiValueAlpha.java +++ b/src/com/android/launcher3/util/MultiValueAlpha.java @@ -16,6 +16,8 @@ package com.android.launcher3.util; +import android.animation.Animator; +import android.animation.ObjectAnimator; import android.util.FloatProperty; import android.view.View; @@ -121,5 +123,12 @@ public class MultiValueAlpha { public String toString() { return Float.toString(mValue); } + + /** + * Creates and returns an Animator from the current value to the given value. + */ + public Animator animateToValue(float value) { + return ObjectAnimator.ofFloat(this, VALUE, value); + } } } diff --git a/src/com/android/launcher3/widget/model/WidgetsListSearchHeaderEntry.java b/src/com/android/launcher3/widget/model/WidgetsListSearchHeaderEntry.java index c0f89bcbcc..055e4ecbe6 100644 --- a/src/com/android/launcher3/widget/model/WidgetsListSearchHeaderEntry.java +++ b/src/com/android/launcher3/widget/model/WidgetsListSearchHeaderEntry.java @@ -60,7 +60,7 @@ public final class WidgetsListSearchHeaderEntry extends WidgetsListBaseEntry WidgetsListSearchHeaderEntry otherEntry = (WidgetsListSearchHeaderEntry) obj; return mWidgets.equals(otherEntry.mWidgets) && mPkgItem.equals(otherEntry.mPkgItem) && mTitleSectionName.equals(otherEntry.mTitleSectionName) - && mIsWidgetListShown; + && mIsWidgetListShown == otherEntry.mIsWidgetListShown; } /** Returns a copy of this {@link WidgetsListSearchHeaderEntry} with the widget list shown. */ diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java index 5d9adf0e71..b668c90031 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java +++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java @@ -18,6 +18,7 @@ package com.android.launcher3.widget.picker; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_APP_EXPANDED; import android.content.Context; +import android.graphics.Rect; import android.os.Process; import android.util.Log; import android.util.Size; @@ -34,6 +35,7 @@ import androidx.annotation.Nullable; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView.Adapter; +import androidx.recyclerview.widget.RecyclerView.LayoutParams; import androidx.recyclerview.widget.RecyclerView.ViewHolder; import com.android.launcher3.BaseActivity; @@ -107,7 +109,8 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC @Nullable private Predicate mFilter = null; @Nullable private RecyclerView mRecyclerView; @Nullable private PackageUserKey mPendingClickHeader; - private int mShortcutPreviewPadding; + private final int mShortcutPreviewPadding; + private final int mSpacingBetweenEntries; private final WidgetPreviewLoadedCallback mPreviewLoadedCallback = ignored -> updateVisibleEntries(); @@ -141,11 +144,28 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC mShortcutPreviewPadding = 2 * context.getResources() .getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding); + mSpacingBetweenEntries = + context.getResources().getDimensionPixelSize(R.dimen.widget_list_entry_spacing); } @Override public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { mRecyclerView = recyclerView; + + mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() { + @Override + public void getItemOffsets( + @NonNull Rect outRect, + @NonNull View view, + @NonNull RecyclerView parent, + @NonNull RecyclerView.State state) { + super.getItemOffsets(outRect, view, parent, state); + int position = ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(); + boolean isHeader = + view.getTag(R.id.tag_widget_entry) instanceof WidgetsListBaseEntry.Header; + outRect.top += position > 0 && isHeader ? mSpacingBetweenEntries : 0; + } + }); } @Override @@ -329,7 +349,9 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC @Override public void onBindViewHolder(ViewHolder holder, int pos) { ViewHolderBinder viewHolderBinder = mViewHolderBinders.get(getItemViewType(pos)); + WidgetsListBaseEntry entry = mVisibleEntries.get(pos); viewHolderBinder.bindViewHolder(holder, mVisibleEntries.get(pos), pos); + holder.itemView.setTag(R.id.tag_widget_entry, entry); } @Override diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java index cdab964279..ef2adbb809 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java +++ b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java @@ -30,7 +30,6 @@ import android.widget.TextView; import androidx.annotation.Nullable; import androidx.annotation.UiThread; -import androidx.recyclerview.widget.RecyclerView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.LauncherAppState; @@ -59,7 +58,6 @@ public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpd @Nullable private HandlerRunnable mIconLoadRequest; @Nullable private Drawable mIconDrawable; private final int mIconSize; - private final int mBottomMarginSize; private ImageView mAppIcon; private TextView mTitle; @@ -86,8 +84,6 @@ public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpd R.styleable.WidgetsListRowHeader, defStyleAttr, /* defStyleRes= */ 0); mIconSize = a.getDimensionPixelSize(R.styleable.WidgetsListRowHeader_appIconSize, grid.iconSizePx); - mBottomMarginSize = - getResources().getDimensionPixelSize(R.dimen.widget_list_entry_bottom_margin); } @Override @@ -146,13 +142,6 @@ public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpd public void setExpanded(boolean isExpanded) { this.mIsExpanded = isExpanded; mExpandToggle.setChecked(isExpanded); - if (getLayoutParams() instanceof RecyclerView.LayoutParams) { - int bottomMargin = isExpanded ? 0 : mBottomMarginSize; - RecyclerView.LayoutParams layoutParams = - ((RecyclerView.LayoutParams) getLayoutParams()); - layoutParams.bottomMargin = bottomMargin; - setLayoutParams(layoutParams); - } } /** Sets the {@link WidgetsListDrawableState} and refreshes the background drawable. */ diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java index 3ee8b33b53..7671841878 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java +++ b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java @@ -52,7 +52,7 @@ public class WidgetsRecyclerView extends BaseRecyclerView implements OnItemTouch private HeaderViewDimensionsProvider mHeaderViewDimensionsProvider; private int mLastVisibleWidgetContentTableHeight = 0; private int mWidgetHeaderHeight = 0; - private final int mCollapsedHeaderBottomMarginSize; + private final int mSpacingBetweenEntries; @Nullable private OnContentChangeListener mOnContentChangeListener; public WidgetsRecyclerView(Context context) { @@ -72,9 +72,9 @@ public class WidgetsRecyclerView extends BaseRecyclerView implements OnItemTouch ActivityContext activity = ActivityContext.lookupContext(getContext()); DeviceProfile grid = activity.getDeviceProfile(); - // The bottom margin used when the header is not expanded. - mCollapsedHeaderBottomMarginSize = - getResources().getDimensionPixelSize(R.dimen.widget_list_entry_bottom_margin); + // The spacing used between entries. + mSpacingBetweenEntries = + getResources().getDimensionPixelSize(R.dimen.widget_list_entry_spacing); } @Override @@ -270,16 +270,16 @@ public class WidgetsRecyclerView extends BaseRecyclerView implements OnItemTouch if (untilIndex > mAdapter.getItems().size()) { untilIndex = mAdapter.getItems().size(); } - int expandedHeaderPosition = mAdapter.getSelectedHeaderPosition().orElse(-1); int totalItemsHeight = 0; for (int i = 0; i < untilIndex; i++) { WidgetsListBaseEntry entry = mAdapter.getItems().get(i); if (entry instanceof WidgetsListHeaderEntry || entry instanceof WidgetsListSearchHeaderEntry) { totalItemsHeight += mWidgetHeaderHeight; - if (expandedHeaderPosition != i) { - // If the header is collapsed, include the bottom margin it will use. - totalItemsHeight += mCollapsedHeaderBottomMarginSize; + if (i > 0) { + // Each header contains the spacing between entries as top decoration, except + // the first one. + totalItemsHeight += mSpacingBetweenEntries; } } else if (entry instanceof WidgetsListContentEntry) { totalItemsHeight += mLastVisibleWidgetContentTableHeight;