Scale down the widget previews on similar scale as the spring load scale
Currently entire workspace scales down, but the original view sticks
to user's finger at same size as before. This makes the view look larger
obstructing the view of workspace behind it - specifically, when the
widgets are of larger size.
Additionally, with this scaling change, the preview images that aren't
of same aspect ratio don't break the transition to final widget as
before.
Bug: 338290585
Test: Unit test
Flag: None bugfix
Change-Id: I1d1818e60506898c7b28074f01cee91282032a94
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 2741158..aa83c01 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -320,6 +320,8 @@
<dimen name="bg_popup_item_height">52dp</dimen>
<dimen name="bg_popup_item_vertical_padding">12dp</dimen>
<dimen name="pre_drag_view_scale">6dp</dimen>
+ <!-- Minimum size of the widget dragged view to keep it visible under the finger. -->
+ <dimen name="widget_drag_view_min_scale_down_size">70dp</dimen>
<!-- an icon with shortcuts must be dragged this far before the container is removed. -->
<dimen name="deep_shortcuts_start_drag_threshold">16dp</dimen>
<!-- Possibly related to b/235886078, icon needs to be scaled up to match expected visual size of 32 dp -->
diff --git a/src/com/android/launcher3/dragndrop/LauncherDragController.java b/src/com/android/launcher3/dragndrop/LauncherDragController.java
index f3708a2..29fc613 100644
--- a/src/com/android/launcher3/dragndrop/LauncherDragController.java
+++ b/src/com/android/launcher3/dragndrop/LauncherDragController.java
@@ -28,6 +28,7 @@
import android.view.View;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DragSource;
@@ -36,6 +37,7 @@
import com.android.launcher3.R;
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.widget.util.WidgetDragScaleUtils;
/**
* Drag controller for Launcher activity
@@ -43,7 +45,6 @@
public class LauncherDragController extends DragController<Launcher> {
private static final boolean PROFILE_DRAWING_DURING_DRAG = false;
-
private final FlingToDeleteHelper mFlingToDeleteHelper;
public LauncherDragController(Launcher launcher) {
@@ -92,8 +93,13 @@
&& !mOptions.preDragCondition.shouldStartDrag(0);
final Resources res = mActivity.getResources();
- final float scaleDps = mIsInPreDrag
- ? res.getDimensionPixelSize(R.dimen.pre_drag_view_scale) : 0f;
+
+ final float scalePx;
+ if (originalView.getViewType() == DraggableView.DRAGGABLE_WIDGET) {
+ scalePx = mIsInPreDrag ? 0f : getWidgetDragScalePx(drawable, view, dragInfo);
+ } else {
+ scalePx = mIsInPreDrag ? res.getDimensionPixelSize(R.dimen.pre_drag_view_scale) : 0f;
+ }
final DragView dragView = mDragObject.dragView = drawable != null
? new LauncherDragView(
mActivity,
@@ -102,7 +108,7 @@
registrationY,
initialDragViewScale,
dragViewScaleOnDrop,
- scaleDps)
+ scalePx)
: new LauncherDragView(
mActivity,
view,
@@ -112,7 +118,7 @@
registrationY,
initialDragViewScale,
dragViewScaleOnDrop,
- scaleDps);
+ scalePx);
dragView.setItemInfo(dragInfo);
mDragObject.dragComplete = false;
@@ -157,6 +163,29 @@
return dragView;
}
+
+ /**
+ * Returns the scale in terms of pixels (to be applied on width) to scale the preview
+ * during drag and drop.
+ */
+ @VisibleForTesting
+ float getWidgetDragScalePx(@Nullable Drawable drawable, @Nullable View view,
+ ItemInfo dragInfo) {
+ float draggedViewWidthPx = 0;
+ float draggedViewHeightPx = 0;
+
+ if (view != null) {
+ draggedViewWidthPx = view.getMeasuredWidth();
+ draggedViewHeightPx = view.getMeasuredHeight();
+ } else if (drawable != null) {
+ draggedViewWidthPx = drawable.getIntrinsicWidth();
+ draggedViewHeightPx = drawable.getIntrinsicHeight();
+ }
+
+ return WidgetDragScaleUtils.getWidgetDragScalePx(mActivity, mActivity.getDeviceProfile(),
+ draggedViewWidthPx, draggedViewHeightPx, dragInfo);
+ }
+
@Override
protected void exitDrag() {
if (!mActivity.isInState(EDIT_MODE)) {
diff --git a/src/com/android/launcher3/widget/util/WidgetDragScaleUtils.java b/src/com/android/launcher3/widget/util/WidgetDragScaleUtils.java
new file mode 100644
index 0000000..b8e7248
--- /dev/null
+++ b/src/com/android/launcher3/widget/util/WidgetDragScaleUtils.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 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.widget.util;
+
+import static com.android.launcher3.widget.util.WidgetSizes.getWidgetSizePx;
+
+import android.content.Context;
+import android.util.Size;
+
+import androidx.annotation.Px;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.model.data.ItemInfo;
+
+/** Utility classes to evaluate widget scale during drag and drops. **/
+public final class WidgetDragScaleUtils {
+ // Widgets are 5% scaled down relative to their size to have shadow display well inside the
+ // drop target frame (if its possible to scale it down within visible area under the finger).
+ private static final float WIDGET_SCALE_DOWN = 0.05f;
+
+ /**
+ * Returns the scale to be applied to given dragged view to scale it down relative to the
+ * spring loaded workspace. Applies additional scale down offset to get it a little inside
+ * the drop target frame. If the relative scale is smaller than minimum size needed to keep the
+ * view visible under the finger, scale down is performed only until the minimum size.
+ */
+ @Px
+ public static float getWidgetDragScalePx(Context context, DeviceProfile deviceProfile,
+ @Px float draggedViewWidthPx, @Px float draggedViewHeightPx, ItemInfo itemInfo) {
+ int minSize = context.getResources().getDimensionPixelSize(
+ R.dimen.widget_drag_view_min_scale_down_size);
+ Size widgetSizesPx = getWidgetSizePx(deviceProfile, itemInfo.spanX, itemInfo.spanY);
+
+ // We add workspace spring load scale, since the widget's drop target is also scaled, so
+ // the widget size is essentially that smaller.
+ float desiredWidgetScale = deviceProfile.getWorkspaceSpringLoadScale(context)
+ - WIDGET_SCALE_DOWN;
+ float desiredWidgetWidthPx = Math.max(minSize,
+ (desiredWidgetScale * widgetSizesPx.getWidth()));
+ float desiredWidgetHeightPx = Math.max(minSize,
+ desiredWidgetScale * widgetSizesPx.getHeight());
+
+ final float bitmapAspectRatio = draggedViewWidthPx / draggedViewHeightPx;
+ final float containerAspectRatio = desiredWidgetWidthPx / desiredWidgetHeightPx;
+
+ // This downscales large views to fit inside drop target frame. Smaller drawable views may
+ // be up-scaled if they are smaller than the min size;
+ final float scale = bitmapAspectRatio >= containerAspectRatio ? desiredWidgetWidthPx
+ / draggedViewWidthPx : desiredWidgetHeightPx / draggedViewHeightPx;
+ // scale in terms of dp to be applied to the drag shadow during drag and drop
+ return (draggedViewWidthPx * scale) - draggedViewWidthPx;
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/util/WidgetDragScaleUtilsTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/util/WidgetDragScaleUtilsTest.kt
new file mode 100644
index 0000000..ec8c9c2
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/util/WidgetDragScaleUtilsTest.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2024 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.widget.util
+
+import android.content.Context
+import android.graphics.Point
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.R
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.util.ActivityContextWrapper
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WidgetDragScaleUtilsTest {
+ private lateinit var context: Context
+ private lateinit var itemInfo: ItemInfo
+ private lateinit var deviceProfile: DeviceProfile
+
+ @Before
+ fun setup() {
+ context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
+
+ itemInfo = ItemInfo()
+
+ deviceProfile =
+ Mockito.spy(LauncherAppState.getIDP(context).getDeviceProfile(context).copy(context))
+
+ doAnswer {
+ return@doAnswer 0.8f
+ }
+ .whenever(deviceProfile)
+ .getWorkspaceSpringLoadScale(any(Context::class.java))
+ whenever(deviceProfile.cellSize).thenReturn(Point(CELL_SIZE, CELL_SIZE))
+ deviceProfile.cellLayoutBorderSpacePx = Point(CELL_SPACING, CELL_SPACING)
+ deviceProfile.widgetPadding.setEmpty()
+ }
+
+ @Test
+ fun getWidgetDragScalePx_largeDraggedView_downScaled() {
+ itemInfo.spanX = 2
+ itemInfo.spanY = 2
+ val widgetSize = WidgetSizes.getWidgetSizePx(deviceProfile, itemInfo.spanX, itemInfo.spanY)
+ // Assume dragged view was a drawable which was larger than widget's size.
+ val draggedViewWidthPx = widgetSize.width + 0.5f * widgetSize.width
+ val draggedViewHeightPx = widgetSize.height + 0.5f * widgetSize.height
+ // Returns negative scale pixels - i.e. downscaled
+ assertThat(
+ WidgetDragScaleUtils.getWidgetDragScalePx(
+ context,
+ deviceProfile,
+ draggedViewWidthPx,
+ draggedViewHeightPx,
+ itemInfo
+ )
+ )
+ .isLessThan(0)
+ }
+
+ @Test
+ fun getWidgetDragScalePx_draggedViewSameAsWidgetSize_downScaled() {
+ itemInfo.spanX = 4
+ itemInfo.spanY = 2
+
+ val widgetSize = WidgetSizes.getWidgetSizePx(deviceProfile, itemInfo.spanX, itemInfo.spanY)
+ // Assume dragged view was a drawable which was larger than widget's size.
+ val draggedViewWidthPx = widgetSize.width.toFloat()
+ val draggedViewHeightPx = widgetSize.height.toFloat()
+ // Returns negative scale pixels - i.e. downscaled
+ // Even if dragged view was of same size as widget's drop target, to accommodate the spring
+ // load scaling of workspace and additionally getting the view inside of drop target frame,
+ // widget would be downscaled.
+ assertThat(
+ WidgetDragScaleUtils.getWidgetDragScalePx(
+ context,
+ deviceProfile,
+ draggedViewWidthPx,
+ draggedViewHeightPx,
+ itemInfo
+ )
+ )
+ .isLessThan(0)
+ }
+
+ @Test
+ fun getWidgetDragScalePx_draggedViewSmallerThanMinSize_scaledSizeIsAtLeastMinSize() {
+ itemInfo.spanX = 1
+ itemInfo.spanY = 1
+ val minSizePx =
+ context.resources.getDimensionPixelSize(R.dimen.widget_drag_view_min_scale_down_size)
+
+ // Assume min size is greater than cell size, so that, we know the upscale of dragged view
+ // is due to min size enforcement.
+ assumeTrue(minSizePx > CELL_SIZE)
+
+ val draggedViewWidthPx = minSizePx - 15f
+ val draggedViewHeightPx = minSizePx - 15f
+
+ // Returns positive scale pixels - i.e. up-scaled
+ val finalScalePx =
+ WidgetDragScaleUtils.getWidgetDragScalePx(
+ context,
+ deviceProfile,
+ draggedViewWidthPx,
+ draggedViewHeightPx,
+ itemInfo
+ )
+
+ val effectiveWidthPx = draggedViewWidthPx + finalScalePx
+ val scaleFactor = (draggedViewWidthPx + finalScalePx) / draggedViewWidthPx
+ val effectiveHeightPx = scaleFactor * draggedViewHeightPx
+ // Both original height and width were smaller than min size, scaling them down below min
+ // size would have made them not visible under the finger. Here, as expected, widget is
+ // at least as large as min size.
+ assertThat(effectiveWidthPx).isAtLeast(minSizePx)
+ assertThat(effectiveHeightPx).isAtLeast(minSizePx)
+ }
+
+ companion object {
+ const val CELL_SIZE = 60
+ const val CELL_SPACING = 10
+ }
+}