Loading core/java/android/widget/SmartSelectSprite.java +54 −140 Original line number Original line Diff line number Diff line Loading @@ -44,11 +44,8 @@ import android.view.animation.Interpolator; import java.lang.annotation.Retention; import java.lang.annotation.Retention; import java.util.Collections; import java.util.Collections; import java.util.Comparator; import java.util.Comparator; import java.util.HashSet; import java.util.LinkedList; import java.util.LinkedList; import java.util.List; import java.util.List; import java.util.Set; import java.util.Stack; /** /** * A utility class for creating and animating the Smart Select animation. * A utility class for creating and animating the Smart Select animation. Loading @@ -59,7 +56,6 @@ final class SmartSelectSprite { private static final int EXPAND_DURATION = 300; private static final int EXPAND_DURATION = 300; private static final int CORNER_DURATION = 150; private static final int CORNER_DURATION = 150; private static final float STROKE_WIDTH_DP = 1.5F; private static final float STROKE_WIDTH_DP = 1.5F; private static final int POINTS_PER_LINE = 4; // GBLUE700 // GBLUE700 @ColorInt @ColorInt Loading @@ -73,40 +69,13 @@ final class SmartSelectSprite { private Animator mActiveAnimator = null; private Animator mActiveAnimator = null; @ColorInt @ColorInt private final int mStrokeColor; private final int mStrokeColor; private Set<Drawable> mExistingAnimationDrawables = new HashSet<>(); static final Comparator<RectF> RECTANGLE_COMPARATOR = Comparator static final Comparator<RectF> RECTANGLE_COMPARATOR = Comparator .<RectF>comparingDouble(e -> e.bottom) .<RectF>comparingDouble(e -> e.bottom) .thenComparingDouble(e -> e.left); .thenComparingDouble(e -> e.left); /** private Drawable mExistingDrawable = null; * Represents a set of points connected by lines. private RectangleList mExistingRectangleList = null; */ private static final class PolygonShape extends Shape { private final float[] mLineCoordinates; private PolygonShape(final List<PointF> points) { mLineCoordinates = new float[points.size() * POINTS_PER_LINE]; int index = 0; PointF currentPoint = points.get(0); for (final PointF nextPoint : points) { mLineCoordinates[index] = currentPoint.x; mLineCoordinates[index + 1] = currentPoint.y; mLineCoordinates[index + 2] = nextPoint.x; mLineCoordinates[index + 3] = nextPoint.y; index += POINTS_PER_LINE; currentPoint = nextPoint; } } @Override public void draw(Canvas canvas, Paint paint) { canvas.drawLines(mLineCoordinates, paint); } } /** /** * A rounded rectangle with a configurable corner radius and the ability to expand outside of * A rounded rectangle with a configurable corner radius and the ability to expand outside of Loading Loading @@ -262,16 +231,27 @@ final class SmartSelectSprite { */ */ private static final class RectangleList extends Shape { 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_RIGHT_BOUNDARY = "rightBoundary"; private static final String PROPERTY_LEFT_BOUNDARY = "leftBoundary"; private static final String PROPERTY_LEFT_BOUNDARY = "leftBoundary"; private final List<RoundedRectangleShape> mRectangles; private final List<RoundedRectangleShape> mRectangles; private final List<RoundedRectangleShape> mReversedRectangles; private final List<RoundedRectangleShape> mReversedRectangles; private RectangleList(List<RoundedRectangleShape> rectangles) { private final Path mOutlinePolygonPath; private @DisplayType int mDisplayType = DisplayType.RECTANGLES; private RectangleList(final List<RoundedRectangleShape> rectangles) { mRectangles = new LinkedList<>(rectangles); mRectangles = new LinkedList<>(rectangles); mReversedRectangles = new LinkedList<>(rectangles); mReversedRectangles = new LinkedList<>(rectangles); Collections.reverse(mReversedRectangles); Collections.reverse(mReversedRectangles); mOutlinePolygonPath = generateOutlinePolygonPath(rectangles); } } private void setLeftBoundary(final float leftBoundary) { private void setLeftBoundary(final float leftBoundary) { Loading Loading @@ -307,6 +287,10 @@ final class SmartSelectSprite { } } } } void setDisplayType(@DisplayType int displayType) { mDisplayType = displayType; } private int getTotalWidth() { private int getTotalWidth() { int sum = 0; int sum = 0; for (RoundedRectangleShape rectangle : mRectangles) { for (RoundedRectangleShape rectangle : mRectangles) { Loading @@ -317,11 +301,34 @@ final class SmartSelectSprite { @Override @Override public void draw(Canvas canvas, Paint paint) { 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) { for (RoundedRectangleShape rectangle : mRectangles) { rectangle.draw(canvas, paint); 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; } } } SmartSelectSprite(final View view) { SmartSelectSprite(final View view) { Loading @@ -337,84 +344,6 @@ final class SmartSelectSprite { mView = view; mView = view; } } private static boolean intersectsOrTouches(RectF a, RectF b) { return a.left <= b.right && b.left <= a.right && a.top <= b.bottom && b.top <= a.bottom; } private List<Drawable> mergeRectanglesToPolygonShape( final List<RectF> rectangles, final int color) { final List<Drawable> drawables = new LinkedList<>(); final Set<List<PointF>> mergedPaths = calculateMergedPolygonPoints(rectangles); for (List<PointF> path : mergedPaths) { // Add the starting point to the end of the polygon so that it ends up closed. path.add(path.get(0)); final PolygonShape shape = new PolygonShape(path); final ShapeDrawable drawable = new ShapeDrawable(shape); drawable.getPaint().setColor(color); drawable.getPaint().setStyle(Paint.Style.STROKE); drawable.getPaint().setStrokeWidth(mStrokeWidth); drawables.add(drawable); } return drawables; } private static Set<List<PointF>> calculateMergedPolygonPoints( List<RectF> rectangles) { final Set<List<RectF>> partitions = new HashSet<>(); final LinkedList<RectF> listOfRects = new LinkedList<>(rectangles); while (!listOfRects.isEmpty()) { final RectF candidate = listOfRects.removeFirst(); final List<RectF> partition = new LinkedList<>(); partition.add(candidate); final LinkedList<RectF> otherCandidates = new LinkedList<>(); otherCandidates.addAll(listOfRects); while (!otherCandidates.isEmpty()) { final RectF otherCandidate = otherCandidates.removeFirst(); for (RectF partitionElement : partition) { if (intersectsOrTouches(partitionElement, otherCandidate)) { partition.add(otherCandidate); listOfRects.remove(otherCandidate); break; } } } partition.sort(Comparator.comparing(o -> o.top)); partitions.add(partition); } final Set<List<PointF>> result = new HashSet<>(); for (List<RectF> partition : partitions) { final List<PointF> points = new LinkedList<>(); final Stack<RectF> rects = new Stack<>(); for (RectF rect : partition) { points.add(new PointF(rect.right, rect.top)); points.add(new PointF(rect.right, rect.bottom)); rects.add(rect); } while (!rects.isEmpty()) { final RectF rect = rects.pop(); points.add(new PointF(rect.left, rect.bottom)); points.add(new PointF(rect.left, rect.top)); } result.add(points); } return result; } /** /** * Performs the Smart Select animation on the view bound to this SmartSelectSprite. * Performs the Smart Select animation on the view bound to this SmartSelectSprite. * * Loading Loading @@ -490,17 +419,17 @@ final class SmartSelectSprite { paint.setStyle(Paint.Style.STROKE); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(mStrokeWidth); paint.setStrokeWidth(mStrokeWidth); addToOverlay(shapeDrawable); mExistingRectangleList = rectangleList; mExistingDrawable = shapeDrawable; mView.getOverlay().add(shapeDrawable); mActiveAnimator = createAnimator(mStrokeColor, destinationRectangles, rectangleList, mActiveAnimator = createAnimator(rectangleList, startingOffsetLeft, startingOffsetRight, startingOffsetLeft, startingOffsetRight, cornerAnimators, updateListener, cornerAnimators, updateListener, onAnimationEnd); onAnimationEnd); mActiveAnimator.start(); mActiveAnimator.start(); } } private Animator createAnimator( private Animator createAnimator( final @ColorInt int color, final List<RectF> destinationRectangles, final RectangleList rectangleList, final RectangleList rectangleList, final float startingOffsetLeft, final float startingOffsetLeft, final float startingOffsetRight, final float startingOffsetRight, Loading Loading @@ -537,15 +466,12 @@ final class SmartSelectSprite { final AnimatorSet animatorSet = new AnimatorSet(); final AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playSequentially(boundaryAnimator, cornerAnimator); animatorSet.playSequentially(boundaryAnimator, cornerAnimator); setUpAnimatorListener(animatorSet, destinationRectangles, color, onAnimationEnd); setUpAnimatorListener(animatorSet, onAnimationEnd); return animatorSet; return animatorSet; } } private void setUpAnimatorListener(final Animator animator, private void setUpAnimatorListener(final Animator animator, final Runnable onAnimationEnd) { final List<RectF> destinationRectangles, final @ColorInt int color, final Runnable onAnimationEnd) { animator.addListener(new Animator.AnimatorListener() { animator.addListener(new Animator.AnimatorListener() { @Override @Override public void onAnimationStart(Animator animator) { public void onAnimationStart(Animator animator) { Loading @@ -553,15 +479,8 @@ final class SmartSelectSprite { @Override @Override public void onAnimationEnd(Animator animator) { public void onAnimationEnd(Animator animator) { removeExistingDrawables(); mExistingRectangleList.setDisplayType(RectangleList.DisplayType.POLYGON); mExistingDrawable.invalidateSelf(); final List<Drawable> polygonShapes = mergeRectanglesToPolygonShape( destinationRectangles, color); for (Drawable drawable : polygonShapes) { addToOverlay(drawable); } onAnimationEnd.run(); onAnimationEnd.run(); } } Loading Loading @@ -661,17 +580,12 @@ final class SmartSelectSprite { && y <= rectangle.bottom; && y <= rectangle.bottom; } } private void addToOverlay(final Drawable drawable) { mView.getOverlay().add(drawable); mExistingAnimationDrawables.add(drawable); } private void removeExistingDrawables() { private void removeExistingDrawables() { final ViewOverlay overlay = mView.getOverlay(); final ViewOverlay overlay = mView.getOverlay(); for (Drawable drawable : mExistingAnimationDrawables) { overlay.remove(mExistingDrawable); overlay.remove(drawable); } mExistingDrawable = null; mExistingAnimationDrawables.clear(); mExistingRectangleList = null; } } /** /** Loading Loading
core/java/android/widget/SmartSelectSprite.java +54 −140 Original line number Original line Diff line number Diff line Loading @@ -44,11 +44,8 @@ import android.view.animation.Interpolator; import java.lang.annotation.Retention; import java.lang.annotation.Retention; import java.util.Collections; import java.util.Collections; import java.util.Comparator; import java.util.Comparator; import java.util.HashSet; import java.util.LinkedList; import java.util.LinkedList; import java.util.List; import java.util.List; import java.util.Set; import java.util.Stack; /** /** * A utility class for creating and animating the Smart Select animation. * A utility class for creating and animating the Smart Select animation. Loading @@ -59,7 +56,6 @@ final class SmartSelectSprite { private static final int EXPAND_DURATION = 300; private static final int EXPAND_DURATION = 300; private static final int CORNER_DURATION = 150; private static final int CORNER_DURATION = 150; private static final float STROKE_WIDTH_DP = 1.5F; private static final float STROKE_WIDTH_DP = 1.5F; private static final int POINTS_PER_LINE = 4; // GBLUE700 // GBLUE700 @ColorInt @ColorInt Loading @@ -73,40 +69,13 @@ final class SmartSelectSprite { private Animator mActiveAnimator = null; private Animator mActiveAnimator = null; @ColorInt @ColorInt private final int mStrokeColor; private final int mStrokeColor; private Set<Drawable> mExistingAnimationDrawables = new HashSet<>(); static final Comparator<RectF> RECTANGLE_COMPARATOR = Comparator static final Comparator<RectF> RECTANGLE_COMPARATOR = Comparator .<RectF>comparingDouble(e -> e.bottom) .<RectF>comparingDouble(e -> e.bottom) .thenComparingDouble(e -> e.left); .thenComparingDouble(e -> e.left); /** private Drawable mExistingDrawable = null; * Represents a set of points connected by lines. private RectangleList mExistingRectangleList = null; */ private static final class PolygonShape extends Shape { private final float[] mLineCoordinates; private PolygonShape(final List<PointF> points) { mLineCoordinates = new float[points.size() * POINTS_PER_LINE]; int index = 0; PointF currentPoint = points.get(0); for (final PointF nextPoint : points) { mLineCoordinates[index] = currentPoint.x; mLineCoordinates[index + 1] = currentPoint.y; mLineCoordinates[index + 2] = nextPoint.x; mLineCoordinates[index + 3] = nextPoint.y; index += POINTS_PER_LINE; currentPoint = nextPoint; } } @Override public void draw(Canvas canvas, Paint paint) { canvas.drawLines(mLineCoordinates, paint); } } /** /** * A rounded rectangle with a configurable corner radius and the ability to expand outside of * A rounded rectangle with a configurable corner radius and the ability to expand outside of Loading Loading @@ -262,16 +231,27 @@ final class SmartSelectSprite { */ */ private static final class RectangleList extends Shape { 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_RIGHT_BOUNDARY = "rightBoundary"; private static final String PROPERTY_LEFT_BOUNDARY = "leftBoundary"; private static final String PROPERTY_LEFT_BOUNDARY = "leftBoundary"; private final List<RoundedRectangleShape> mRectangles; private final List<RoundedRectangleShape> mRectangles; private final List<RoundedRectangleShape> mReversedRectangles; private final List<RoundedRectangleShape> mReversedRectangles; private RectangleList(List<RoundedRectangleShape> rectangles) { private final Path mOutlinePolygonPath; private @DisplayType int mDisplayType = DisplayType.RECTANGLES; private RectangleList(final List<RoundedRectangleShape> rectangles) { mRectangles = new LinkedList<>(rectangles); mRectangles = new LinkedList<>(rectangles); mReversedRectangles = new LinkedList<>(rectangles); mReversedRectangles = new LinkedList<>(rectangles); Collections.reverse(mReversedRectangles); Collections.reverse(mReversedRectangles); mOutlinePolygonPath = generateOutlinePolygonPath(rectangles); } } private void setLeftBoundary(final float leftBoundary) { private void setLeftBoundary(final float leftBoundary) { Loading Loading @@ -307,6 +287,10 @@ final class SmartSelectSprite { } } } } void setDisplayType(@DisplayType int displayType) { mDisplayType = displayType; } private int getTotalWidth() { private int getTotalWidth() { int sum = 0; int sum = 0; for (RoundedRectangleShape rectangle : mRectangles) { for (RoundedRectangleShape rectangle : mRectangles) { Loading @@ -317,11 +301,34 @@ final class SmartSelectSprite { @Override @Override public void draw(Canvas canvas, Paint paint) { 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) { for (RoundedRectangleShape rectangle : mRectangles) { rectangle.draw(canvas, paint); 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; } } } SmartSelectSprite(final View view) { SmartSelectSprite(final View view) { Loading @@ -337,84 +344,6 @@ final class SmartSelectSprite { mView = view; mView = view; } } private static boolean intersectsOrTouches(RectF a, RectF b) { return a.left <= b.right && b.left <= a.right && a.top <= b.bottom && b.top <= a.bottom; } private List<Drawable> mergeRectanglesToPolygonShape( final List<RectF> rectangles, final int color) { final List<Drawable> drawables = new LinkedList<>(); final Set<List<PointF>> mergedPaths = calculateMergedPolygonPoints(rectangles); for (List<PointF> path : mergedPaths) { // Add the starting point to the end of the polygon so that it ends up closed. path.add(path.get(0)); final PolygonShape shape = new PolygonShape(path); final ShapeDrawable drawable = new ShapeDrawable(shape); drawable.getPaint().setColor(color); drawable.getPaint().setStyle(Paint.Style.STROKE); drawable.getPaint().setStrokeWidth(mStrokeWidth); drawables.add(drawable); } return drawables; } private static Set<List<PointF>> calculateMergedPolygonPoints( List<RectF> rectangles) { final Set<List<RectF>> partitions = new HashSet<>(); final LinkedList<RectF> listOfRects = new LinkedList<>(rectangles); while (!listOfRects.isEmpty()) { final RectF candidate = listOfRects.removeFirst(); final List<RectF> partition = new LinkedList<>(); partition.add(candidate); final LinkedList<RectF> otherCandidates = new LinkedList<>(); otherCandidates.addAll(listOfRects); while (!otherCandidates.isEmpty()) { final RectF otherCandidate = otherCandidates.removeFirst(); for (RectF partitionElement : partition) { if (intersectsOrTouches(partitionElement, otherCandidate)) { partition.add(otherCandidate); listOfRects.remove(otherCandidate); break; } } } partition.sort(Comparator.comparing(o -> o.top)); partitions.add(partition); } final Set<List<PointF>> result = new HashSet<>(); for (List<RectF> partition : partitions) { final List<PointF> points = new LinkedList<>(); final Stack<RectF> rects = new Stack<>(); for (RectF rect : partition) { points.add(new PointF(rect.right, rect.top)); points.add(new PointF(rect.right, rect.bottom)); rects.add(rect); } while (!rects.isEmpty()) { final RectF rect = rects.pop(); points.add(new PointF(rect.left, rect.bottom)); points.add(new PointF(rect.left, rect.top)); } result.add(points); } return result; } /** /** * Performs the Smart Select animation on the view bound to this SmartSelectSprite. * Performs the Smart Select animation on the view bound to this SmartSelectSprite. * * Loading Loading @@ -490,17 +419,17 @@ final class SmartSelectSprite { paint.setStyle(Paint.Style.STROKE); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(mStrokeWidth); paint.setStrokeWidth(mStrokeWidth); addToOverlay(shapeDrawable); mExistingRectangleList = rectangleList; mExistingDrawable = shapeDrawable; mView.getOverlay().add(shapeDrawable); mActiveAnimator = createAnimator(mStrokeColor, destinationRectangles, rectangleList, mActiveAnimator = createAnimator(rectangleList, startingOffsetLeft, startingOffsetRight, startingOffsetLeft, startingOffsetRight, cornerAnimators, updateListener, cornerAnimators, updateListener, onAnimationEnd); onAnimationEnd); mActiveAnimator.start(); mActiveAnimator.start(); } } private Animator createAnimator( private Animator createAnimator( final @ColorInt int color, final List<RectF> destinationRectangles, final RectangleList rectangleList, final RectangleList rectangleList, final float startingOffsetLeft, final float startingOffsetLeft, final float startingOffsetRight, final float startingOffsetRight, Loading Loading @@ -537,15 +466,12 @@ final class SmartSelectSprite { final AnimatorSet animatorSet = new AnimatorSet(); final AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playSequentially(boundaryAnimator, cornerAnimator); animatorSet.playSequentially(boundaryAnimator, cornerAnimator); setUpAnimatorListener(animatorSet, destinationRectangles, color, onAnimationEnd); setUpAnimatorListener(animatorSet, onAnimationEnd); return animatorSet; return animatorSet; } } private void setUpAnimatorListener(final Animator animator, private void setUpAnimatorListener(final Animator animator, final Runnable onAnimationEnd) { final List<RectF> destinationRectangles, final @ColorInt int color, final Runnable onAnimationEnd) { animator.addListener(new Animator.AnimatorListener() { animator.addListener(new Animator.AnimatorListener() { @Override @Override public void onAnimationStart(Animator animator) { public void onAnimationStart(Animator animator) { Loading @@ -553,15 +479,8 @@ final class SmartSelectSprite { @Override @Override public void onAnimationEnd(Animator animator) { public void onAnimationEnd(Animator animator) { removeExistingDrawables(); mExistingRectangleList.setDisplayType(RectangleList.DisplayType.POLYGON); mExistingDrawable.invalidateSelf(); final List<Drawable> polygonShapes = mergeRectanglesToPolygonShape( destinationRectangles, color); for (Drawable drawable : polygonShapes) { addToOverlay(drawable); } onAnimationEnd.run(); onAnimationEnd.run(); } } Loading Loading @@ -661,17 +580,12 @@ final class SmartSelectSprite { && y <= rectangle.bottom; && y <= rectangle.bottom; } } private void addToOverlay(final Drawable drawable) { mView.getOverlay().add(drawable); mExistingAnimationDrawables.add(drawable); } private void removeExistingDrawables() { private void removeExistingDrawables() { final ViewOverlay overlay = mView.getOverlay(); final ViewOverlay overlay = mView.getOverlay(); for (Drawable drawable : mExistingAnimationDrawables) { overlay.remove(mExistingDrawable); overlay.remove(drawable); } mExistingDrawable = null; mExistingAnimationDrawables.clear(); mExistingRectangleList = null; } } /** /** Loading