Skip to content

Commit cf69538

Browse files
ymarianldjcmu
authored andcommitted
Refactor path logic out of MSD
PiperOrigin-RevId: 224182175
1 parent aa807ea commit cf69538

File tree

3 files changed

+302
-171
lines changed

3 files changed

+302
-171
lines changed

lib/java/com/google/android/material/shape/MaterialShapeDrawable.java

Lines changed: 29 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import android.graphics.Paint.Style;
3232
import android.graphics.Path;
3333
import android.graphics.PixelFormat;
34-
import android.graphics.PointF;
3534
import android.graphics.PorterDuff;
3635
import android.graphics.PorterDuff.Mode;
3736
import android.graphics.PorterDuffColorFilter;
@@ -53,6 +52,7 @@
5352
import android.support.annotation.StyleRes;
5453
import com.google.android.material.internal.Experimental;
5554
import com.google.android.material.shadow.ShadowRenderer;
55+
import com.google.android.material.shape.ShapeAppearancePathProvider.PathListener;
5656
import com.google.android.material.shape.ShapePath.ShadowCompatOperation;
5757
import android.support.v4.graphics.drawable.TintAwareDrawable;
5858
import android.util.AttributeSet;
@@ -97,9 +97,6 @@ public class MaterialShapeDrawable extends Drawable implements TintAwareDrawable
9797
private MaterialShapeDrawableState drawableState;
9898

9999
// Inter-method state.
100-
private final Matrix[] cornerTransforms = new Matrix[4];
101-
private final Matrix[] edgeTransforms = new Matrix[4];
102-
private final ShapePath[] cornerPaths = new ShapePath[4];
103100
private final ShadowCompatOperation[] cornerShadowOperation = new ShadowCompatOperation[4];
104101
private final ShadowCompatOperation[] edgeShadowOperation = new ShadowCompatOperation[4];
105102
private boolean pathDirty;
@@ -108,20 +105,18 @@ public class MaterialShapeDrawable extends Drawable implements TintAwareDrawable
108105
private final Matrix matrix = new Matrix();
109106
private final Path path = new Path();
110107
private final Path pathInsetByStroke = new Path();
111-
private final PointF pointF = new PointF();
112108
private final RectF rectF = new RectF();
113109
private final RectF insetRectF = new RectF();
114-
private final ShapePath shapePath = new ShapePath();
115110
private final Region transparentRegion = new Region();
116111
private final Region scratchRegion = new Region();
117-
private final float[] scratch = new float[2];
118-
private final float[] scratch2 = new float[2];
119112
private ShapeAppearanceModel strokeShapeAppearance;
120113

121114
private final Paint fillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
122115
private final Paint strokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
123116

124117
private final ShadowRenderer shadowRenderer = new ShadowRenderer();
118+
private final PathListener pathShadowListener;
119+
private final ShapeAppearancePathProvider pathProvider = new ShapeAppearancePathProvider();
125120

126121
@Nullable private PorterDuffColorFilter tintFilter;
127122
@Nullable private PorterDuffColorFilter strokeTintFilter;
@@ -149,13 +144,21 @@ private MaterialShapeDrawable(MaterialShapeDrawableState drawableState) {
149144
fillPaint.setStyle(Style.FILL);
150145
clearPaint.setColor(Color.WHITE);
151146
clearPaint.setXfermode(new PorterDuffXfermode(Mode.DST_OUT));
152-
for (int i = 0; i < 4; i++) {
153-
cornerTransforms[i] = new Matrix();
154-
edgeTransforms[i] = new Matrix();
155-
cornerPaths[i] = new ShapePath();
156-
}
157147
updateTintFilter();
158148
updateColorsForState(getState(), false);
149+
// Listens to additions of corners and edges, to create the shadow operations.
150+
pathShadowListener =
151+
new PathListener() {
152+
@Override
153+
public void onCornerPathCreated(ShapePath cornerPath, Matrix transform, int count) {
154+
cornerShadowOperation[count] = cornerPath.createShadowCompatOperation(transform);
155+
}
156+
157+
@Override
158+
public void onEdgePathCreated(ShapePath edgePath, Matrix transform, int count) {
159+
edgeShadowOperation[count] = edgePath.createShadowCompatOperation(transform);
160+
}
161+
};
159162
}
160163

