complete ClipPath element on android

This commit is contained in:
Horcrux
2016-04-28 01:01:47 +08:00
parent 6da1b5887f
commit f173726b9c
6 changed files with 181 additions and 119 deletions
+1
View File
@@ -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);
}