mirror of
https://github.com/zoriya/react-native-svg.git
synced 2026-05-31 05:51:47 +00:00
complete ClipPath element on android
This commit is contained in:
@@ -565,6 +565,7 @@ npm install
|
||||
7. Pattern element
|
||||
8. Image element
|
||||
9. calculate bounding box only if necessary.
|
||||
10. alignment to textAnchor
|
||||
|
||||
#### Thanks:
|
||||
|
||||
|
||||
@@ -9,17 +9,27 @@
|
||||
|
||||
package com.horcrux.svg;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Region;
|
||||
import android.graphics.Path;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
|
||||
/**
|
||||
* Shadow node for virtual RNSVGGroup view
|
||||
*/
|
||||
public class RNSVGGroupShadowNode extends RNSVGVirtualNode {
|
||||
|
||||
private String mAsClipPath = null;
|
||||
|
||||
@ReactProp(name = "asClipPath")
|
||||
public void setAsClipPath(String asClipPath) {
|
||||
mAsClipPath = asClipPath;
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isVirtual() {
|
||||
return true;
|
||||
@@ -29,16 +39,27 @@ public class RNSVGGroupShadowNode extends RNSVGVirtualNode {
|
||||
opacity *= mOpacity;
|
||||
if (opacity > MIN_OPACITY_FOR_DRAW) {
|
||||
saveAndSetupCanvas(canvas);
|
||||
|
||||
clip(canvas, paint);
|
||||
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
RNSVGVirtualNode child = (RNSVGVirtualNode) getChildAt(i);
|
||||
child.draw(canvas, paint, opacity);
|
||||
child.markUpdateSeen();
|
||||
if (mAsClipPath == null) {
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
RNSVGVirtualNode child = (RNSVGVirtualNode) getChildAt(i);
|
||||
child.draw(canvas, paint, opacity);
|
||||
child.markUpdateSeen();
|
||||
}
|
||||
} else {
|
||||
defineClipPath(getPath(canvas), mAsClipPath);
|
||||
}
|
||||
|
||||
restoreCanvas(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Path getPath(Canvas canvas) {
|
||||
Path path = new Path();
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
RNSVGVirtualNode child = (RNSVGVirtualNode) getChildAt(i);
|
||||
path.addPath(child.getPath(canvas));
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,8 +48,6 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
|
||||
|
||||
private static final int FILL_RULE_EVENODD = 0;
|
||||
private static final int FILL_RULE_NONZERO = 1;
|
||||
|
||||
protected Path mPath;
|
||||
private @Nullable ReadableArray mStrokeColor;
|
||||
private @Nullable ReadableArray mFillColor;
|
||||
private @Nullable float[] mStrokeDasharray;
|
||||
@@ -59,14 +57,15 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
|
||||
private int mStrokeLinejoin = JOIN_ROUND;
|
||||
private int mFillRule = FILL_RULE_NONZERO;
|
||||
private boolean mFillRuleSet;
|
||||
protected Path mPath;
|
||||
private boolean mPathSet;
|
||||
private float[] mShapePath;
|
||||
private float[] mD;
|
||||
protected RectF mContentBoundingBox;
|
||||
private Point mPaint;
|
||||
|
||||
@ReactProp(name = "d")
|
||||
public void setPath(@Nullable ReadableArray shapePath) {
|
||||
mShapePath = PropHelper.toFloatArray(shapePath);
|
||||
mD = PropHelper.toFloatArray(shapePath);
|
||||
mPathSet = true;
|
||||
setupPath();
|
||||
markUpdated();
|
||||
@@ -156,20 +155,7 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
|
||||
private void setupPath() {
|
||||
// init path after both fillRule and path have been set
|
||||
if (mFillRuleSet && mPathSet) {
|
||||
Path path = new Path();
|
||||
|
||||
switch (mFillRule) {
|
||||
case FILL_RULE_EVENODD:
|
||||
path.setFillType(Path.FillType.EVEN_ODD);
|
||||
break;
|
||||
case FILL_RULE_NONZERO:
|
||||
break;
|
||||
default:
|
||||
throw new JSApplicationIllegalArgumentException(
|
||||
"fillRule " + mFillRule + " unrecognized");
|
||||
}
|
||||
|
||||
mPath = super.createPath(mShapePath, path);
|
||||
mPath = getPath(null);
|
||||
RectF box = new RectF();
|
||||
mPath.computeBounds(box, true);
|
||||
mContentBoundingBox = box;
|
||||
@@ -328,4 +314,20 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
|
||||
FLog.w(ReactConstants.TAG, "RNSVG: Color type " + colorType + " not supported!");
|
||||
}
|
||||
}
|
||||
|
||||
protected Path getPath(@Nullable Canvas canvas) {
|
||||
Path path = new Path();
|
||||
switch (mFillRule) {
|
||||
case FILL_RULE_EVENODD:
|
||||
path.setFillType(Path.FillType.EVEN_ODD);
|
||||
break;
|
||||
case FILL_RULE_NONZERO:
|
||||
break;
|
||||
default:
|
||||
throw new JSApplicationIllegalArgumentException(
|
||||
"fillRule " + mFillRule + " unrecognized");
|
||||
}
|
||||
super.createPath(mD, path);
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import android.graphics.RectF;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
@@ -40,86 +41,7 @@ public class RNSVGShapeShadowNode extends RNSVGPathShadowNode {
|
||||
public void draw(Canvas canvas, Paint paint, float opacity) {
|
||||
|
||||
if (mShape != null) {
|
||||
int type = mShape.getInt("type");
|
||||
Rect box = canvas.getClipBounds();
|
||||
float height = box.height();
|
||||
float width = box.width();
|
||||
mPath = new Path();
|
||||
|
||||
switch (type) {
|
||||
case 0: {
|
||||
// draw circle
|
||||
// TODO:
|
||||
float cx = getActualProp("cx", width);
|
||||
float cy = getActualProp("cy", height);
|
||||
|
||||
float r;
|
||||
ReadableMap value = mShape.getMap("r");
|
||||
if (value.getBoolean("percentage")) {
|
||||
float percent = (float)value.getDouble("value");
|
||||
float powX = (float)Math.pow((width * percent), 2);
|
||||
float powY = (float)Math.pow((height*percent), 2);
|
||||
r = (float)Math.sqrt(powX + powY) / (float)Math.sqrt(2);
|
||||
} else {
|
||||
r = (float)value.getDouble("value") * mScale;
|
||||
}
|
||||
|
||||
mPath.addCircle(cx, cy, r, Path.Direction.CW);
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
// draw ellipse
|
||||
float cx = getActualProp("cx", width);
|
||||
float cy = getActualProp("cy", height);
|
||||
float rx = getActualProp("rx", width);
|
||||
float ry = getActualProp("ry", height);
|
||||
RectF oval = new RectF(cx - rx, cy - ry, cx + rx, cy + ry);
|
||||
mPath.addOval(oval, Path.Direction.CW);
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// draw line
|
||||
float x1 = getActualProp("x1", width);
|
||||
float y1 = getActualProp("y1", height);
|
||||
float x2 = getActualProp("x2", width);
|
||||
float y2 = getActualProp("y2", height);
|
||||
mPath.moveTo(x1, y1);
|
||||
mPath.lineTo(x2, y2);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
// draw rect
|
||||
float x = getActualProp("x", width);
|
||||
float y = getActualProp("y", height);
|
||||
float w = getActualProp("width", width);
|
||||
float h = getActualProp("height", height);
|
||||
float rx = getActualProp("rx", width);
|
||||
float ry = getActualProp("ry", height);
|
||||
|
||||
if (rx != 0 || ry != 0) {
|
||||
if (rx == 0) {
|
||||
rx = ry;
|
||||
} else if (ry == 0) {
|
||||
ry = rx;
|
||||
}
|
||||
|
||||
if (rx > w / 2) {
|
||||
rx = w / 2;
|
||||
}
|
||||
|
||||
if (ry > h / 2) {
|
||||
ry = h / 2;
|
||||
}
|
||||
mPath.addRoundRect(new RectF(x, y, x + w, y + h), rx, ry, Path.Direction.CW);
|
||||
} else {
|
||||
mPath.addRect(x, y, x + w, y + h, Path.Direction.CW);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
FLog.e(ReactConstants.TAG, "RNSVG: Invalid Shape type " + type + " at " + mShape);
|
||||
}
|
||||
|
||||
mPath = getPath(canvas);
|
||||
RectF shapeBox = new RectF();
|
||||
mPath.computeBounds(shapeBox, true);
|
||||
mContentBoundingBox = shapeBox;
|
||||
@@ -140,4 +62,89 @@ public class RNSVGShapeShadowNode extends RNSVGPathShadowNode {
|
||||
return 0f;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Path getPath(Canvas canvas) {
|
||||
Path path = new Path();
|
||||
|
||||
int type = mShape.getInt("type");
|
||||
Rect box = canvas.getClipBounds();
|
||||
float height = box.height();
|
||||
float width = box.width();
|
||||
|
||||
switch (type) {
|
||||
case 0: {
|
||||
// draw circle
|
||||
// TODO:
|
||||
float cx = getActualProp("cx", width);
|
||||
float cy = getActualProp("cy", height);
|
||||
|
||||
float r;
|
||||
ReadableMap value = mShape.getMap("r");
|
||||
if (value.getBoolean("percentage")) {
|
||||
float percent = (float)value.getDouble("value");
|
||||
float powX = (float)Math.pow((width * percent), 2);
|
||||
float powY = (float)Math.pow((height*percent), 2);
|
||||
r = (float)Math.sqrt(powX + powY) / (float)Math.sqrt(2);
|
||||
} else {
|
||||
r = (float)value.getDouble("value") * mScale;
|
||||
}
|
||||
|
||||
path.addCircle(cx, cy, r, Path.Direction.CW);
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
// draw ellipse
|
||||
float cx = getActualProp("cx", width);
|
||||
float cy = getActualProp("cy", height);
|
||||
float rx = getActualProp("rx", width);
|
||||
float ry = getActualProp("ry", height);
|
||||
RectF oval = new RectF(cx - rx, cy - ry, cx + rx, cy + ry);
|
||||
path.addOval(oval, Path.Direction.CW);
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// draw line
|
||||
float x1 = getActualProp("x1", width);
|
||||
float y1 = getActualProp("y1", height);
|
||||
float x2 = getActualProp("x2", width);
|
||||
float y2 = getActualProp("y2", height);
|
||||
path.moveTo(x1, y1);
|
||||
path.lineTo(x2, y2);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
// draw rect
|
||||
float x = getActualProp("x", width);
|
||||
float y = getActualProp("y", height);
|
||||
float w = getActualProp("width", width);
|
||||
float h = getActualProp("height", height);
|
||||
float rx = getActualProp("rx", width);
|
||||
float ry = getActualProp("ry", height);
|
||||
|
||||
if (rx != 0 || ry != 0) {
|
||||
if (rx == 0) {
|
||||
rx = ry;
|
||||
} else if (ry == 0) {
|
||||
ry = rx;
|
||||
}
|
||||
|
||||
if (rx > w / 2) {
|
||||
rx = w / 2;
|
||||
}
|
||||
|
||||
if (ry > h / 2) {
|
||||
ry = h / 2;
|
||||
}
|
||||
path.addRoundRect(new RectF(x, y, x + w, y + h), rx, ry, Path.Direction.CW);
|
||||
} else {
|
||||
path.addRect(x, y, x + w, y + h, Path.Direction.CW);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
FLog.e(ReactConstants.TAG, "RNSVG: Invalid Shape type " + type + " at " + mShape);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,8 +61,8 @@ public class RNSVGTextShadowNode extends RNSVGPathShadowNode {
|
||||
@ReactProp(name = "path")
|
||||
public void setPath(@Nullable ReadableArray textPath) {
|
||||
float[] pathData = PropHelper.toFloatArray(textPath);
|
||||
Path path = new Path();
|
||||
mPath = super.createPath(pathData, path);
|
||||
mPath = new Path();
|
||||
super.createPath(pathData, mPath);
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,29 +19,35 @@ import android.graphics.Path;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Region;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.uimanager.DisplayMetricsHolder;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.uimanager.ReactShadowNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Base class for RNSVGView virtual nodes: {@link RNSVGGroupShadowNode}, {@link RNSVGPathShadowNode} and
|
||||
* indirectly for {@link RNSVGTextShadowNode}.
|
||||
*/
|
||||
public abstract class RNSVGVirtualNode extends ReactShadowNode {
|
||||
protected static Map<String, Path> CLIP_PATHS = new HashMap<>();
|
||||
|
||||
protected static final float MIN_OPACITY_FOR_DRAW = 0.01f;
|
||||
|
||||
private static final float[] sMatrixData = new float[9];
|
||||
private static final float[] sRawMatrix = new float[9];
|
||||
|
||||
private @Nullable String mDefinedClipPathId;
|
||||
protected float mOpacity = 1f;
|
||||
private @Nullable Matrix mMatrix = new Matrix();
|
||||
|
||||
protected @Nullable Path mClipPath;
|
||||
|
||||
private @Nullable String mClipPathId;
|
||||
private static final int PATH_TYPE_ARC = 4;
|
||||
private static final int PATH_TYPE_CLOSE = 1;
|
||||
private static final int PATH_TYPE_CURVETO = 3;
|
||||
@@ -101,6 +107,12 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode {
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@ReactProp(name = "clipPathId")
|
||||
public void setClipPathId(String clipPathId) {
|
||||
mClipPathId = clipPathId;
|
||||
markUpdated();
|
||||
}
|
||||
|
||||
@ReactProp(name = "clipRule", defaultInt = CLIP_RULE_NONZERO)
|
||||
public void setClipRule(int clipRule) {
|
||||
mClipRule = clipRule;
|
||||
@@ -132,10 +144,10 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode {
|
||||
|
||||
private void setupClip() {
|
||||
if (mClipDataSet && mClipRuleSet) {
|
||||
Path path = new Path();
|
||||
mClipPath = new Path();
|
||||
switch (mClipRule) {
|
||||
case CLIP_RULE_EVENODD:
|
||||
path.setFillType(Path.FillType.EVEN_ODD);
|
||||
mClipPath.setFillType(Path.FillType.EVEN_ODD);
|
||||
break;
|
||||
case CLIP_RULE_NONZERO:
|
||||
break;
|
||||
@@ -144,7 +156,7 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode {
|
||||
"clipRule " + mClipRule + " unrecognized");
|
||||
}
|
||||
|
||||
mClipPath = createPath(mClipData, path);
|
||||
createPath(mClipData, mClipPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,9 +197,9 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode {
|
||||
* 2 (PATH_LINE_TO), x, y. This will draw a line from the last draw point (or 0,0) to x,y.
|
||||
*
|
||||
* @param data the array of instructions
|
||||
* @return the {@link Path} that can be drawn to a canvas
|
||||
* @param path the {@link Path} that can be drawn to a canvas
|
||||
*/
|
||||
protected Path createPath(float[] data, Path path) {
|
||||
protected void createPath(float[] data, Path path) {
|
||||
path.moveTo(0, 0);
|
||||
int i = 0;
|
||||
|
||||
@@ -242,13 +254,32 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode {
|
||||
"Unrecognized drawing instruction " + type);
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
protected void clip(Canvas canvas, Paint paint) {
|
||||
Path clip = null;
|
||||
if (mClipPath != null) {
|
||||
canvas.clipPath(mClipPath, Region.Op.REPLACE);
|
||||
clip = mClipPath;
|
||||
} else if (mClipPathId != null) {
|
||||
clip = CLIP_PATHS.get(mClipPathId);
|
||||
}
|
||||
|
||||
if (clip != null) {
|
||||
canvas.clipPath(clip, Region.Op.REPLACE);
|
||||
canvas.saveLayer(0f, 0f, 0f, 0f, paint, Canvas.CLIP_SAVE_FLAG);
|
||||
}
|
||||
}
|
||||
|
||||
protected void defineClipPath(Path clipPath, String clipPathId) {
|
||||
CLIP_PATHS.put(clipPathId, clipPath);
|
||||
mDefinedClipPathId = clipPathId;
|
||||
}
|
||||
|
||||
protected void finalize() {
|
||||
if (mDefinedClipPathId != null) {
|
||||
CLIP_PATHS.remove(mDefinedClipPathId);
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected Path getPath(Canvas canvas);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user