161164
@Nullable
@@ -846,10 +849,8 @@ private void drawCompatShadow(Canvas canvas) {
846849

847850
// Draw the fake shadow for each of the corners and edges.
848851
for (int index = 0; index < 4; index++) {
849-
cornerShadowOperation[index].draw(
850-
cornerTransforms[index], shadowRenderer, drawableState.shadowCompatRadius, canvas);
851-
edgeShadowOperation[index].draw(
852-
edgeTransforms[index], shadowRenderer, drawableState.shadowCompatRadius, canvas);
852+
cornerShadowOperation[index].draw(shadowRenderer, drawableState.shadowCompatRadius, canvas);
853+
edgeShadowOperation[index].draw(shadowRenderer, drawableState.shadowCompatRadius, canvas);
853854
}
854855

855856
int shadowOffsetX =
@@ -871,34 +872,13 @@ public void getPathForSize(Rect bounds, Path path) {
871872
calculatePathForSize(new RectF(bounds), path);
872873
}
873874

874-
/**
875-
* Writes to the given {@link Path} for the current edge and corner treatments at the specified
876-
* size.
877-
*
878-
* @param bounds bounds of target path.
879-
* @param path the returned path out-var.
880-
* @return the generated path.
881-
*/
882875
private void calculatePathForSize(RectF bounds, Path path) {
883-
path.rewind();
884-
885-
// Calculate the transformations (rotations and translations) necessary for each edge and
886-
// corner treatment.
887-
for (int index = 0; index < 4; index++) {
888-
setCornerPathAndTransform(index, bounds);
889-
setEdgePathAndTransform(index);
890-
}
891-
892-
// Apply corners and edges to the path in clockwise interleaving sequence: top-right corner,
893-
// right edge, bottom-right corner, bottom edge, bottom-left corner etc. We start from the top
894-
// right corner rather than the top left to work around a bug in API level 21 and 22 in which
895-
// rounding error causes the path to incorrectly be marked as concave.
896-
for (int index = 0; index < 4; index++) {
897-
appendCornerPath(index, path);
898-
appendEdgePath(index, path);
899-
}
900-
901-
path.close();
876+
pathProvider.calculatePath(
877+
drawableState.shapeAppearanceModel,
878+
drawableState.interpolation,
879+
bounds,
880+
pathShadowListener,
881+
path);
902882
}
903883

904884
/** Calculates the path that can be used to draw the stroke entirely inside the shape */
@@ -917,13 +897,11 @@ private void calculateStrokePath() {
917897
adjustCornerSizeForStrokeSize(cornerSizeBottomRight),
918898
adjustCornerSizeForStrokeSize(cornerSizeBottomLeft));
919899

920-
//TODO: Clean up this logic when the ShapeAppearanceModel can calculate the path.
921-
ShapeAppearanceModel tmp = drawableState.shapeAppearanceModel;
922-
drawableState.shapeAppearanceModel = strokeShapeAppearance;
923-
924-
calculatePath(getBoundsInsetByStroke(), pathInsetByStroke);
925-
926-
drawableState.shapeAppearanceModel = tmp;
900+
pathProvider.calculatePath(
901+
strokeShapeAppearance,
902+
drawableState.interpolation,
903+
getBoundsInsetByStroke(),
904+
pathInsetByStroke);
927905
}
928906

929907
private float adjustCornerSizeForStrokeSize(float cornerSize) {
@@ -953,124 +931,6 @@ public void getOutline(Outline outline) {
953931
}
954932
}
955933

