Loading res/values/dimens.xml +4 −6 Original line number Diff line number Diff line Loading @@ -183,13 +183,11 @@ <dimen name="popup_padding_start">10dp</dimen> <dimen name="popup_padding_end">16dp</dimen> <dimen name="popup_vertical_padding">4dp</dimen> <dimen name="popup_arrow_width">10dp</dimen> <dimen name="popup_arrow_height">8dp</dimen> <dimen name="popup_arrow_vertical_offset">-2dp</dimen> <dimen name="popup_arrow_width">12dp</dimen> <dimen name="popup_arrow_height">10dp</dimen> <dimen name="popup_arrow_vertical_offset">-1dp</dimen> <!-- popup_padding_start + deep_shortcut_icon_size / 2 --> <dimen name="popup_arrow_horizontal_center_start">28dp</dimen> <!-- popup_padding_end + deep_shortcut_drag_handle_size / 2 --> <dimen name="popup_arrow_horizontal_center_end">24dp</dimen> <dimen name="popup_arrow_horizontal_center_offset">28dp</dimen> <dimen name="popup_arrow_corner_radius">2dp</dimen> <!-- popup_padding_start + icon_size + 10dp --> <dimen name="deep_shortcuts_text_padding_start">56dp</dimen> Loading src/com/android/launcher3/popup/ArrowPopup.java +40 −73 Original line number Diff line number Diff line Loading @@ -26,11 +26,8 @@ import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; import android.graphics.CornerPathEffect; import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.ShapeDrawable; import android.util.AttributeSet; import android.util.Pair; import android.view.Gravity; Loading @@ -51,7 +48,6 @@ import com.android.launcher3.Utilities; import com.android.launcher3.anim.RevealOutlineAnimation; import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.graphics.TriangleShape; import com.android.launcher3.util.Themes; import com.android.launcher3.views.BaseDragLayer; Loading @@ -72,7 +68,11 @@ public abstract class ArrowPopup<T extends BaseDraggingActivity> extends Abstrac protected final T mLauncher; protected final boolean mIsRtl; private final int mArrowOffset; private final int mArrowOffsetVertical; private final int mArrowOffsetHorizontal; private final int mArrowWidth; private final int mArrowHeight; private final int mArrowPointRadius; private final View mArrow; protected boolean mIsLeftAligned; Loading Loading @@ -103,11 +103,14 @@ public abstract class ArrowPopup<T extends BaseDraggingActivity> extends Abstrac // Initialize arrow view final Resources resources = getResources(); final int arrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width); final int arrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height); mArrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width); mArrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height); mArrow = new View(context); mArrow.setLayoutParams(new DragLayer.LayoutParams(arrowWidth, arrowHeight)); mArrowOffset = resources.getDimensionPixelSize(R.dimen.popup_arrow_vertical_offset); mArrow.setLayoutParams(new DragLayer.LayoutParams(mArrowWidth, mArrowHeight)); mArrowOffsetVertical = resources.getDimensionPixelSize(R.dimen.popup_arrow_vertical_offset); mArrowOffsetHorizontal = resources.getDimensionPixelSize( R.dimen.popup_arrow_horizontal_center_offset) - (mArrowWidth / 2); mArrowPointRadius = resources.getDimensionPixelSize(R.dimen.popup_arrow_corner_radius); } public ArrowPopup(Context context, AttributeSet attrs) { Loading Loading @@ -200,48 +203,33 @@ public abstract class ArrowPopup<T extends BaseDraggingActivity> extends Abstrac orientAboutObject(); } private void addArrow() { final Resources res = getResources(); final int arrowCenterOffset = res.getDimensionPixelSize(isAlignedWithStart() ? R.dimen.popup_arrow_horizontal_center_start : R.dimen.popup_arrow_horizontal_center_end); final int halfArrowWidth = res.getDimensionPixelSize(R.dimen.popup_arrow_width) / 2; getPopupContainer().addView(mArrow); DragLayer.LayoutParams arrowLp = (DragLayer.LayoutParams) mArrow.getLayoutParams(); private int getArrowLeft() { if (mIsLeftAligned) { mArrow.setX(getX() + arrowCenterOffset - halfArrowWidth); } else { mArrow.setX(getX() + getMeasuredWidth() - arrowCenterOffset - halfArrowWidth); return mArrowOffsetHorizontal; } return getMeasuredWidth() - mArrowOffsetHorizontal - mArrowWidth; } private void addArrow() { getPopupContainer().addView(mArrow); mArrow.setX(getX() + getArrowLeft()); if (Gravity.isVertical(mGravity)) { // This is only true if there wasn't room for the container next to the icon, // so we centered it instead. In that case we don't want to showDefaultOptions the arrow. mArrow.setVisibility(INVISIBLE); } else { ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create( arrowLp.width, arrowLp.height, !mIsAboveIcon)); Paint arrowPaint = arrowDrawable.getPaint(); arrowPaint.setColor(Themes.getAttrColor(getContext(), R.attr.popupColorPrimary)); // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable. int radius = getResources().getDimensionPixelSize(R.dimen.popup_arrow_corner_radius); arrowPaint.setPathEffect(new CornerPathEffect(radius)); mArrow.setBackground(arrowDrawable); // Clip off the part of the arrow that is underneath the popup. if (mIsAboveIcon) { mArrow.setClipBounds(new Rect(0, -mArrowOffset, arrowLp.width, arrowLp.height)); } else { mArrow.setClipBounds(new Rect(0, 0, arrowLp.width, arrowLp.height + mArrowOffset)); } mArrow.setBackground(new RoundedArrowDrawable( mArrowWidth, mArrowHeight, mArrowPointRadius, mOutlineRadius, getMeasuredWidth(), getMeasuredHeight(), mArrowOffsetHorizontal, -mArrowOffsetVertical, !mIsAboveIcon, mIsLeftAligned, Themes.getAttrColor(getContext(), R.attr.popupColorPrimary))); mArrow.setElevation(getElevation()); } mArrow.setPivotX(arrowLp.width / 2); mArrow.setPivotY(mIsAboveIcon ? arrowLp.height : 0); } protected boolean isAlignedWithStart() { return mIsLeftAligned && !mIsRtl || !mIsLeftAligned && mIsRtl; mArrow.setPivotX(mArrowWidth / 2.0f); mArrow.setPivotY(mIsAboveIcon ? mArrowHeight : 0); } /** Loading Loading @@ -274,8 +262,9 @@ public abstract class ArrowPopup<T extends BaseDraggingActivity> extends Abstrac */ private void orientAboutObject(boolean allowAlignLeft, boolean allowAlignRight) { measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); int width = getMeasuredWidth(); int extraVerticalSpace = mArrow.getLayoutParams().height + mArrowOffset int extraVerticalSpace = mArrowHeight + mArrowOffsetVertical + getResources().getDimensionPixelSize(R.dimen.popup_vertical_padding); int height = getMeasuredHeight() + extraVerticalSpace; Loading @@ -291,22 +280,7 @@ public abstract class ArrowPopup<T extends BaseDraggingActivity> extends Abstrac // Offset x so that the arrow and shortcut icons are center-aligned with the original icon. int iconWidth = mTempRect.width(); Resources resources = getResources(); int xOffset; if (isAlignedWithStart()) { // Aligning with the shortcut icon. int shortcutIconWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcut_icon_size); int shortcutPaddingStart = resources.getDimensionPixelSize( R.dimen.popup_padding_start); xOffset = iconWidth / 2 - shortcutIconWidth / 2 - shortcutPaddingStart; } else { // Aligning with the drag handle. int shortcutDragHandleWidth = resources.getDimensionPixelSize( R.dimen.deep_shortcut_drag_handle_size); int shortcutPaddingEnd = resources.getDimensionPixelSize( R.dimen.popup_padding_end); xOffset = iconWidth / 2 - shortcutDragHandleWidth / 2 - shortcutPaddingEnd; } int xOffset = iconWidth / 2 - mArrowOffsetHorizontal - mArrowWidth / 2; x += mIsLeftAligned ? xOffset : -xOffset; // Check whether we can still align as we originally wanted, now that we've calculated x. Loading Loading @@ -375,12 +349,14 @@ public abstract class ArrowPopup<T extends BaseDraggingActivity> extends Abstrac FrameLayout.LayoutParams arrowLp = (FrameLayout.LayoutParams) mArrow.getLayoutParams(); if (mIsAboveIcon) { arrowLp.gravity = lp.gravity = Gravity.BOTTOM; lp.bottomMargin = getPopupContainer().getHeight() - y - getMeasuredHeight() - insets.top; arrowLp.bottomMargin = lp.bottomMargin - arrowLp.height - mArrowOffset - insets.bottom; lp.bottomMargin = getPopupContainer().getHeight() - y - getMeasuredHeight() - insets.top; arrowLp.bottomMargin = lp.bottomMargin - arrowLp.height - mArrowOffsetVertical - insets.bottom; } else { arrowLp.gravity = lp.gravity = Gravity.TOP; lp.topMargin = y + insets.top; arrowLp.topMargin = lp.topMargin - insets.top - arrowLp.height - mArrowOffset; arrowLp.topMargin = lp.topMargin - insets.top - arrowLp.height - mArrowOffsetVertical; } } Loading Loading @@ -529,22 +505,13 @@ public abstract class ArrowPopup<T extends BaseDraggingActivity> extends Abstrac protected void onCreateCloseAnimation(AnimatorSet anim) { } private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() { Resources res = getResources(); int arrowCenterX = res.getDimensionPixelSize(mIsLeftAligned ^ mIsRtl ? R.dimen.popup_arrow_horizontal_center_start: R.dimen.popup_arrow_horizontal_center_end); int halfArrowWidth = res.getDimensionPixelSize(R.dimen.popup_arrow_width) / 2; float arrowCornerRadius = res.getDimension(R.dimen.popup_arrow_corner_radius); if (!mIsLeftAligned) { arrowCenterX = getMeasuredWidth() - arrowCenterX; } int arrowLeft = getArrowLeft(); int arrowCenterY = mIsAboveIcon ? getMeasuredHeight() : 0; mStartRect.set(arrowCenterX - halfArrowWidth, arrowCenterY, arrowCenterX + halfArrowWidth, arrowCenterY); mStartRect.set(arrowLeft, arrowCenterY, arrowLeft + mArrowWidth, arrowCenterY); return new RoundedRectRevealOutlineProvider (arrowCornerRadius, mOutlineRadius, mStartRect, mEndRect); return new RoundedRectRevealOutlineProvider( mArrowPointRadius, mOutlineRadius, mStartRect, mEndRect); } /** Loading src/com/android/launcher3/popup/RoundedArrowDrawable.java 0 → 100644 +161 −0 Original line number Diff line number Diff line /* * 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.popup; import static java.lang.Math.atan; import static java.lang.Math.cos; import static java.lang.Math.sin; import static java.lang.Math.toDegrees; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Matrix; import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; /** * A drawable for a very specific purpose. Used for the caret arrow on a rounded rectangle popup * bubble. * Draws a triangle with one rounded tip, the opposite edge is clipped by the body of the popup * so there is no overlap when drawing them together. */ public class RoundedArrowDrawable extends Drawable { private final Path mPath; private final Paint mPaint; /** * Default constructor. * * @param width of the arrow. * @param height of the arrow. * @param radius of the tip of the arrow. * @param popupRadius of the rect to clip this by. * @param popupWidth of the rect to clip this by. * @param popupHeight of the rect to clip this by. * @param arrowOffsetX from the edge of the popup to the arrow. * @param arrowOffsetY how much the arrow will overlap the popup. * @param isPointingUp or not. * @param leftAligned or false for right aligned. * @param color to draw the triangle. */ public RoundedArrowDrawable(float width, float height, float radius, float popupRadius, float popupWidth, float popupHeight, float arrowOffsetX, float arrowOffsetY, boolean isPointingUp, boolean leftAligned, int color) { mPath = new Path(); mPaint = new Paint(); mPaint.setColor(color); mPaint.setStyle(Paint.Style.FILL); mPaint.setAntiAlias(true); // Make the drawable with the triangle pointing down and positioned on the left.. addDownPointingRoundedTriangleToPath(width, height, radius, mPath); clipPopupBodyFromPath(popupRadius, popupWidth, popupHeight, arrowOffsetX, arrowOffsetY, mPath); // ... then flip it horizontal or vertical based on where it will be used. Matrix pathTransform = new Matrix(); pathTransform.setScale( leftAligned ? 1 : -1, isPointingUp ? -1 : 1, width * 0.5f, height * 0.5f); mPath.transform(pathTransform); } @Override public void draw(Canvas canvas) { canvas.drawPath(mPath, mPaint); } @Override public void getOutline(Outline outline) { outline.setPath(mPath); } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } @Override public void setAlpha(int i) { mPaint.setAlpha(i); } @Override public void setColorFilter(ColorFilter colorFilter) { mPaint.setColorFilter(colorFilter); } private static void addDownPointingRoundedTriangleToPath(float width, float height, float radius, Path path) { // Calculated for the arrow pointing down, will be flipped later if needed. // Theta is half of the angle inside the triangle tip float tanTheta = width / (2.0f * height); float theta = (float) atan(tanTheta); // Some trigonometry to find the center of the circle for the rounded tip float roundedPointCenterY = (float) (height - (radius / sin(theta))); // p is the distance along the triangle side to the intersection with the point circle float p = radius / tanTheta; float lineRoundPointIntersectFromCenter = (float) (p * sin(theta)); float lineRoundPointIntersectFromTop = (float) (height - (p * cos(theta))); float centerX = width / 2.0f; float thetaDeg = (float) toDegrees(theta); path.reset(); path.moveTo(0, 0); // Draw the top path.lineTo(width, 0); // Draw the right side up to the circle intersection path.lineTo( centerX + lineRoundPointIntersectFromCenter, lineRoundPointIntersectFromTop); // Draw the rounded point path.arcTo( centerX - radius, roundedPointCenterY - radius, centerX + radius, roundedPointCenterY + radius, thetaDeg, 180 - (2 * thetaDeg), false); // Draw the left edge to close path.lineTo(0, 0); path.close(); } private static void clipPopupBodyFromPath(float popupRadius, float popupWidth, float popupHeight, float arrowOffsetX, float arrowOffsetY, Path path) { // Make a path that is used to clip the triangle, this represents the body of the popup Path clipPiece = new Path(); clipPiece.addRoundRect( 0, 0, popupWidth, popupHeight, popupRadius, popupRadius, Path.Direction.CW); // clipping is performed as if the arrow is pointing down and positioned on the left, the // resulting path will be flipped as needed later. // The extra 0.5 in the vertical offset is to close the gap between this anti-aliased object // and the anti-aliased body of the popup. clipPiece.offset(-arrowOffsetX, -popupHeight + arrowOffsetY - 0.5f); path.op(clipPiece, Path.Op.DIFFERENCE); } } Loading
res/values/dimens.xml +4 −6 Original line number Diff line number Diff line Loading @@ -183,13 +183,11 @@ <dimen name="popup_padding_start">10dp</dimen> <dimen name="popup_padding_end">16dp</dimen> <dimen name="popup_vertical_padding">4dp</dimen> <dimen name="popup_arrow_width">10dp</dimen> <dimen name="popup_arrow_height">8dp</dimen> <dimen name="popup_arrow_vertical_offset">-2dp</dimen> <dimen name="popup_arrow_width">12dp</dimen> <dimen name="popup_arrow_height">10dp</dimen> <dimen name="popup_arrow_vertical_offset">-1dp</dimen> <!-- popup_padding_start + deep_shortcut_icon_size / 2 --> <dimen name="popup_arrow_horizontal_center_start">28dp</dimen> <!-- popup_padding_end + deep_shortcut_drag_handle_size / 2 --> <dimen name="popup_arrow_horizontal_center_end">24dp</dimen> <dimen name="popup_arrow_horizontal_center_offset">28dp</dimen> <dimen name="popup_arrow_corner_radius">2dp</dimen> <!-- popup_padding_start + icon_size + 10dp --> <dimen name="deep_shortcuts_text_padding_start">56dp</dimen> Loading
src/com/android/launcher3/popup/ArrowPopup.java +40 −73 Original line number Diff line number Diff line Loading @@ -26,11 +26,8 @@ import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; import android.graphics.CornerPathEffect; import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.ShapeDrawable; import android.util.AttributeSet; import android.util.Pair; import android.view.Gravity; Loading @@ -51,7 +48,6 @@ import com.android.launcher3.Utilities; import com.android.launcher3.anim.RevealOutlineAnimation; import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.graphics.TriangleShape; import com.android.launcher3.util.Themes; import com.android.launcher3.views.BaseDragLayer; Loading @@ -72,7 +68,11 @@ public abstract class ArrowPopup<T extends BaseDraggingActivity> extends Abstrac protected final T mLauncher; protected final boolean mIsRtl; private final int mArrowOffset; private final int mArrowOffsetVertical; private final int mArrowOffsetHorizontal; private final int mArrowWidth; private final int mArrowHeight; private final int mArrowPointRadius; private final View mArrow; protected boolean mIsLeftAligned; Loading Loading @@ -103,11 +103,14 @@ public abstract class ArrowPopup<T extends BaseDraggingActivity> extends Abstrac // Initialize arrow view final Resources resources = getResources(); final int arrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width); final int arrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height); mArrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width); mArrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height); mArrow = new View(context); mArrow.setLayoutParams(new DragLayer.LayoutParams(arrowWidth, arrowHeight)); mArrowOffset = resources.getDimensionPixelSize(R.dimen.popup_arrow_vertical_offset); mArrow.setLayoutParams(new DragLayer.LayoutParams(mArrowWidth, mArrowHeight)); mArrowOffsetVertical = resources.getDimensionPixelSize(R.dimen.popup_arrow_vertical_offset); mArrowOffsetHorizontal = resources.getDimensionPixelSize( R.dimen.popup_arrow_horizontal_center_offset) - (mArrowWidth / 2); mArrowPointRadius = resources.getDimensionPixelSize(R.dimen.popup_arrow_corner_radius); } public ArrowPopup(Context context, AttributeSet attrs) { Loading Loading @@ -200,48 +203,33 @@ public abstract class ArrowPopup<T extends BaseDraggingActivity> extends Abstrac orientAboutObject(); } private void addArrow() { final Resources res = getResources(); final int arrowCenterOffset = res.getDimensionPixelSize(isAlignedWithStart() ? R.dimen.popup_arrow_horizontal_center_start : R.dimen.popup_arrow_horizontal_center_end); final int halfArrowWidth = res.getDimensionPixelSize(R.dimen.popup_arrow_width) / 2; getPopupContainer().addView(mArrow); DragLayer.LayoutParams arrowLp = (DragLayer.LayoutParams) mArrow.getLayoutParams(); private int getArrowLeft() { if (mIsLeftAligned) { mArrow.setX(getX() + arrowCenterOffset - halfArrowWidth); } else { mArrow.setX(getX() + getMeasuredWidth() - arrowCenterOffset - halfArrowWidth); return mArrowOffsetHorizontal; } return getMeasuredWidth() - mArrowOffsetHorizontal - mArrowWidth; } private void addArrow() { getPopupContainer().addView(mArrow); mArrow.setX(getX() + getArrowLeft()); if (Gravity.isVertical(mGravity)) { // This is only true if there wasn't room for the container next to the icon, // so we centered it instead. In that case we don't want to showDefaultOptions the arrow. mArrow.setVisibility(INVISIBLE); } else { ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create( arrowLp.width, arrowLp.height, !mIsAboveIcon)); Paint arrowPaint = arrowDrawable.getPaint(); arrowPaint.setColor(Themes.getAttrColor(getContext(), R.attr.popupColorPrimary)); // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable. int radius = getResources().getDimensionPixelSize(R.dimen.popup_arrow_corner_radius); arrowPaint.setPathEffect(new CornerPathEffect(radius)); mArrow.setBackground(arrowDrawable); // Clip off the part of the arrow that is underneath the popup. if (mIsAboveIcon) { mArrow.setClipBounds(new Rect(0, -mArrowOffset, arrowLp.width, arrowLp.height)); } else { mArrow.setClipBounds(new Rect(0, 0, arrowLp.width, arrowLp.height + mArrowOffset)); } mArrow.setBackground(new RoundedArrowDrawable( mArrowWidth, mArrowHeight, mArrowPointRadius, mOutlineRadius, getMeasuredWidth(), getMeasuredHeight(), mArrowOffsetHorizontal, -mArrowOffsetVertical, !mIsAboveIcon, mIsLeftAligned, Themes.getAttrColor(getContext(), R.attr.popupColorPrimary))); mArrow.setElevation(getElevation()); } mArrow.setPivotX(arrowLp.width / 2); mArrow.setPivotY(mIsAboveIcon ? arrowLp.height : 0); } protected boolean isAlignedWithStart() { return mIsLeftAligned && !mIsRtl || !mIsLeftAligned && mIsRtl; mArrow.setPivotX(mArrowWidth / 2.0f); mArrow.setPivotY(mIsAboveIcon ? mArrowHeight : 0); } /** Loading Loading @@ -274,8 +262,9 @@ public abstract class ArrowPopup<T extends BaseDraggingActivity> extends Abstrac */ private void orientAboutObject(boolean allowAlignLeft, boolean allowAlignRight) { measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); int width = getMeasuredWidth(); int extraVerticalSpace = mArrow.getLayoutParams().height + mArrowOffset int extraVerticalSpace = mArrowHeight + mArrowOffsetVertical + getResources().getDimensionPixelSize(R.dimen.popup_vertical_padding); int height = getMeasuredHeight() + extraVerticalSpace; Loading @@ -291,22 +280,7 @@ public abstract class ArrowPopup<T extends BaseDraggingActivity> extends Abstrac // Offset x so that the arrow and shortcut icons are center-aligned with the original icon. int iconWidth = mTempRect.width(); Resources resources = getResources(); int xOffset; if (isAlignedWithStart()) { // Aligning with the shortcut icon. int shortcutIconWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcut_icon_size); int shortcutPaddingStart = resources.getDimensionPixelSize( R.dimen.popup_padding_start); xOffset = iconWidth / 2 - shortcutIconWidth / 2 - shortcutPaddingStart; } else { // Aligning with the drag handle. int shortcutDragHandleWidth = resources.getDimensionPixelSize( R.dimen.deep_shortcut_drag_handle_size); int shortcutPaddingEnd = resources.getDimensionPixelSize( R.dimen.popup_padding_end); xOffset = iconWidth / 2 - shortcutDragHandleWidth / 2 - shortcutPaddingEnd; } int xOffset = iconWidth / 2 - mArrowOffsetHorizontal - mArrowWidth / 2; x += mIsLeftAligned ? xOffset : -xOffset; // Check whether we can still align as we originally wanted, now that we've calculated x. Loading Loading @@ -375,12 +349,14 @@ public abstract class ArrowPopup<T extends BaseDraggingActivity> extends Abstrac FrameLayout.LayoutParams arrowLp = (FrameLayout.LayoutParams) mArrow.getLayoutParams(); if (mIsAboveIcon) { arrowLp.gravity = lp.gravity = Gravity.BOTTOM; lp.bottomMargin = getPopupContainer().getHeight() - y - getMeasuredHeight() - insets.top; arrowLp.bottomMargin = lp.bottomMargin - arrowLp.height - mArrowOffset - insets.bottom; lp.bottomMargin = getPopupContainer().getHeight() - y - getMeasuredHeight() - insets.top; arrowLp.bottomMargin = lp.bottomMargin - arrowLp.height - mArrowOffsetVertical - insets.bottom; } else { arrowLp.gravity = lp.gravity = Gravity.TOP; lp.topMargin = y + insets.top; arrowLp.topMargin = lp.topMargin - insets.top - arrowLp.height - mArrowOffset; arrowLp.topMargin = lp.topMargin - insets.top - arrowLp.height - mArrowOffsetVertical; } } Loading Loading @@ -529,22 +505,13 @@ public abstract class ArrowPopup<T extends BaseDraggingActivity> extends Abstrac protected void onCreateCloseAnimation(AnimatorSet anim) { } private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() { Resources res = getResources(); int arrowCenterX = res.getDimensionPixelSize(mIsLeftAligned ^ mIsRtl ? R.dimen.popup_arrow_horizontal_center_start: R.dimen.popup_arrow_horizontal_center_end); int halfArrowWidth = res.getDimensionPixelSize(R.dimen.popup_arrow_width) / 2; float arrowCornerRadius = res.getDimension(R.dimen.popup_arrow_corner_radius); if (!mIsLeftAligned) { arrowCenterX = getMeasuredWidth() - arrowCenterX; } int arrowLeft = getArrowLeft(); int arrowCenterY = mIsAboveIcon ? getMeasuredHeight() : 0; mStartRect.set(arrowCenterX - halfArrowWidth, arrowCenterY, arrowCenterX + halfArrowWidth, arrowCenterY); mStartRect.set(arrowLeft, arrowCenterY, arrowLeft + mArrowWidth, arrowCenterY); return new RoundedRectRevealOutlineProvider (arrowCornerRadius, mOutlineRadius, mStartRect, mEndRect); return new RoundedRectRevealOutlineProvider( mArrowPointRadius, mOutlineRadius, mStartRect, mEndRect); } /** Loading
src/com/android/launcher3/popup/RoundedArrowDrawable.java 0 → 100644 +161 −0 Original line number Diff line number Diff line /* * 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.popup; import static java.lang.Math.atan; import static java.lang.Math.cos; import static java.lang.Math.sin; import static java.lang.Math.toDegrees; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Matrix; import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; /** * A drawable for a very specific purpose. Used for the caret arrow on a rounded rectangle popup * bubble. * Draws a triangle with one rounded tip, the opposite edge is clipped by the body of the popup * so there is no overlap when drawing them together. */ public class RoundedArrowDrawable extends Drawable { private final Path mPath; private final Paint mPaint; /** * Default constructor. * * @param width of the arrow. * @param height of the arrow. * @param radius of the tip of the arrow. * @param popupRadius of the rect to clip this by. * @param popupWidth of the rect to clip this by. * @param popupHeight of the rect to clip this by. * @param arrowOffsetX from the edge of the popup to the arrow. * @param arrowOffsetY how much the arrow will overlap the popup. * @param isPointingUp or not. * @param leftAligned or false for right aligned. * @param color to draw the triangle. */ public RoundedArrowDrawable(float width, float height, float radius, float popupRadius, float popupWidth, float popupHeight, float arrowOffsetX, float arrowOffsetY, boolean isPointingUp, boolean leftAligned, int color) { mPath = new Path(); mPaint = new Paint(); mPaint.setColor(color); mPaint.setStyle(Paint.Style.FILL); mPaint.setAntiAlias(true); // Make the drawable with the triangle pointing down and positioned on the left.. addDownPointingRoundedTriangleToPath(width, height, radius, mPath); clipPopupBodyFromPath(popupRadius, popupWidth, popupHeight, arrowOffsetX, arrowOffsetY, mPath); // ... then flip it horizontal or vertical based on where it will be used. Matrix pathTransform = new Matrix(); pathTransform.setScale( leftAligned ? 1 : -1, isPointingUp ? -1 : 1, width * 0.5f, height * 0.5f); mPath.transform(pathTransform); } @Override public void draw(Canvas canvas) { canvas.drawPath(mPath, mPaint); } @Override public void getOutline(Outline outline) { outline.setPath(mPath); } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } @Override public void setAlpha(int i) { mPaint.setAlpha(i); } @Override public void setColorFilter(ColorFilter colorFilter) { mPaint.setColorFilter(colorFilter); } private static void addDownPointingRoundedTriangleToPath(float width, float height, float radius, Path path) { // Calculated for the arrow pointing down, will be flipped later if needed. // Theta is half of the angle inside the triangle tip float tanTheta = width / (2.0f * height); float theta = (float) atan(tanTheta); // Some trigonometry to find the center of the circle for the rounded tip float roundedPointCenterY = (float) (height - (radius / sin(theta))); // p is the distance along the triangle side to the intersection with the point circle float p = radius / tanTheta; float lineRoundPointIntersectFromCenter = (float) (p * sin(theta)); float lineRoundPointIntersectFromTop = (float) (height - (p * cos(theta))); float centerX = width / 2.0f; float thetaDeg = (float) toDegrees(theta); path.reset(); path.moveTo(0, 0); // Draw the top path.lineTo(width, 0); // Draw the right side up to the circle intersection path.lineTo( centerX + lineRoundPointIntersectFromCenter, lineRoundPointIntersectFromTop); // Draw the rounded point path.arcTo( centerX - radius, roundedPointCenterY - radius, centerX + radius, roundedPointCenterY + radius, thetaDeg, 180 - (2 * thetaDeg), false); // Draw the left edge to close path.lineTo(0, 0); path.close(); } private static void clipPopupBodyFromPath(float popupRadius, float popupWidth, float popupHeight, float arrowOffsetX, float arrowOffsetY, Path path) { // Make a path that is used to clip the triangle, this represents the body of the popup Path clipPiece = new Path(); clipPiece.addRoundRect( 0, 0, popupWidth, popupHeight, popupRadius, popupRadius, Path.Direction.CW); // clipping is performed as if the arrow is pointing down and positioned on the left, the // resulting path will be flipped as needed later. // The extra 0.5 in the vertical offset is to close the gap between this anti-aliased object // and the anti-aliased body of the popup. clipPiece.offset(-arrowOffsetX, -popupHeight + arrowOffsetY - 0.5f); path.op(clipPiece, Path.Op.DIFFERENCE); } }