Updating smart text selection animation
Now animates the highlight itself as opposed to an outline.
Bug: 70540865
Test: Manually tested it with single and multi-line - ltr and rtl
Change-Id: I8afee259c9952fcff0b713bca62c82a1022f2b0d
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 7bb0db1..5ab579d 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -1745,16 +1745,19 @@
highlight = null;
}
+ if (mSelectionActionModeHelper != null) {
+ mSelectionActionModeHelper.onDraw(canvas);
+ if (mSelectionActionModeHelper.isDrawingHighlight()) {
+ highlight = null;
+ }
+ }
+
if (mTextView.canHaveDisplayList() && canvas.isHardwareAccelerated()) {
drawHardwareAccelerated(canvas, layout, highlight, highlightPaint,
cursorOffsetVertical);
} else {
layout.draw(canvas, highlight, highlightPaint, cursorOffsetVertical);
}
-
- if (mSelectionActionModeHelper != null) {
- mSelectionActionModeHelper.onDraw(canvas);
- }
}
private void drawHardwareAccelerated(Canvas canvas, Layout layout, Path highlight,
diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index 2e354c1..e7a4c02 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -93,7 +93,7 @@
if (SMART_SELECT_ANIMATION_ENABLED) {
mSmartSelectSprite = new SmartSelectSprite(mTextView.getContext(),
- mTextView::invalidate);
+ editor.getTextView().mHighlightColor, mTextView::invalidate);
} else {
mSmartSelectSprite = null;
}
@@ -200,11 +200,15 @@
}
public void onDraw(final Canvas canvas) {
- if (mSmartSelectSprite != null) {
+ if (isDrawingHighlight() && mSmartSelectSprite != null) {
mSmartSelectSprite.draw(canvas);
}
}
+ public boolean isDrawingHighlight() {
+ return mSmartSelectSprite != null && mSmartSelectSprite.isAnimationActive();
+ }
+
private void cancelAsyncTask() {
if (mTextClassificationAsyncTask != null) {
mTextClassificationAsyncTask.cancel(true);
diff --git a/core/java/android/widget/SmartSelectSprite.java b/core/java/android/widget/SmartSelectSprite.java
index a391c6e..9a84f69 100644
--- a/core/java/android/widget/SmartSelectSprite.java
+++ b/core/java/android/widget/SmartSelectSprite.java
@@ -26,7 +26,6 @@
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.content.Context;
-import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
@@ -36,7 +35,6 @@
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.Shape;
import android.text.Layout;
-import android.util.TypedValue;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
@@ -54,21 +52,15 @@
final class SmartSelectSprite {
private static final int EXPAND_DURATION = 300;
- private static final int CORNER_DURATION = 150;
- private static final float STROKE_WIDTH_DP = 1.5F;
-
- // GBLUE700
- @ColorInt
- private static final int DEFAULT_STROKE_COLOR = 0xFF3367D6;
+ private static final int CORNER_DURATION = 50;
private final Interpolator mExpandInterpolator;
private final Interpolator mCornerInterpolator;
- private final float mStrokeWidth;
private Animator mActiveAnimator = null;
private final Runnable mInvalidator;
@ColorInt
- private final int mStrokeColor;
+ private final int mFillColor;
static final Comparator<RectF> RECTANGLE_COMPARATOR = Comparator
.<RectF>comparingDouble(e -> e.bottom)
@@ -124,26 +116,11 @@
return expansionDirection * -1;
}
- @Retention(SOURCE)
- @IntDef({RectangleBorderType.FIT, RectangleBorderType.OVERSHOOT})
- private @interface RectangleBorderType {
- /** A rectangle which, fully expanded, fits inside of its bounding rectangle. */
- int FIT = 0;
- /**
- * A rectangle which, when fully expanded, clips outside of its bounding rectangle so that
- * its edges no longer appear rounded.
- */
- int OVERSHOOT = 1;
- }
-
- private final float mStrokeWidth;
private final RectF mBoundingRectangle;
private float mRoundRatio = 1.0f;
private final @ExpansionDirection int mExpansionDirection;
- private final @RectangleBorderType int mRectangleBorderType;
private final RectF mDrawRect = new RectF();
- private final RectF mClipRect = new RectF();
private final Path mClipPath = new Path();
/** How offset the left edge of the rectangle is from the left side of the bounding box. */
@@ -159,13 +136,9 @@
private RoundedRectangleShape(
final RectF boundingRectangle,
final @ExpansionDirection int expansionDirection,
- final @RectangleBorderType int rectangleBorderType,
- final boolean inverted,
- final float strokeWidth) {
+ final boolean inverted) {
mBoundingRectangle = new RectF(boundingRectangle);
mBoundingWidth = boundingRectangle.width();
- mRectangleBorderType = rectangleBorderType;
- mStrokeWidth = strokeWidth;
mInverted = inverted && expansionDirection != ExpansionDirection.CENTER;
if (inverted) {
@@ -182,14 +155,8 @@
}
/*
- * In order to achieve the "rounded rectangle hits the wall" effect, the drawing needs to be
- * done in two passes. In this context, the wall is the bounding rectangle and in the first
- * pass we need to draw the rounded rectangle (expanded and with a corner radius as per
- * object properties) clipped by the bounding box. If the rounded rectangle expands outside
- * of the bounding box, one more pass needs to be done, as there will now be a hole in the
- * rounded rectangle where it "flattened" against the bounding box. In order to fill just
- * this hole, we need to draw the bounding box, but clip it with the rounded rectangle and
- * this will connect the missing pieces.
+ * In order to achieve the "rounded rectangle hits the wall" effect, we draw an expanding
+ * rounded rectangle that is clipped by the bounding box of the selected text.
*/
@Override
public void draw(Canvas canvas, Paint paint) {
@@ -201,31 +168,8 @@
final float adjustedCornerRadius = getAdjustedCornerRadius();
mDrawRect.set(mBoundingRectangle);
- mDrawRect.left = mBoundingRectangle.left + mLeftBoundary;
- mDrawRect.right = mBoundingRectangle.left + mRightBoundary;
-
- if (mRectangleBorderType == RectangleBorderType.OVERSHOOT) {
- mDrawRect.left -= cornerRadius / 2;
- mDrawRect.right += cornerRadius / 2;
- } else {
- switch (mExpansionDirection) {
- case ExpansionDirection.CENTER:
- break;
- case ExpansionDirection.LEFT:
- mDrawRect.right += cornerRadius;
- break;
- case ExpansionDirection.RIGHT:
- mDrawRect.left -= cornerRadius;
- break;
- }
- }
-
- canvas.save();
- mClipRect.set(mBoundingRectangle);
- mClipRect.inset(-mStrokeWidth / 2, -mStrokeWidth / 2);
- canvas.clipRect(mClipRect);
- canvas.drawRoundRect(mDrawRect, adjustedCornerRadius, adjustedCornerRadius, paint);
- canvas.restore();
+ mDrawRect.left = mBoundingRectangle.left + mLeftBoundary - cornerRadius / 2;
+ mDrawRect.right = mBoundingRectangle.left + mRightBoundary + cornerRadius / 2;
canvas.save();
mClipPath.reset();
@@ -272,11 +216,7 @@
}
private float getBoundingWidth() {
- if (mRectangleBorderType == RectangleBorderType.OVERSHOOT) {
- return (int) (mBoundingRectangle.width() + getCornerRadius());
- } else {
- return mBoundingRectangle.width();
- }
+ return (int) (mBoundingRectangle.width() + getCornerRadius());
}
}
@@ -388,19 +328,20 @@
}
/**
- * @param context the {@link Context} in which the animation will run
+ * @param context the {@link Context} in which the animation will run
+ * @param highlightColor the highlight color of the underlying {@link TextView}
* @param invalidator a {@link Runnable} which will be called every time the animation updates,
* indicating that the view drawing the animation should invalidate itself
*/
- SmartSelectSprite(final Context context, final Runnable invalidator) {
+ SmartSelectSprite(final Context context, @ColorInt int highlightColor,
+ final Runnable invalidator) {
mExpandInterpolator = AnimationUtils.loadInterpolator(
context,
android.R.interpolator.fast_out_slow_in);
mCornerInterpolator = AnimationUtils.loadInterpolator(
context,
android.R.interpolator.fast_out_linear_in);
- mStrokeWidth = dpToPixel(context, STROKE_WIDTH_DP);
- mStrokeColor = getStrokeColor(context);
+ mFillColor = highlightColor;
mInvalidator = Preconditions.checkNotNull(invalidator);
}
@@ -437,17 +378,14 @@
RectangleWithTextSelectionLayout centerRectangle = null;
int startingOffset = 0;
- int startingRectangleIndex = 0;
- for (int index = 0; index < rectangleCount; ++index) {
- final RectangleWithTextSelectionLayout rectangleWithTextSelectionLayout =
- destinationRectangles.get(index);
+ for (RectangleWithTextSelectionLayout rectangleWithTextSelectionLayout :
+ destinationRectangles) {
final RectF rectangle = rectangleWithTextSelectionLayout.getRectangle();
if (contains(rectangle, start)) {
centerRectangle = rectangleWithTextSelectionLayout;
break;
}
startingOffset += rectangle.width();
- ++startingRectangleIndex;
}
if (centerRectangle == null) {
@@ -459,9 +397,6 @@
final @RoundedRectangleShape.ExpansionDirection int[] expansionDirections =
generateDirections(centerRectangle, destinationRectangles);
- final @RoundedRectangleShape.RectangleBorderType int[] rectangleBorderTypes =
- generateBorderTypes(rectangleCount);
-
for (int index = 0; index < rectangleCount; ++index) {
final RectangleWithTextSelectionLayout rectangleWithTextSelectionLayout =
destinationRectangles.get(index);
@@ -469,10 +404,8 @@
final RoundedRectangleShape shape = new RoundedRectangleShape(
rectangle,
expansionDirections[index],
- rectangleBorderTypes[index],
rectangleWithTextSelectionLayout.getTextSelectionLayout()
- == Layout.TEXT_SELECTION_LAYOUT_RIGHT_TO_LEFT,
- mStrokeWidth);
+ == Layout.TEXT_SELECTION_LAYOUT_RIGHT_TO_LEFT);
cornerAnimators.add(createCornerAnimator(shape, updateListener));
shapes.add(shape);
}
@@ -480,44 +413,23 @@
final RectangleList rectangleList = new RectangleList(shapes);
final ShapeDrawable shapeDrawable = new ShapeDrawable(rectangleList);
- final float startingOffsetLeft;
- final float startingOffsetRight;
-
- final RoundedRectangleShape startingRectangleShape = shapes.get(startingRectangleIndex);
- final float cornerRadius = startingRectangleShape.getCornerRadius();
- if (startingRectangleShape.mRectangleBorderType
- == RoundedRectangleShape.RectangleBorderType.FIT) {
- switch (startingRectangleShape.mExpansionDirection) {
- case RoundedRectangleShape.ExpansionDirection.LEFT:
- startingOffsetLeft = startingOffsetRight = startingOffset - cornerRadius / 2;
- break;
- case RoundedRectangleShape.ExpansionDirection.RIGHT:
- startingOffsetLeft = startingOffsetRight = startingOffset + cornerRadius / 2;
- break;
- case RoundedRectangleShape.ExpansionDirection.CENTER: // fall through
- default:
- startingOffsetLeft = startingOffset - cornerRadius / 2;
- startingOffsetRight = startingOffset + cornerRadius / 2;
- break;
- }
- } else {
- startingOffsetLeft = startingOffsetRight = startingOffset;
- }
-
final Paint paint = shapeDrawable.getPaint();
- paint.setColor(mStrokeColor);
- paint.setStyle(Paint.Style.STROKE);
- paint.setStrokeWidth(mStrokeWidth);
+ paint.setColor(mFillColor);
+ paint.setStyle(Paint.Style.FILL);
mExistingRectangleList = rectangleList;
mExistingDrawable = shapeDrawable;
- mActiveAnimator = createAnimator(rectangleList, startingOffsetLeft, startingOffsetRight,
- cornerAnimators, updateListener,
- onAnimationEnd);
+ mActiveAnimator = createAnimator(rectangleList, startingOffset, startingOffset,
+ cornerAnimators, updateListener, onAnimationEnd);
mActiveAnimator.start();
}
+ /** Returns whether the sprite is currently animating. */
+ public boolean isAnimationActive() {
+ return mActiveAnimator != null && mActiveAnimator.isRunning();
+ }
+
private Animator createAnimator(
final RectangleList rectangleList,
final float startingOffsetLeft,
@@ -625,36 +537,6 @@
return result;
}
- private static @RoundedRectangleShape.RectangleBorderType int[] generateBorderTypes(
- final int numberOfRectangles) {
- final @RoundedRectangleShape.RectangleBorderType int[] result = new int[numberOfRectangles];
-
- for (int i = 1; i < result.length - 1; ++i) {
- result[i] = RoundedRectangleShape.RectangleBorderType.OVERSHOOT;
- }
-
- result[0] = RoundedRectangleShape.RectangleBorderType.FIT;
- result[result.length - 1] = RoundedRectangleShape.RectangleBorderType.FIT;
- return result;
- }
-
- private static float dpToPixel(final Context context, final float dp) {
- return TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP,
- dp,
- context.getResources().getDisplayMetrics());
- }
-
- @ColorInt
- private static int getStrokeColor(final Context context) {
- final TypedValue typedValue = new TypedValue();
- final TypedArray array = context.obtainStyledAttributes(typedValue.data, new int[]{
- android.R.attr.colorControlActivated});
- final int result = array.getColor(0, DEFAULT_STROKE_COLOR);
- array.recycle();
- return result;
- }
-
/**
* A variant of {@link RectF#contains(float, float)} that also allows the point to reside on
* the right boundary of the rectangle.