956-
private void setCornerPathAndTransform(int index, RectF bounds) {
957-
getCornerTreatmentForIndex(index)
958-
.getCornerPath(90, drawableState.interpolation, cornerPaths[index]);
959-
960-
float edgeAngle = angleOfEdge(index);
961-
cornerTransforms[index].reset();
962-
getCoordinatesOfCorner(index, bounds, pointF);
963-
cornerTransforms[index].setTranslate(pointF.x, pointF.y);
964-
cornerTransforms[index].preRotate(edgeAngle);
965-
}
966-
967-
private void setEdgePathAndTransform(int index) {
968-
scratch[0] = cornerPaths[index].endX;
969-
scratch[1] = cornerPaths[index].endY;
970-
cornerTransforms[index].mapPoints(scratch);
971-
float edgeAngle = angleOfEdge(index);
972-
edgeTransforms[index].reset();
973-
edgeTransforms[index].setTranslate(scratch[0], scratch[1]);
974-
edgeTransforms[index].preRotate(edgeAngle);
975-
}
976-
977-
private void appendCornerPath(int index, Path path) {
978-
scratch[0] = cornerPaths[index].startX;
979-
scratch[1] = cornerPaths[index].startY;
980-
cornerTransforms[index].mapPoints(scratch);
981-
if (index == 0) {
982-
path.moveTo(scratch[0], scratch[1]);
983-
} else {
984-
path.lineTo(scratch[0], scratch[1]);
985-
}
986-
cornerPaths[index].applyToPath(cornerTransforms[index], path);
987-
cornerShadowOperation[index] = cornerPaths[index].createShadowCompatOperation();
988-
}
989-
990-
private void appendEdgePath(int index, Path path) {
991-
int nextIndex = (index + 1) % 4;
992-
scratch[0] = cornerPaths[index].endX;
993-
scratch[1] = cornerPaths[index].endY;
994-
cornerTransforms[index].mapPoints(scratch);
995-
996-
scratch2[0] = cornerPaths[nextIndex].startX;
997-
scratch2[1] = cornerPaths[nextIndex].startY;
998-
cornerTransforms[nextIndex].mapPoints(scratch2);
999-
1000-
float edgeLength = (float) Math.hypot(scratch[0] - scratch2[0], scratch[1] - scratch2[1]);
1001-
float center = getEdgeCenterForIndex(index);
1002-
shapePath.reset(0, 0);
1003-
getEdgeTreatmentForIndex(index)
1004-
.getEdgePath(edgeLength, center, drawableState.interpolation, shapePath);
1005-
shapePath.applyToPath(edgeTransforms[index], path);
1006-
edgeShadowOperation[index] = shapePath.createShadowCompatOperation();
1007-
}
1008-
1009-
private float getEdgeCenterForIndex(int index) {
1010-
scratch[0] = cornerPaths[index].endX;
1011-
scratch[1] = cornerPaths[index].endY;
1012-
cornerTransforms[index].mapPoints(scratch);
1013-
switch (index) {
1014-
case 1:
1015-
case 3:
1016-
return Math.abs(getBoundsAsRectF().centerX() - scratch[0]);
1017-
case 2:
1018-
case 0:
1019-
default:
1020-
return Math.abs(getBoundsAsRectF().centerY() - scratch[1]);
1021-
}
1022-
}
1023-
1024-
private CornerTreatment getCornerTreatmentForIndex(int index) {
1025-
switch (index) {
1026-
case 1:
1027-
return drawableState.shapeAppearanceModel.getBottomRightCorner();
1028-
case 2:
1029-
return drawableState.shapeAppearanceModel.getBottomLeftCorner();
1030-
case 3:
1031-
return drawableState.shapeAppearanceModel.getTopLeftCorner();
1032-
case 0:
1033-
default:
1034-
return drawableState.shapeAppearanceModel.getTopRightCorner();
1035-
}
1036-
}
1037-
1038-
private EdgeTreatment getEdgeTreatmentForIndex(int index) {
1039-
switch (index) {
1040-
case 1:
1041-
return drawableState.shapeAppearanceModel.getBottomEdge();
1042-
case 2:
1043-
return drawableState.shapeAppearanceModel.getLeftEdge();
1044-
case 3:
1045-
return drawableState.shapeAppearanceModel.getTopEdge();
1046-
case 0:
1047-
default:
1048-
return drawableState.shapeAppearanceModel.getRightEdge();
1049-
}
1050-
}
1051-
1052-
private void getCoordinatesOfCorner(int index, RectF bounds, PointF pointF) {
1053-
switch (index) {
1054-
case 1: // bottom-right
1055-
pointF.set(bounds.right, bounds.bottom);
1056-
break;
1057-
case 2: // bottom-left
1058-
pointF.set(bounds.left, bounds.bottom);
1059-
break;
1060-
case 3: // top-left
1061-
pointF.set(bounds.left, bounds.top);
1062-
break;
1063-
case 0: // top-right
1064-
default:
1065-
pointF.set(bounds.right, bounds.top);
1066-
break;
1067-
}
1068-
}
1069-
1070-
private float angleOfEdge(int index) {
1071-
return 90 * (index + 1 % 4);
1072-
}
1073-
1074934
private void calculatePath(RectF bounds, Path path) {
1075935
calculatePathForSize(bounds, path);
1076936
if (drawableState.scale == 1f) {

0 commit comments

Comments
 (0)