Always place notification dots directly on adaptive icon path
- Calculate point on icon path nearest to top right corner, and
use that as center for the dot
- Cleanup code related to dot offset
Test:
Set each style (different icon shape) and verify dot is in correct
placement for each of:
- Folders
- Icons in folders
- Icons in all apps
- Icons on workspace
Bug: 124414511
Change-Id: I036ed3677e8af222f00d4fad4a36a7e4d9b49ad9
diff --git a/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java b/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java
index 6bc859a..af07aa3 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java
@@ -23,8 +23,10 @@
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
-import android.graphics.Point;
+import android.graphics.Path;
+import android.graphics.PathMeasure;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.util.Log;
import android.view.ViewDebug;
@@ -36,33 +38,51 @@
private static final String TAG = "DotRenderer";
// The dot size is defined as a percentage of the app icon size.
- private static final float SIZE_PERCENTAGE = 0.38f;
+ private static final float SIZE_PERCENTAGE = 0.228f;
- // Extra scale down of the dot
- private static final float DOT_SCALE = 0.6f;
-
- // Offset the dot slightly away from the icon if there's space.
- private static final float OFFSET_PERCENTAGE = 0.02f;
-
- private final float mDotCenterOffset;
- private final int mOffset;
private final float mCircleRadius;
private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG);
private final Bitmap mBackgroundWithShadow;
private final float mBitmapOffset;
- public DotRenderer(int iconSizePx) {
- mDotCenterOffset = SIZE_PERCENTAGE * iconSizePx;
- mOffset = (int) (OFFSET_PERCENTAGE * iconSizePx);
+ // Stores the center x and y position as a percentage (0 to 1) of the icon size
+ private final float[] mRightDotPosition;
+ private final float[] mLeftDotPosition;
- int size = (int) (DOT_SCALE * mDotCenterOffset);
+ public DotRenderer(int iconSizePx, Path iconShapePath, int pathSize) {
+ int size = Math.round(SIZE_PERCENTAGE * iconSizePx);
ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT);
builder.ambientShadowAlpha = 88;
mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, size);
mCircleRadius = builder.radius;
mBitmapOffset = -mBackgroundWithShadow.getHeight() * 0.5f; // Same as width.
+
+ // Find the points on the path that are closest to the top left and right corners.
+ mLeftDotPosition = getPathPoint(iconShapePath, pathSize, -1);
+ mRightDotPosition = getPathPoint(iconShapePath, pathSize, 1);
+ }
+
+ private static float[] getPathPoint(Path path, float size, float direction) {
+ float halfSize = size / 2;
+ // Small delta so that we don't get a zero size triangle
+ float delta = 1;
+
+ float x = halfSize + direction * halfSize;
+ Path trianglePath = new Path();
+ trianglePath.moveTo(halfSize, halfSize);
+ trianglePath.lineTo(x + delta * direction, 0);
+ trianglePath.lineTo(x, -delta);
+ trianglePath.close();
+
+ trianglePath.op(path, Path.Op.INTERSECT);
+ float[] pos = new float[2];
+ new PathMeasure(trianglePath, false).getPosTan(0, pos, null);
+
+ pos[0] = pos[0] / size;
+ pos[1] = pos[1] / size;
+ return pos;
}
/**
@@ -74,15 +94,21 @@
return;
}
canvas.save();
- // We draw the dot relative to its center.
- float dotCenterX = params.leftAlign
- ? params.iconBounds.left + mDotCenterOffset / 2
- : params.iconBounds.right - mDotCenterOffset / 2;
- float dotCenterY = params.iconBounds.top + mDotCenterOffset / 2;
- int offsetX = Math.min(mOffset, params.spaceForOffset.x);
- int offsetY = Math.min(mOffset, params.spaceForOffset.y);
- canvas.translate(dotCenterX + offsetX, dotCenterY - offsetY);
+ Rect iconBounds = params.iconBounds;
+ float[] dotPosition = params.leftAlign ? mLeftDotPosition : mRightDotPosition;
+ float dotCenterX = iconBounds.left + iconBounds.width() * dotPosition[0];
+ float dotCenterY = iconBounds.top + iconBounds.height() * dotPosition[1];
+
+ // Ensure dot fits entirely in canvas clip bounds.
+ Rect canvasBounds = canvas.getClipBounds();
+ float offsetX = params.leftAlign
+ ? Math.max(0, canvasBounds.left - (dotCenterX + mBitmapOffset))
+ : Math.min(0, canvasBounds.right - (dotCenterX - mBitmapOffset));
+ float offsetY = Math.max(0, canvasBounds.top - (dotCenterY + mBitmapOffset));
+
+ // We draw the dot relative to its center.
+ canvas.translate(dotCenterX + offsetX, dotCenterY + offsetY);
canvas.scale(params.scale, params.scale);
mCirclePaint.setColor(Color.BLACK);
@@ -102,9 +128,6 @@
/** The progress of the animation, from 0 to 1. */
@ViewDebug.ExportedProperty(category = "notification dot")
public float scale;
- /** Overrides internally calculated offset if specified value is smaller. */
- @ViewDebug.ExportedProperty(category = "notification dot")
- public Point spaceForOffset = new Point();
/** Whether the dot should align to the top left of the icon rather than the top right. */
@ViewDebug.ExportedProperty(category = "notification dot")
public boolean leftAlign;
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 8291acc..bff7f42 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -46,10 +46,12 @@
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.IconCache.IconLoadRequest;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
+import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.views.ActivityContext;
@@ -388,7 +390,7 @@
protected void drawDotIfNecessary(Canvas canvas) {
if (!mForceHideDot && (hasDot() || mDotParams.scale > 0)) {
getIconBounds(mDotParams.iconBounds);
- mDotParams.spaceForOffset.set((getWidth() - mIconSize) / 2, getPaddingTop());
+ Utilities.scaleRectAboutCenter(mDotParams.iconBounds, IconShape.getNormalizationScale());
final int scrollX = getScrollX();
final int scrollY = getScrollY();
canvas.translate(scrollX, scrollY);
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index c0affb9..3d2d7cf 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -24,15 +24,13 @@
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.view.Surface;
-import android.view.View;
import android.view.WindowManager;
import com.android.launcher3.CellLayout.ContainerType;
+import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.IconNormalizer;
-import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
-
public class DeviceProfile {
public final InvariantDeviceProfile inv;
@@ -240,7 +238,8 @@
updateWorkspacePadding();
// This is done last, after iconSizePx is calculated above.
- mDotRenderer = new DotRenderer(iconSizePx);
+ mDotRenderer = new DotRenderer(iconSizePx, IconShape.getShapePath(),
+ IconShape.DEFAULT_PATH_SIZE);
}
public DeviceProfile copy(Context context) {
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 6fa9ba9..8d9c520 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -36,6 +36,8 @@
import android.view.ViewGroup;
import android.widget.FrameLayout;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.Alarm;
import com.android.launcher3.AppInfo;
import com.android.launcher3.BubbleTextView;
@@ -50,11 +52,11 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.OnAlarmListener;
import com.android.launcher3.R;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.SimpleOnStylusPressListener;
import com.android.launcher3.StylusEventHelper;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
+import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.dot.FolderDotInfo;
import com.android.launcher3.dragndrop.BaseItemDragListener;
@@ -68,8 +70,6 @@
import java.util.ArrayList;
import java.util.List;
-import androidx.annotation.NonNull;
-
/**
* An icon that can appear on in the workspace representing an {@link Folder}.
*/
@@ -510,10 +510,11 @@
Rect iconBounds = mDotParams.iconBounds;
BubbleTextView.getIconBounds(this, iconBounds,
mLauncher.getWallpaperDeviceProfile().iconSizePx);
+ float iconScale = (float) mBackground.previewSize / iconBounds.width();
+ Utilities.scaleRectAboutCenter(iconBounds, iconScale);
// If we are animating to the accepting state, animate the dot out.
mDotParams.scale = Math.max(0, mDotScale - mBackground.getScaleProgress());
- mDotParams.spaceForOffset.set(getWidth() - iconBounds.right, iconBounds.top);
mDotParams.color = mBackground.getDotColor();
mDotRenderer.draw(canvas, mDotParams);
}