summaryrefslogtreecommitdiff
path: root/core/java/android/widget/SmartSelectSprite.java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android/widget/SmartSelectSprite.java')
-rw-r--r--core/java/android/widget/SmartSelectSprite.java142
1 files changed, 102 insertions, 40 deletions
diff --git a/core/java/android/widget/SmartSelectSprite.java b/core/java/android/widget/SmartSelectSprite.java
index 37ede60f243e..27eff4394f96 100644
--- a/core/java/android/widget/SmartSelectSprite.java
+++ b/core/java/android/widget/SmartSelectSprite.java
@@ -35,6 +35,7 @@ import android.graphics.RectF;
import android.graphics.drawable.Drawable;
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;
@@ -42,9 +43,9 @@ import android.view.animation.Interpolator;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
-import java.util.LinkedList;
import java.util.List;
/**
@@ -76,6 +77,26 @@ final class SmartSelectSprite {
private Drawable mExistingDrawable = null;
private RectangleList mExistingRectangleList = null;
+ static final class RectangleWithTextSelectionLayout {
+ private final RectF mRectangle;
+ @Layout.TextSelectionLayout
+ private final int mTextSelectionLayout;
+
+ RectangleWithTextSelectionLayout(RectF rectangle, int textSelectionLayout) {
+ mRectangle = Preconditions.checkNotNull(rectangle);
+ mTextSelectionLayout = textSelectionLayout;
+ }
+
+ public RectF getRectangle() {
+ return mRectangle;
+ }
+
+ @Layout.TextSelectionLayout
+ public int getTextSelectionLayout() {
+ return mTextSelectionLayout;
+ }
+ }
+
/**
* A rounded rectangle with a configurable corner radius and the ability to expand outside of
* its bounding rectangle and clip against it.
@@ -84,12 +105,23 @@ final class SmartSelectSprite {
private static final String PROPERTY_ROUND_RATIO = "roundRatio";
+ /**
+ * The direction in which the rectangle will perform its expansion. A rectangle can expand
+ * from its left edge, its right edge or from the center (or, more precisely, the user's
+ * touch point). For example, in left-to-right text, a selection spanning two lines with the
+ * user's action being on the first line will have the top rectangle and expansion direction
+ * of CENTER, while the bottom one will have an expansion direction of RIGHT.
+ */
@Retention(SOURCE)
@IntDef({ExpansionDirection.LEFT, ExpansionDirection.CENTER, ExpansionDirection.RIGHT})
private @interface ExpansionDirection {
- int LEFT = 0;
- int CENTER = 1;
- int RIGHT = 2;
+ int LEFT = -1;
+ int CENTER = 0;
+ int RIGHT = 1;
+ }
+
+ private static @ExpansionDirection int invert(@ExpansionDirection int expansionDirection) {
+ return expansionDirection * -1;
}
@Retention(SOURCE)
@@ -119,15 +151,28 @@ final class SmartSelectSprite {
/** How far offset the right edge of the rectangle is from the bounding box. */
private float mRightBoundary = 0;
+ /** Whether the horizontal bounds are inverted (for RTL scenarios). */
+ private final boolean mInverted;
+
+ private final float mBoundingWidth;
+
private RoundedRectangleShape(
final RectF boundingRectangle,
final @ExpansionDirection int expansionDirection,
final @RectangleBorderType int rectangleBorderType,
+ final boolean inverted,
final float strokeWidth) {
mBoundingRectangle = new RectF(boundingRectangle);
- mExpansionDirection = expansionDirection;
+ mBoundingWidth = boundingRectangle.width();
mRectangleBorderType = rectangleBorderType;
mStrokeWidth = strokeWidth;
+ mInverted = inverted && expansionDirection != ExpansionDirection.CENTER;
+
+ if (inverted) {
+ mExpansionDirection = invert(expansionDirection);
+ } else {
+ mExpansionDirection = expansionDirection;
+ }
if (boundingRectangle.height() > boundingRectangle.width()) {
setRoundRatio(0.0f);
@@ -190,20 +235,28 @@ final class SmartSelectSprite {
canvas.restore();
}
- public void setRoundRatio(@FloatRange(from = 0.0, to = 1.0) final float roundRatio) {
+ void setRoundRatio(@FloatRange(from = 0.0, to = 1.0) final float roundRatio) {
mRoundRatio = roundRatio;
}
- public float getRoundRatio() {
+ float getRoundRatio() {
return mRoundRatio;
}
- private void setLeftBoundary(final float leftBoundary) {
- mLeftBoundary = leftBoundary;
+ private void setStartBoundary(final float startBoundary) {
+ if (mInverted) {
+ mRightBoundary = mBoundingWidth - startBoundary;
+ } else {
+ mLeftBoundary = startBoundary;
+ }
}
- private void setRightBoundary(final float rightBoundary) {
- mRightBoundary = rightBoundary;
+ private void setEndBoundary(final float endBoundary) {
+ if (mInverted) {
+ mLeftBoundary = mBoundingWidth - endBoundary;
+ } else {
+ mRightBoundary = endBoundary;
+ }
}
private float getCornerRadius() {
@@ -247,8 +300,8 @@ final class SmartSelectSprite {
private @DisplayType int mDisplayType = DisplayType.RECTANGLES;
private RectangleList(final List<RoundedRectangleShape> rectangles) {
- mRectangles = new LinkedList<>(rectangles);
- mReversedRectangles = new LinkedList<>(rectangles);
+ mRectangles = new ArrayList<>(rectangles);
+ mReversedRectangles = new ArrayList<>(rectangles);
Collections.reverse(mReversedRectangles);
mOutlinePolygonPath = generateOutlinePolygonPath(rectangles);
}
@@ -258,11 +311,11 @@ final class SmartSelectSprite {
for (RoundedRectangleShape rectangle : mReversedRectangles) {
final float rectangleLeftBoundary = boundarySoFar - rectangle.getBoundingWidth();
if (leftBoundary < rectangleLeftBoundary) {
- rectangle.setLeftBoundary(0);
+ rectangle.setStartBoundary(0);
} else if (leftBoundary > boundarySoFar) {
- rectangle.setLeftBoundary(rectangle.getBoundingWidth());
+ rectangle.setStartBoundary(rectangle.getBoundingWidth());
} else {
- rectangle.setLeftBoundary(
+ rectangle.setStartBoundary(
rectangle.getBoundingWidth() - boundarySoFar + leftBoundary);
}
@@ -275,11 +328,11 @@ final class SmartSelectSprite {
for (RoundedRectangleShape rectangle : mRectangles) {
final float rectangleRightBoundary = rectangle.getBoundingWidth() + boundarySoFar;
if (rectangleRightBoundary < rightBoundary) {
- rectangle.setRightBoundary(rectangle.getBoundingWidth());
+ rectangle.setEndBoundary(rectangle.getBoundingWidth());
} else if (boundarySoFar > rightBoundary) {
- rectangle.setRightBoundary(0);
+ rectangle.setEndBoundary(0);
} else {
- rectangle.setRightBoundary(rightBoundary - boundarySoFar);
+ rectangle.setEndBoundary(rightBoundary - boundarySoFar);
}
boundarySoFar = rectangleRightBoundary;
@@ -331,8 +384,8 @@ final class SmartSelectSprite {
}
/**
- * @param context The {@link Context} in which the animation will run
- * @param invalidator A {@link Runnable} which will be called every time the animation updates,
+ * @param context the {@link Context} in which the animation will run
+ * @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) {
@@ -356,30 +409,36 @@ final class SmartSelectSprite {
* "selection" and finally join them into a single polygon. In
* order to get the correct visual behavior, these rectangles
* should be sorted according to {@link #RECTANGLE_COMPARATOR}.
- * @param onAnimationEnd The callback which will be invoked once the whole animation
- * completes.
+ * @param onAnimationEnd the callback which will be invoked once the whole animation
+ * completes
* @throws IllegalArgumentException if the given start point is not in any of the
- * destinationRectangles.
+ * destinationRectangles
* @see #cancelAnimation()
*/
+ // TODO nullability checks on parameters
public void startAnimation(
final PointF start,
- final List<RectF> destinationRectangles,
+ final List<RectangleWithTextSelectionLayout> destinationRectangles,
final Runnable onAnimationEnd) {
cancelAnimation();
final ValueAnimator.AnimatorUpdateListener updateListener =
valueAnimator -> mInvalidator.run();
- final List<RoundedRectangleShape> shapes = new LinkedList<>();
- final List<Animator> cornerAnimators = new LinkedList<>();
+ final int rectangleCount = destinationRectangles.size();
+
+ final List<RoundedRectangleShape> shapes = new ArrayList<>(rectangleCount);
+ final List<Animator> cornerAnimators = new ArrayList<>(rectangleCount);
- RectF centerRectangle = null;
+ RectangleWithTextSelectionLayout centerRectangle = null;
int startingOffset = 0;
- for (RectF rectangle : destinationRectangles) {
+ for (int index = 0; index < rectangleCount; ++index) {
+ final RectangleWithTextSelectionLayout rectangleWithTextSelectionLayout =
+ destinationRectangles.get(index);
+ final RectF rectangle = rectangleWithTextSelectionLayout.getRectangle();
if (contains(rectangle, start)) {
- centerRectangle = rectangle;
+ centerRectangle = rectangleWithTextSelectionLayout;
break;
}
startingOffset += rectangle.width();
@@ -389,9 +448,9 @@ final class SmartSelectSprite {
throw new IllegalArgumentException("Center point is not inside any of the rectangles!");
}
- startingOffset += start.x - centerRectangle.left;
+ startingOffset += start.x - centerRectangle.getRectangle().left;
- final float centerRectangleHalfHeight = centerRectangle.height() / 2;
+ final float centerRectangleHalfHeight = centerRectangle.getRectangle().height() / 2;
final float startingOffsetLeft = startingOffset - centerRectangleHalfHeight;
final float startingOffsetRight = startingOffset + centerRectangleHalfHeight;
@@ -399,19 +458,21 @@ final class SmartSelectSprite {
generateDirections(centerRectangle, destinationRectangles);
final @RoundedRectangleShape.RectangleBorderType int[] rectangleBorderTypes =
- generateBorderTypes(destinationRectangles);
-
- int index = 0;
+ generateBorderTypes(rectangleCount);
- for (RectF rectangle : destinationRectangles) {
+ for (int index = 0; index < rectangleCount; ++index) {
+ final RectangleWithTextSelectionLayout rectangleWithTextSelectionLayout =
+ destinationRectangles.get(index);
+ final RectF rectangle = rectangleWithTextSelectionLayout.getRectangle();
final RoundedRectangleShape shape = new RoundedRectangleShape(
rectangle,
expansionDirections[index],
rectangleBorderTypes[index],
+ rectangleWithTextSelectionLayout.getTextSelectionLayout()
+ == Layout.TEXT_SELECTION_LAYOUT_RIGHT_TO_LEFT,
mStrokeWidth);
cornerAnimators.add(createCornerAnimator(shape, updateListener));
shapes.add(shape);
- index++;
}
final RectangleList rectangleList = new RectangleList(shapes);
@@ -511,7 +572,8 @@ final class SmartSelectSprite {
}
private static @RoundedRectangleShape.ExpansionDirection int[] generateDirections(
- final RectF centerRectangle, final List<RectF> rectangles) {
+ final RectangleWithTextSelectionLayout centerRectangle,
+ final List<RectangleWithTextSelectionLayout> rectangles) {
final @RoundedRectangleShape.ExpansionDirection int[] result = new int[rectangles.size()];
final int centerRectangleIndex = rectangles.indexOf(centerRectangle);
@@ -538,8 +600,8 @@ final class SmartSelectSprite {
}
private static @RoundedRectangleShape.RectangleBorderType int[] generateBorderTypes(
- final List<RectF> rectangles) {
- final @RoundedRectangleShape.RectangleBorderType int[] result = new int[rectangles.size()];
+ 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;