551 lines
21 KiB
Java
551 lines
21 KiB
Java
![]() |
/*
|
||
|
* Copyright (C) 2017 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 android.widget;
|
||
|
|
||
|
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||
|
|
||
|
import android.animation.Animator;
|
||
|
import android.animation.AnimatorSet;
|
||
|
import android.animation.ObjectAnimator;
|
||
|
import android.animation.ValueAnimator;
|
||
|
import android.annotation.ColorInt;
|
||
|
import android.annotation.FloatRange;
|
||
|
import android.annotation.IntDef;
|
||
|
import android.content.Context;
|
||
|
import android.graphics.Canvas;
|
||
|
import android.graphics.Paint;
|
||
|
import android.graphics.Path;
|
||
|
import android.graphics.PointF;
|
||
|
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.view.animation.AnimationUtils;
|
||
|
import android.view.animation.Interpolator;
|
||
|
|
||
|
import java.lang.annotation.Retention;
|
||
|
import java.util.ArrayList;
|
||
|
import java.util.Collections;
|
||
|
import java.util.Comparator;
|
||
|
import java.util.List;
|
||
|
import java.util.Objects;
|
||
|
|
||
|
/**
|
||
|
* A utility class for creating and animating the Smart Select animation.
|
||
|
*/
|
||
|
final class SmartSelectSprite {
|
||
|
|
||
|
private static final int EXPAND_DURATION = 200;
|
||
|
|
||
|
private final Interpolator mExpandInterpolator;
|
||
|
|
||
|
private Animator mActiveAnimator = null;
|
||
|
private final Runnable mInvalidator;
|
||
|
@ColorInt
|
||
|
private final int mFillColor;
|
||
|
|
||
|
static final Comparator<RectF> RECTANGLE_COMPARATOR = Comparator
|
||
|
.<RectF>comparingDouble(e -> e.bottom)
|
||
|
.thenComparingDouble(e -> e.left);
|
||
|
|
||
|
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 = Objects.requireNonNull(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.
|
||
|
*/
|
||
|
private static final class RoundedRectangleShape extends Shape {
|
||
|
|
||
|
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 = -1;
|
||
|
int CENTER = 0;
|
||
|
int RIGHT = 1;
|
||
|
}
|
||
|
|
||
|
private static @ExpansionDirection int invert(@ExpansionDirection int expansionDirection) {
|
||
|
return expansionDirection * -1;
|
||
|
}
|
||
|
|
||
|
private final RectF mBoundingRectangle;
|
||
|
private float mRoundRatio = 1.0f;
|
||
|
private final @ExpansionDirection int mExpansionDirection;
|
||
|
|
||
|
private final RectF mDrawRect = 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. */
|
||
|
private float mLeftBoundary = 0;
|
||
|
/** How offset the right edge of the rectangle is from the left side of 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 boolean inverted) {
|
||
|
mBoundingRectangle = new RectF(boundingRectangle);
|
||
|
mBoundingWidth = boundingRectangle.width();
|
||
|
mInverted = inverted && expansionDirection != ExpansionDirection.CENTER;
|
||
|
|
||
|
if (inverted) {
|
||
|
mExpansionDirection = invert(expansionDirection);
|
||
|
} else {
|
||
|
mExpansionDirection = expansionDirection;
|
||
|
}
|
||
|
|
||
|
if (boundingRectangle.height() > boundingRectangle.width()) {
|
||
|
setRoundRatio(0.0f);
|
||
|
} else {
|
||
|
setRoundRatio(1.0f);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* 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) {
|
||
|
if (mLeftBoundary == mRightBoundary) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
final float cornerRadius = getCornerRadius();
|
||
|
final float adjustedCornerRadius = getAdjustedCornerRadius();
|
||
|
|
||
|
mDrawRect.set(mBoundingRectangle);
|
||
|
mDrawRect.left = mBoundingRectangle.left + mLeftBoundary - cornerRadius / 2;
|
||
|
mDrawRect.right = mBoundingRectangle.left + mRightBoundary + cornerRadius / 2;
|
||
|
|
||
|
canvas.save();
|
||
|
mClipPath.reset();
|
||
|
mClipPath.addRoundRect(
|
||
|
mDrawRect,
|
||
|
adjustedCornerRadius,
|
||
|
adjustedCornerRadius,
|
||
|
Path.Direction.CW);
|
||
|
canvas.clipPath(mClipPath);
|
||
|
canvas.drawRect(mBoundingRectangle, paint);
|
||
|
canvas.restore();
|
||
|
}
|
||
|
|
||
|
void setRoundRatio(@FloatRange(from = 0.0, to = 1.0) final float roundRatio) {
|
||
|
mRoundRatio = roundRatio;
|
||
|
}
|
||
|
|
||
|
float getRoundRatio() {
|
||
|
return mRoundRatio;
|
||
|
}
|
||
|
|
||
|
private void setStartBoundary(final float startBoundary) {
|
||
|
if (mInverted) {
|
||
|
mRightBoundary = mBoundingWidth - startBoundary;
|
||
|
} else {
|
||
|
mLeftBoundary = startBoundary;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void setEndBoundary(final float endBoundary) {
|
||
|
if (mInverted) {
|
||
|
mLeftBoundary = mBoundingWidth - endBoundary;
|
||
|
} else {
|
||
|
mRightBoundary = endBoundary;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private float getCornerRadius() {
|
||
|
return Math.min(mBoundingRectangle.width(), mBoundingRectangle.height());
|
||
|
}
|
||
|
|
||
|
private float getAdjustedCornerRadius() {
|
||
|
return (getCornerRadius() * mRoundRatio);
|
||
|
}
|
||
|
|
||
|
private float getBoundingWidth() {
|
||
|
return (int) (mBoundingRectangle.width() + getCornerRadius());
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A collection of {@link RoundedRectangleShape}s that abstracts them to a single shape whose
|
||
|
* collective left and right boundary can be manipulated.
|
||
|
*/
|
||
|
private static final class RectangleList extends Shape {
|
||
|
|
||
|
@Retention(SOURCE)
|
||
|
@IntDef({DisplayType.RECTANGLES, DisplayType.POLYGON})
|
||
|
private @interface DisplayType {
|
||
|
int RECTANGLES = 0;
|
||
|
int POLYGON = 1;
|
||
|
}
|
||
|
|
||
|
private static final String PROPERTY_RIGHT_BOUNDARY = "rightBoundary";
|
||
|
private static final String PROPERTY_LEFT_BOUNDARY = "leftBoundary";
|
||
|
|
||
|
private final List<RoundedRectangleShape> mRectangles;
|
||
|
private final List<RoundedRectangleShape> mReversedRectangles;
|
||
|
|
||
|
private final Path mOutlinePolygonPath;
|
||
|
private @DisplayType int mDisplayType = DisplayType.RECTANGLES;
|
||
|
|
||
|
private RectangleList(final List<RoundedRectangleShape> rectangles) {
|
||
|
mRectangles = new ArrayList<>(rectangles);
|
||
|
mReversedRectangles = new ArrayList<>(rectangles);
|
||
|
Collections.reverse(mReversedRectangles);
|
||
|
mOutlinePolygonPath = generateOutlinePolygonPath(rectangles);
|
||
|
}
|
||
|
|
||
|
private void setLeftBoundary(final float leftBoundary) {
|
||
|
float boundarySoFar = getTotalWidth();
|
||
|
for (RoundedRectangleShape rectangle : mReversedRectangles) {
|
||
|
final float rectangleLeftBoundary = boundarySoFar - rectangle.getBoundingWidth();
|
||
|
if (leftBoundary < rectangleLeftBoundary) {
|
||
|
rectangle.setStartBoundary(0);
|
||
|
} else if (leftBoundary > boundarySoFar) {
|
||
|
rectangle.setStartBoundary(rectangle.getBoundingWidth());
|
||
|
} else {
|
||
|
rectangle.setStartBoundary(
|
||
|
rectangle.getBoundingWidth() - boundarySoFar + leftBoundary);
|
||
|
}
|
||
|
|
||
|
boundarySoFar = rectangleLeftBoundary;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void setRightBoundary(final float rightBoundary) {
|
||
|
float boundarySoFar = 0;
|
||
|
for (RoundedRectangleShape rectangle : mRectangles) {
|
||
|
final float rectangleRightBoundary = rectangle.getBoundingWidth() + boundarySoFar;
|
||
|
if (rectangleRightBoundary < rightBoundary) {
|
||
|
rectangle.setEndBoundary(rectangle.getBoundingWidth());
|
||
|
} else if (boundarySoFar > rightBoundary) {
|
||
|
rectangle.setEndBoundary(0);
|
||
|
} else {
|
||
|
rectangle.setEndBoundary(rightBoundary - boundarySoFar);
|
||
|
}
|
||
|
|
||
|
boundarySoFar = rectangleRightBoundary;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void setDisplayType(@DisplayType int displayType) {
|
||
|
mDisplayType = displayType;
|
||
|
}
|
||
|
|
||
|
private int getTotalWidth() {
|
||
|
int sum = 0;
|
||
|
for (RoundedRectangleShape rectangle : mRectangles) {
|
||
|
sum += rectangle.getBoundingWidth();
|
||
|
}
|
||
|
return sum;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void draw(Canvas canvas, Paint paint) {
|
||
|
if (mDisplayType == DisplayType.POLYGON) {
|
||
|
drawPolygon(canvas, paint);
|
||
|
} else {
|
||
|
drawRectangles(canvas, paint);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void drawRectangles(final Canvas canvas, final Paint paint) {
|
||
|
for (RoundedRectangleShape rectangle : mRectangles) {
|
||
|
rectangle.draw(canvas, paint);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void drawPolygon(final Canvas canvas, final Paint paint) {
|
||
|
canvas.drawPath(mOutlinePolygonPath, paint);
|
||
|
}
|
||
|
|
||
|
private static Path generateOutlinePolygonPath(
|
||
|
final List<RoundedRectangleShape> rectangles) {
|
||
|
final Path path = new Path();
|
||
|
for (final RoundedRectangleShape shape : rectangles) {
|
||
|
final Path rectanglePath = new Path();
|
||
|
rectanglePath.addRect(shape.mBoundingRectangle, Path.Direction.CW);
|
||
|
path.op(rectanglePath, Path.Op.UNION);
|
||
|
}
|
||
|
return path;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @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, @ColorInt int highlightColor,
|
||
|
final Runnable invalidator) {
|
||
|
mExpandInterpolator = AnimationUtils.loadInterpolator(
|
||
|
context,
|
||
|
android.R.interpolator.fast_out_slow_in);
|
||
|
mFillColor = highlightColor;
|
||
|
mInvalidator = Objects.requireNonNull(invalidator);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Performs the Smart Select animation on the view bound to this SmartSelectSprite.
|
||
|
*
|
||
|
* @param start The point from which the animation will start. Must be inside
|
||
|
* destinationRectangles.
|
||
|
* @param destinationRectangles The rectangles which the animation will fill out by its
|
||
|
* "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
|
||
|
* @throws IllegalArgumentException if the given start point is not in any of the
|
||
|
* destinationRectangles
|
||
|
* @see #cancelAnimation()
|
||
|
*/
|
||
|
// TODO nullability checks on parameters
|
||
|
public void startAnimation(
|
||
|
final PointF start,
|
||
|
final List<RectangleWithTextSelectionLayout> destinationRectangles,
|
||
|
final Runnable onAnimationEnd) {
|
||
|
cancelAnimation();
|
||
|
|
||
|
final ValueAnimator.AnimatorUpdateListener updateListener =
|
||
|
valueAnimator -> mInvalidator.run();
|
||
|
|
||
|
final int rectangleCount = destinationRectangles.size();
|
||
|
|
||
|
final List<RoundedRectangleShape> shapes = new ArrayList<>(rectangleCount);
|
||
|
|
||
|
RectangleWithTextSelectionLayout centerRectangle = null;
|
||
|
|
||
|
int startingOffset = 0;
|
||
|
for (RectangleWithTextSelectionLayout rectangleWithTextSelectionLayout :
|
||
|
destinationRectangles) {
|
||
|
final RectF rectangle = rectangleWithTextSelectionLayout.getRectangle();
|
||
|
if (contains(rectangle, start)) {
|
||
|
centerRectangle = rectangleWithTextSelectionLayout;
|
||
|
break;
|
||
|
}
|
||
|
startingOffset += rectangle.width();
|
||
|
}
|
||
|
|
||
|
if (centerRectangle == null) {
|
||
|
throw new IllegalArgumentException("Center point is not inside any of the rectangles!");
|
||
|
}
|
||
|
|
||
|
startingOffset += start.x - centerRectangle.getRectangle().left;
|
||
|
|
||
|
final @RoundedRectangleShape.ExpansionDirection int[] expansionDirections =
|
||
|
generateDirections(centerRectangle, 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],
|
||
|
rectangleWithTextSelectionLayout.getTextSelectionLayout()
|
||
|
== Layout.TEXT_SELECTION_LAYOUT_RIGHT_TO_LEFT);
|
||
|
shapes.add(shape);
|
||
|
}
|
||
|
|
||
|
final RectangleList rectangleList = new RectangleList(shapes);
|
||
|
final ShapeDrawable shapeDrawable = new ShapeDrawable(rectangleList);
|
||
|
|
||
|
final Paint paint = shapeDrawable.getPaint();
|
||
|
paint.setColor(mFillColor);
|
||
|
paint.setStyle(Paint.Style.FILL);
|
||
|
|
||
|
mExistingRectangleList = rectangleList;
|
||
|
mExistingDrawable = shapeDrawable;
|
||
|
|
||
|
mActiveAnimator = createAnimator(rectangleList, startingOffset, startingOffset,
|
||
|
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,
|
||
|
final float startingOffsetRight,
|
||
|
final ValueAnimator.AnimatorUpdateListener updateListener,
|
||
|
final Runnable onAnimationEnd) {
|
||
|
final ObjectAnimator rightBoundaryAnimator = ObjectAnimator.ofFloat(
|
||
|
rectangleList,
|
||
|
RectangleList.PROPERTY_RIGHT_BOUNDARY,
|
||
|
startingOffsetRight,
|
||
|
rectangleList.getTotalWidth());
|
||
|
|
||
|
final ObjectAnimator leftBoundaryAnimator = ObjectAnimator.ofFloat(
|
||
|
rectangleList,
|
||
|
RectangleList.PROPERTY_LEFT_BOUNDARY,
|
||
|
startingOffsetLeft,
|
||
|
0);
|
||
|
|
||
|
rightBoundaryAnimator.setDuration(EXPAND_DURATION);
|
||
|
leftBoundaryAnimator.setDuration(EXPAND_DURATION);
|
||
|
|
||
|
rightBoundaryAnimator.addUpdateListener(updateListener);
|
||
|
leftBoundaryAnimator.addUpdateListener(updateListener);
|
||
|
|
||
|
rightBoundaryAnimator.setInterpolator(mExpandInterpolator);
|
||
|
leftBoundaryAnimator.setInterpolator(mExpandInterpolator);
|
||
|
|
||
|
final AnimatorSet boundaryAnimator = new AnimatorSet();
|
||
|
boundaryAnimator.playTogether(leftBoundaryAnimator, rightBoundaryAnimator);
|
||
|
|
||
|
setUpAnimatorListener(boundaryAnimator, onAnimationEnd);
|
||
|
|
||
|
return boundaryAnimator;
|
||
|
}
|
||
|
|
||
|
private void setUpAnimatorListener(final Animator animator, final Runnable onAnimationEnd) {
|
||
|
animator.addListener(new Animator.AnimatorListener() {
|
||
|
@Override
|
||
|
public void onAnimationStart(Animator animator) {
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onAnimationEnd(Animator animator) {
|
||
|
mExistingRectangleList.setDisplayType(RectangleList.DisplayType.POLYGON);
|
||
|
mInvalidator.run();
|
||
|
|
||
|
onAnimationEnd.run();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onAnimationCancel(Animator animator) {
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onAnimationRepeat(Animator animator) {
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
private static @RoundedRectangleShape.ExpansionDirection int[] generateDirections(
|
||
|
final RectangleWithTextSelectionLayout centerRectangle,
|
||
|
final List<RectangleWithTextSelectionLayout> rectangles) {
|
||
|
final @RoundedRectangleShape.ExpansionDirection int[] result = new int[rectangles.size()];
|
||
|
|
||
|
final int centerRectangleIndex = rectangles.indexOf(centerRectangle);
|
||
|
|
||
|
for (int i = 0; i < centerRectangleIndex - 1; ++i) {
|
||
|
result[i] = RoundedRectangleShape.ExpansionDirection.LEFT;
|
||
|
}
|
||
|
|
||
|
if (rectangles.size() == 1) {
|
||
|
result[centerRectangleIndex] = RoundedRectangleShape.ExpansionDirection.CENTER;
|
||
|
} else if (centerRectangleIndex == 0) {
|
||
|
result[centerRectangleIndex] = RoundedRectangleShape.ExpansionDirection.LEFT;
|
||
|
} else if (centerRectangleIndex == rectangles.size() - 1) {
|
||
|
result[centerRectangleIndex] = RoundedRectangleShape.ExpansionDirection.RIGHT;
|
||
|
} else {
|
||
|
result[centerRectangleIndex] = RoundedRectangleShape.ExpansionDirection.CENTER;
|
||
|
}
|
||
|
|
||
|
for (int i = centerRectangleIndex + 1; i < result.length; ++i) {
|
||
|
result[i] = RoundedRectangleShape.ExpansionDirection.RIGHT;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A variant of {@link RectF#contains(float, float)} that also allows the point to reside on
|
||
|
* the right boundary of the rectangle.
|
||
|
*
|
||
|
* @param rectangle the rectangle inside which the point should be to be considered "contained"
|
||
|
* @param point the point which will be tested
|
||
|
* @return whether the point is inside the rectangle (or on it's right boundary)
|
||
|
*/
|
||
|
private static boolean contains(final RectF rectangle, final PointF point) {
|
||
|
final float x = point.x;
|
||
|
final float y = point.y;
|
||
|
return x >= rectangle.left && x <= rectangle.right && y >= rectangle.top
|
||
|
&& y <= rectangle.bottom;
|
||
|
}
|
||
|
|
||
|
private void removeExistingDrawables() {
|
||
|
mExistingDrawable = null;
|
||
|
mExistingRectangleList = null;
|
||
|
mInvalidator.run();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Cancels any active Smart Select animation that might be in progress.
|
||
|
*/
|
||
|
public void cancelAnimation() {
|
||
|
if (mActiveAnimator != null) {
|
||
|
mActiveAnimator.cancel();
|
||
|
mActiveAnimator = null;
|
||
|
removeExistingDrawables();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void draw(Canvas canvas) {
|
||
|
if (mExistingDrawable != null) {
|
||
|
mExistingDrawable.draw(canvas);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|