From 4b346108d16730db804016d32e0a4e83ac9543d3 Mon Sep 17 00:00:00 2001 From: Steven Ng Date: Tue, 14 Sep 2021 19:17:09 +0100 Subject: [PATCH] Add an animation for invalid widget resizing in 2 panel UI Test: manual Fix: 197509872 Change-Id: Ibcfae107e7f4b3bfbc562d4a6436f4b9237db122 --- res/values/dimens.xml | 1 + .../launcher3/AppWidgetResizeFrame.java | 114 +++++++++++++++++- src/com/android/launcher3/Launcher.java | 4 +- src/com/android/launcher3/Workspace.java | 31 +++-- 4 files changed, 140 insertions(+), 10 deletions(-) diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 5fc048093e..bc232a6534 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -57,6 +57,7 @@ 13dp 24dp 22dp + 24dp 14dp diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java index ee711463d9..fef3f8fe7d 100644 --- a/src/com/android/launcher3/AppWidgetResizeFrame.java +++ b/src/com/android/launcher3/AppWidgetResizeFrame.java @@ -2,6 +2,7 @@ package com.android.launcher3; import static android.appwidget.AppWidgetHostView.getDefaultPaddingForWidget; +import static com.android.launcher3.CellLayout.SPRING_LOADED_PROGRESS; import static com.android.launcher3.LauncherAnimUtils.LAYOUT_HEIGHT; import static com.android.launcher3.LauncherAnimUtils.LAYOUT_WIDTH; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGET_RESIZE_COMPLETED; @@ -9,6 +10,8 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH import static com.android.launcher3.views.BaseDragLayer.LAYOUT_X; import static com.android.launcher3.views.BaseDragLayer.LAYOUT_Y; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; @@ -29,6 +32,7 @@ import androidx.annotation.Px; import com.android.launcher3.accessibility.DragViewStateAnnouncer; import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.keyboard.ViewGroupFocusHelper; import com.android.launcher3.logging.InstanceId; import com.android.launcher3.logging.InstanceIdSequence; import com.android.launcher3.model.data.ItemInfo; @@ -49,12 +53,14 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O private static final String KEY_RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN = "launcher.reconfigurable_widget_education_tip_seen"; private static final Rect sTmpRect = new Rect(); + private static final Rect sTmpRect2 = new Rect(); private static final int HANDLE_COUNT = 4; private static final int INDEX_LEFT = 0; private static final int INDEX_TOP = 1; private static final int INDEX_RIGHT = 2; private static final int INDEX_BOTTOM = 3; + private static final float MIN_OPACITY_FOR_CELL_LAYOUT_DURING_INVALID_RESIZE = 0.5f; private final Launcher mLauncher; private final DragViewStateAnnouncer mStateAnnouncer; @@ -103,6 +109,16 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O private final InstanceId logInstanceId = new InstanceIdSequence().newInstanceId(); + private final ViewGroupFocusHelper mDragLayerRelativeCoordinateHelper; + + /** + * In the two panel UI, it is not possible to resize a widget to cross its host + * {@link CellLayout}'s sibling. When this happens, we gradually reduce the opacity of the + * sibling {@link CellLayout} from 1f to + * {@link #MIN_OPACITY_FOR_CELL_LAYOUT_DURING_INVALID_RESIZE}. + */ + private final float mDragAcrossTwoPanelOpacityMargin; + private boolean mLeftBorderActive; private boolean mRightBorderActive; private boolean mTopBorderActive; @@ -149,6 +165,10 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O for (int i = 0; i < HANDLE_COUNT; i++) { mSystemGestureExclusionRects.add(new Rect()); } + + mDragAcrossTwoPanelOpacityMargin = mLauncher.getResources().getDimensionPixelSize( + R.dimen.resize_frame_invalid_drag_across_two_panel_opacity_margin); + mDragLayerRelativeCoordinateHelper = new ViewGroupFocusHelper(mLauncher.getDragLayer()); } @Override @@ -359,6 +379,37 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O lp.y = sTmpRect.top; } + // Handle invalid resize across CellLayouts in the two panel UI. + if (mCellLayout.getParent() instanceof Workspace) { + Workspace workspace = (Workspace) mCellLayout.getParent(); + CellLayout pairedCellLayout = workspace.getScreenPair(mCellLayout); + if (pairedCellLayout != null) { + Rect focusedCellLayoutBound = sTmpRect; + mDragLayerRelativeCoordinateHelper.viewToRect(mCellLayout, focusedCellLayoutBound); + Rect resizeFrameBound = sTmpRect2; + findViewById(R.id.widget_resize_frame).getGlobalVisibleRect(resizeFrameBound); + float progress = 1f; + if (workspace.indexOfChild(pairedCellLayout) < workspace.indexOfChild(mCellLayout) + && mDeltaX < 0 + && resizeFrameBound.left < focusedCellLayoutBound.left) { + // Resize from right to left. + progress = (mDragAcrossTwoPanelOpacityMargin + mDeltaX) + / mDragAcrossTwoPanelOpacityMargin; + } else if (workspace.indexOfChild(pairedCellLayout) + > workspace.indexOfChild(mCellLayout) + && mDeltaX > 0 + && resizeFrameBound.right > focusedCellLayoutBound.right) { + // Resize from left to right. + progress = (mDragAcrossTwoPanelOpacityMargin - mDeltaX) + / mDragAcrossTwoPanelOpacityMargin; + } + float alpha = Math.max(MIN_OPACITY_FOR_CELL_LAYOUT_DURING_INVALID_RESIZE, progress); + float springLoadedProgress = Math.min(1f, 1f - progress); + updateInvalidResizeEffect(mCellLayout, pairedCellLayout, alpha, + springLoadedProgress); + } + } + requestLayout(); } @@ -515,13 +566,24 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O } final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); + final CellLayout pairedCellLayout; + if (mCellLayout.getParent() instanceof Workspace) { + Workspace workspace = (Workspace) mCellLayout.getParent(); + pairedCellLayout = workspace.getScreenPair(mCellLayout); + } else { + pairedCellLayout = null; + } if (!animate) { lp.width = newWidth; lp.height = newHeight; lp.x = newX; lp.y = newY; for (int i = 0; i < HANDLE_COUNT; i++) { - mDragHandles[i].setAlpha(1.0f); + mDragHandles[i].setAlpha(1f); + } + if (pairedCellLayout != null) { + updateInvalidResizeEffect(mCellLayout, pairedCellLayout, /* alpha= */ 1f, + /* springLoadedProgress= */ 0f); } requestLayout(); } else { @@ -538,6 +600,10 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O set.play(mFirstFrameAnimatorHelper.addTo( ObjectAnimator.ofFloat(mDragHandles[i], ALPHA, 1f))); } + if (pairedCellLayout != null) { + updateInvalidResizeEffect(mCellLayout, pairedCellLayout, /* alpha= */ 1f, + /* springLoadedProgress= */ 0f, /* animatorSet= */ set); + } set.setDuration(SNAP_DURATION); set.start(); } @@ -624,6 +690,52 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O } } + private void updateInvalidResizeEffect(CellLayout cellLayout, CellLayout pairedCellLayout, + float alpha, float springLoadedProgress) { + updateInvalidResizeEffect(cellLayout, pairedCellLayout, alpha, + springLoadedProgress, /* animatorSet= */ null); + } + + private void updateInvalidResizeEffect(CellLayout cellLayout, CellLayout pairedCellLayout, + float alpha, float springLoadedProgress, @Nullable AnimatorSet animatorSet) { + int childCount = pairedCellLayout.getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = pairedCellLayout.getChildAt(i); + if (animatorSet != null) { + animatorSet.play( + mFirstFrameAnimatorHelper.addTo( + ObjectAnimator.ofFloat(child, ALPHA, alpha))); + } else { + child.setAlpha(alpha); + } + } + if (animatorSet != null) { + animatorSet.play(mFirstFrameAnimatorHelper.addTo( + ObjectAnimator.ofFloat(cellLayout, SPRING_LOADED_PROGRESS, + springLoadedProgress))); + animatorSet.play(mFirstFrameAnimatorHelper.addTo( + ObjectAnimator.ofFloat(pairedCellLayout, SPRING_LOADED_PROGRESS, + springLoadedProgress))); + } else { + cellLayout.setSpringLoadedProgress(springLoadedProgress); + pairedCellLayout.setSpringLoadedProgress(springLoadedProgress); + } + + boolean shouldShowCellLayoutBorder = springLoadedProgress > 0f; + if (animatorSet != null) { + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + cellLayout.setIsDragOverlapping(shouldShowCellLayoutBorder); + pairedCellLayout.setIsDragOverlapping(shouldShowCellLayoutBorder); + } + }); + } else { + cellLayout.setIsDragOverlapping(shouldShowCellLayoutBorder); + pairedCellLayout.setIsDragOverlapping(shouldShowCellLayoutBorder); + } + } + @Override protected boolean isOfType(int type) { return (type & TYPE_WIDGET_RESIZE_FRAME) != 0; diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index ed9f0449b9..31ff7d7ade 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -2114,7 +2114,7 @@ public class Launcher extends StatefulActivity implements Launche actualIds.add(id); } int firstId = visibleIds.getArray().get(0); - int pairId = mWorkspace.getPagePair(firstId); + int pairId = mWorkspace.getScreenPair(firstId); // Double check that actual screenIds contains the visibleId, as empty screens are hidden // in single panel. if (actualIds.contains(firstId)) { @@ -2198,7 +2198,7 @@ public class Launcher extends StatefulActivity implements Launche // Some empty pages might have been removed while the phone was in a single panel // mode, so we want to add those empty pages back. IntSet screenIds = IntSet.wrap(orderedScreenIds); - orderedScreenIds.forEach(screenId -> screenIds.add(mWorkspace.getPagePair(screenId))); + orderedScreenIds.forEach(screenId -> screenIds.add(mWorkspace.getScreenPair(screenId))); orderedScreenIds = screenIds.getArray(); } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 54b2c968c0..d748186107 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -651,7 +651,7 @@ public class Workspace extends PagedView // If the icon was dragged from Hotseat, there is no page pair if (isTwoPanelEnabled() && !(mDragSourceInternal.getParent() instanceof Hotseat)) { - int pagePairScreenId = getPagePair(dragObject.dragInfo.screenId); + int pagePairScreenId = getScreenPair(dragObject.dragInfo.screenId); CellLayout pagePair = mWorkspaceScreens.get(pagePairScreenId); if (pagePair == null) { // TODO: after http://b/198820019 is fixed, remove this @@ -917,16 +917,33 @@ public class Workspace extends PagedView } /** - * Returns the page that is shown together with the given page when two panel is enabled. + * Returns the screen ID of a page that is shown together with the given page screen ID when the + * two panel UI is enabled. */ - public int getPagePair(int page) { - if (page % 2 == 0) { - return page + 1; + public int getScreenPair(int screenId) { + if (screenId % 2 == 0) { + return screenId + 1; } else { - return page - 1; + return screenId - 1; } } + /** + * Returns {@link CellLayout} that is shown together with the given {@link CellLayout} when the + * two panel UI is enabled. + */ + @Nullable + public CellLayout getScreenPair(CellLayout cellLayout) { + if (!isTwoPanelEnabled()) { + return null; + } + int screenId = getIdForScreen(cellLayout); + if (screenId == -1) { + return null; + } + return getScreenWithId(getScreenPair(screenId)); + } + public void stripEmptyScreens() { if (mLauncher.isWorkspaceLoading()) { // Don't strip empty screens if the workspace is still loading. @@ -959,7 +976,7 @@ public class Workspace extends PagedView Iterator removeScreensIterator = removeScreens.iterator(); while (removeScreensIterator.hasNext()) { int pageToRemove = removeScreensIterator.next(); - int pagePair = getPagePair(pageToRemove); + int pagePair = getScreenPair(pageToRemove); if (!removeScreens.contains(pagePair)) { // The page pair isn't empty so we want to remove the current page from the // removable pages' collection