diff --git a/Example/android/app/BUCK b/Example/android/app/BUCK new file mode 100644 index 00000000..ebd552aa --- /dev/null +++ b/Example/android/app/BUCK @@ -0,0 +1,66 @@ +import re + +# To learn about Buck see [Docs](https://buckbuild.com/). +# To run your application with Buck: +# - install Buck +# - `npm start` - to start the packager +# - `cd android` +# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US` +# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck +# - `buck install -r android/app` - compile, install and run application +# + +lib_deps = [] +for jarfile in glob(['libs/*.jar']): + name = 'jars__' + re.sub(r'^.*/([^/]+)\.jar$', r'\1', jarfile) + lib_deps.append(':' + name) + prebuilt_jar( + name = name, + binary_jar = jarfile, + ) + +for aarfile in glob(['libs/*.aar']): + name = 'aars__' + re.sub(r'^.*/([^/]+)\.aar$', r'\1', aarfile) + lib_deps.append(':' + name) + android_prebuilt_aar( + name = name, + aar = aarfile, + ) + +android_library( + name = 'all-libs', + exported_deps = lib_deps +) + +android_library( + name = 'app-code', + srcs = glob([ + 'src/main/java/**/*.java', + ]), + deps = [ + ':all-libs', + ':build_config', + ':res', + ], +) + +android_build_config( + name = 'build_config', + package = 'com.artsvgexample', +) + +android_resource( + name = 'res', + res = 'src/main/res', + package = 'com.artsvgexample', +) + +android_binary( + name = 'app', + package_type = 'debug', + manifest = 'src/main/AndroidManifest.xml', + keystore = '//android/keystores:debug', + deps = [ + ':app-code', + ], +) diff --git a/Example/android/app/build.gradle b/Example/android/app/build.gradle index e2aa7962..24bd3d85 100644 --- a/Example/android/app/build.gradle +++ b/Example/android/app/build.gradle @@ -9,7 +9,7 @@ import com.android.build.OutputFile * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the * bundle directly from the development server. Below you can see all the possible configurations * and their defaults. If you decide to add a configuration block, make sure to add it before the - * `apply from: "react.gradle"` line. + * `apply from: "../../node_modules/react-native/react.gradle"` line. * * project.ext.react = [ * // the name of the generated asset file containing your JS bundle @@ -26,7 +26,9 @@ import com.android.build.OutputFile * * // whether to bundle JS and assets in another build variant (if configured). * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants - * // The configuration property is in the format 'bundleIn${productFlavor}${buildType}' + * // The configuration property can be in the following formats + * // 'bundleIn${productFlavor}${buildType}' + * // 'bundleIn${buildType}' * // bundleInFreeDebug: true, * // bundleInPaidRelease: true, * // bundleInBeta: true, @@ -57,11 +59,10 @@ import com.android.build.OutputFile * ] */ -apply from: "react.gradle" +apply from: "../../node_modules/react-native/react.gradle" /** - * Set this to true to create three separate APKs instead of one: - * - A universal APK that works on all devices + * Set this to true to create two separate APKs instead of one: * - An APK that only works on ARM devices * - An APK that only works on x86 devices * The advantage is the size of the APK is reduced by about 4MB. @@ -91,9 +92,9 @@ android { } splits { abi { - enable enableSeparateBuildPerCPUArchitecture - universalApk false reset() + enable enableSeparateBuildPerCPUArchitecture + universalApk false // If true, also generate a universal APK include "armeabi-v7a", "x86" } } @@ -119,9 +120,15 @@ android { } dependencies { + compile project(':react-native-svg') compile fileTree(dir: "libs", include: ["*.jar"]) compile "com.android.support:appcompat-v7:23.0.1" - compile "com.facebook.react:react-native:0.20.+" - - compile project(':react-native-svg') + compile "com.facebook.react:react-native:+" // From node_modules +} + +// Run this once to be able to run the application with BUCK +// puts all compile dependencies into folder libs for BUCK to use +task copyDownloadableDepsToLibs(type: Copy) { + from configurations.compile + into 'libs' } diff --git a/Example/android/app/proguard-rules.pro b/Example/android/app/proguard-rules.pro index 7d72e469..347a13ce 100644 --- a/Example/android/app/proguard-rules.pro +++ b/Example/android/app/proguard-rules.pro @@ -61,7 +61,3 @@ -dontwarn java.nio.file.* -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement -dontwarn okio.** - -# stetho - --dontwarn com.facebook.stetho.** diff --git a/Example/android/app/src/main/java/com/artsvgexample/MainActivity.java b/Example/android/app/src/main/java/com/artsvgexample/MainActivity.java index 35faddcc..d41d756e 100644 --- a/Example/android/app/src/main/java/com/artsvgexample/MainActivity.java +++ b/Example/android/app/src/main/java/com/artsvgexample/MainActivity.java @@ -1,12 +1,12 @@ package com.artsvgexample; import com.facebook.react.ReactActivity; +import com.horcrux.svg.RNSvgPackage; import com.facebook.react.ReactPackage; import com.facebook.react.shell.MainReactPackage; import java.util.Arrays; import java.util.List; -import com.horcrux.svg.RNSvgPackage; public class MainActivity extends ReactActivity { @@ -28,15 +28,15 @@ public class MainActivity extends ReactActivity { return BuildConfig.DEBUG; } - /** - * A list of packages used by the app. If the app uses additional views - * or modules besides the default ones, add more packages here. - */ + /** + * A list of packages used by the app. If the app uses additional views + * or modules besides the default ones, add more packages here. + */ @Override protected List getPackages() { - return Arrays.asList( - new RNSvgPackage(), - new MainReactPackage() - ); + return Arrays.asList( + new MainReactPackage(), + new RNSvgPackage() + ); } } diff --git a/Example/android/build.gradle b/Example/android/build.gradle index ccdfc4e3..403a0075 100644 --- a/Example/android/build.gradle +++ b/Example/android/build.gradle @@ -16,5 +16,9 @@ allprojects { repositories { mavenLocal() jcenter() + maven { + // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm + url "$projectDir/../../node_modules/react-native/android" + } } } diff --git a/Example/android/settings.gradle b/Example/android/settings.gradle index 3efb88ac..32b3fe2a 100644 --- a/Example/android/settings.gradle +++ b/Example/android/settings.gradle @@ -1,6 +1,5 @@ rootProject.name = 'ArtSvgExample' include ':app' - include ':react-native-svg' project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android') diff --git a/android/src/main/java/com/horcrux/svg/RNSVGCircleShadowNode.java b/android/src/main/java/com/horcrux/svg/RNSVGCircleShadowNode.java index 4b9bf0ce..bd72ec7e 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGCircleShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RNSVGCircleShadowNode.java @@ -62,19 +62,14 @@ public class RNSVGCircleShadowNode extends RNSVGPathShadowNode { @Override protected Path getPath(Canvas canvas, Paint paint) { Path path = new Path(); - - Rect box = canvas.getClipBounds(); - float height = box.height(); - float width = box.width(); - - float cx = PropHelper.fromPercentageToFloat(mCx, width, 0, mScale); - float cy = PropHelper.fromPercentageToFloat(mCy, height, 0, mScale); + float cx = PropHelper.fromPercentageToFloat(mCx, mWidth, 0, mScale); + float cy = PropHelper.fromPercentageToFloat(mCy, mHeight, 0, mScale); float r; if (PropHelper.isPercentage(mR)) { r = PropHelper.fromPercentageToFloat(mR, 1, 0, 1); - float powX = (float)Math.pow((width * r), 2); - float powY = (float)Math.pow((height * r), 2); + float powX = (float)Math.pow((mWidth * r), 2); + float powY = (float)Math.pow((mHeight * r), 2); r = (float)Math.sqrt(powX + powY) / (float)Math.sqrt(2); } else { r = Float.parseFloat(mR) * mScale; diff --git a/android/src/main/java/com/horcrux/svg/RNSVGEllipseShadowNode.java b/android/src/main/java/com/horcrux/svg/RNSVGEllipseShadowNode.java index c1ea0501..ffb69708 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGEllipseShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RNSVGEllipseShadowNode.java @@ -65,15 +65,11 @@ public class RNSVGEllipseShadowNode extends RNSVGPathShadowNode { protected Path getPath(Canvas canvas, Paint paint) { Path path = new Path(); - Rect box = canvas.getClipBounds(); - float height = box.height(); - float width = box.width(); - // draw ellipse - float cx = PropHelper.fromPercentageToFloat(mCx, width, 0, mScale); - float cy = PropHelper.fromPercentageToFloat(mCy, height, 0, mScale); - float rx = PropHelper.fromPercentageToFloat(mRx, width, 0, mScale); - float ry = PropHelper.fromPercentageToFloat(mRy, height, 0, mScale); + float cx = PropHelper.fromPercentageToFloat(mCx, mWidth, 0, mScale); + float cy = PropHelper.fromPercentageToFloat(mCy, mHeight, 0, mScale); + float rx = PropHelper.fromPercentageToFloat(mRx, mWidth, 0, mScale); + float ry = PropHelper.fromPercentageToFloat(mRy, mHeight, 0, mScale); RectF oval = new RectF(cx - rx, cy - ry, cx + rx, cy + ry); path.addOval(oval, Path.Direction.CW); diff --git a/android/src/main/java/com/horcrux/svg/RNSVGGroupShadowNode.java b/android/src/main/java/com/horcrux/svg/RNSVGGroupShadowNode.java index 5a37a838..8ac948fd 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGGroupShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RNSVGGroupShadowNode.java @@ -12,7 +12,10 @@ package com.horcrux.svg; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; +import android.graphics.Point; import android.util.Log; +import android.view.View; +import android.view.ViewGroup; import com.facebook.react.uimanager.annotations.ReactProp; @@ -29,12 +32,6 @@ public class RNSVGGroupShadowNode extends RNSVGVirtualNode { markUpdated(); } - - @Override - public boolean isVirtual() { - return true; - } - public void draw(Canvas canvas, Paint paint, float opacity) { opacity *= mOpacity; if (opacity > MIN_OPACITY_FOR_DRAW) { @@ -43,6 +40,7 @@ public class RNSVGGroupShadowNode extends RNSVGVirtualNode { if (mAsClipPath == null) { for (int i = 0; i < getChildCount(); i++) { RNSVGVirtualNode child = (RNSVGVirtualNode) getChildAt(i); + child.setDimensions(mWidth, mHeight); child.draw(canvas, paint, opacity); child.markUpdateSeen(); } @@ -62,4 +60,23 @@ public class RNSVGGroupShadowNode extends RNSVGVirtualNode { } return path; } + + @Override + public int hitTest(Point point, View view) { + // TODO: run hit test only if necessary + // TODO: ClipPath never run hitTest + if (mClipPathId == null) { + return -1; + } + + int viewTag = -1; + for (int i = getChildCount() - 1; i >= 0; i--) { + viewTag = ((RNSVGVirtualNode) getChildAt(i)).hitTest(point, ((ViewGroup) view).getChildAt(i)); + if (viewTag != -1) { + break; + } + } + + return viewTag; + } } diff --git a/android/src/main/java/com/horcrux/svg/RNSVGImageShadowNode.java b/android/src/main/java/com/horcrux/svg/RNSVGImageShadowNode.java index d9430698..5f7a29ab 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGImageShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RNSVGImageShadowNode.java @@ -78,6 +78,7 @@ public class RNSVGImageShadowNode extends RNSVGPathShadowNode { bitmap = BitmapFactory.decodeStream(url.openConnection().getInputStream()); } } catch (Exception e) { + Log.e("URI", "" + e); } return bitmap; @@ -131,6 +132,7 @@ public class RNSVGImageShadowNode extends RNSVGPathShadowNode { @Override public void draw(Canvas canvas, Paint paint, float opacity) { canvas.saveLayer(0f, 0f, 0f, 0f, paint, Canvas.ALL_SAVE_FLAG); + Log.e("Count", "" + canvas.getSaveCount()); loadBitmap(getResourceDrawableId(getThemedContext(), null), canvas, paint); } diff --git a/android/src/main/java/com/horcrux/svg/RNSVGLineShadowNode.java b/android/src/main/java/com/horcrux/svg/RNSVGLineShadowNode.java index 0bb68973..da16ae9b 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGLineShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RNSVGLineShadowNode.java @@ -69,14 +69,10 @@ public class RNSVGLineShadowNode extends RNSVGPathShadowNode { protected Path getPath(Canvas canvas, Paint paint) { Path path = new Path(); - Rect box = canvas.getClipBounds(); - float height = box.height(); - float width = box.width(); - - float x1 = PropHelper.fromPercentageToFloat(mX1, width, 0, mScale); - float y1 = PropHelper.fromPercentageToFloat(mY1, height, 0, mScale); - float x2 = PropHelper.fromPercentageToFloat(mX2, width, 0, mScale); - float y2 = PropHelper.fromPercentageToFloat(mY2, height, 0, mScale); + float x1 = PropHelper.fromPercentageToFloat(mX1, mWidth, 0, mScale); + float y1 = PropHelper.fromPercentageToFloat(mY1, mHeight, 0, mScale); + float x2 = PropHelper.fromPercentageToFloat(mX2, mWidth, 0, mScale); + float y2 = PropHelper.fromPercentageToFloat(mY2, mHeight, 0, mScale); path.moveTo(x1, y1); path.lineTo(x2, y2); diff --git a/android/src/main/java/com/horcrux/svg/RNSVGPathShadowNode.java b/android/src/main/java/com/horcrux/svg/RNSVGPathShadowNode.java index d3a2b89c..a13ebfa8 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGPathShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RNSVGPathShadowNode.java @@ -11,6 +11,7 @@ package com.horcrux.svg; import javax.annotation.Nullable; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.DashPathEffect; import android.graphics.Paint; @@ -26,6 +27,8 @@ import android.graphics.Region; import android.graphics.Shader; import android.graphics.Matrix; import android.util.Log; +import android.view.View; +import android.view.ViewGroup; import com.facebook.common.logging.FLog; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; @@ -53,14 +56,14 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode { private @Nullable float[] mStrokeDasharray; private float mStrokeWidth = 1; private float mStrokeDashoffset = 0; - private int mStrokeLinecap = CAP_ROUND; - private int mStrokeLinejoin = JOIN_ROUND; - private int mFillRule = FILL_RULE_NONZERO; + private Paint.Cap mStrokeLinecap = Paint.Cap.ROUND; + private Paint.Join mStrokeLinejoin = Paint.Join.ROUND; + private Path.FillType mFillRule = Path.FillType.WINDING; private boolean mFillRuleSet; protected Path mPath; private boolean mPathSet; private float[] mD; - private Point mPaint; + @ReactProp(name = "d") public void setPath(@Nullable ReadableArray shapePath) { @@ -79,7 +82,17 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode { @ReactProp(name = "fillRule", defaultInt = FILL_RULE_NONZERO) public void setFillRule(int fillRule) { - mFillRule = fillRule; + switch (fillRule) { + case FILL_RULE_EVENODD: + mFillRule = Path.FillType.EVEN_ODD; + break; + case FILL_RULE_NONZERO: + break; + default: + throw new JSApplicationIllegalArgumentException( + "fillRule " + mFillRule + " unrecognized"); + } + mFillRuleSet = true; setupPath(); markUpdated(); @@ -117,13 +130,39 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode { @ReactProp(name = "strokeLinecap", defaultInt = CAP_ROUND) public void setStrokeLinecap(int strokeLinecap) { - mStrokeLinecap = strokeLinecap; + switch (strokeLinecap) { + case CAP_BUTT: + mStrokeLinecap = Paint.Cap.BUTT; + break; + case CAP_SQUARE: + mStrokeLinecap = Paint.Cap.SQUARE; + break; + case CAP_ROUND: + mStrokeLinecap = Paint.Cap.ROUND; + break; + default: + throw new JSApplicationIllegalArgumentException( + "strokeLinecap " + mStrokeLinecap + " unrecognized"); + } markUpdated(); } @ReactProp(name = "strokeLinejoin", defaultInt = JOIN_ROUND) public void setStrokeLinejoin(int strokeLinejoin) { - mStrokeLinejoin = strokeLinejoin; + switch (strokeLinejoin) { + case JOIN_MITER: + mStrokeLinejoin = Paint.Join.MITER; + break; + case JOIN_BEVEL: + mStrokeLinejoin = Paint.Join.BEVEL; + break; + case JOIN_ROUND: + mStrokeLinejoin = Paint.Join.ROUND; + break; + default: + throw new JSApplicationIllegalArgumentException( + "strokeLinejoin " + mStrokeLinejoin + " unrecognized"); + } markUpdated(); } @@ -178,7 +217,7 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode { /** - * Sets up {@link #mPaint} according to the props set on a shadow view. Returns {@code true} + * Sets up paint according to the props set on a shadow view. Returns {@code true} * if the fill should be drawn, {@code false} if not. */ protected boolean setupFillPaint(Paint paint, float opacity, @Nullable RectF box) { @@ -193,7 +232,7 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode { } /** - * Sets up {@link #mPaint} according to the props set on a shadow view. Returns {@code true} + * Sets up paint according to the props set on a shadow view. Returns {@code true} * if the stroke should be drawn, {@code false} if not. */ protected boolean setupStrokePaint(Paint paint, float opacity, @Nullable RectF box) { @@ -203,35 +242,8 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode { paint.reset(); paint.setFlags(Paint.ANTI_ALIAS_FLAG); paint.setStyle(Paint.Style.STROKE); - switch (mStrokeLinecap) { - case CAP_BUTT: - paint.setStrokeCap(Paint.Cap.BUTT); - break; - case CAP_SQUARE: - paint.setStrokeCap(Paint.Cap.SQUARE); - break; - case CAP_ROUND: - paint.setStrokeCap(Paint.Cap.ROUND); - break; - default: - throw new JSApplicationIllegalArgumentException( - "strokeLinecap " + mStrokeLinecap + " unrecognized"); - } - switch (mStrokeLinejoin) { - case JOIN_MITER: - paint.setStrokeJoin(Paint.Join.MITER); - break; - case JOIN_BEVEL: - paint.setStrokeJoin(Paint.Join.BEVEL); - break; - case JOIN_ROUND: - paint.setStrokeJoin(Paint.Join.ROUND); - break; - default: - throw new JSApplicationIllegalArgumentException( - "strokeLinejoin " + mStrokeLinejoin + " unrecognized"); - } - + paint.setStrokeCap(mStrokeLinecap); + paint.setStrokeJoin(mStrokeLinejoin); paint.setStrokeWidth(mStrokeWidth * mScale); setupPaint(paint, opacity, mStrokeColor, box); @@ -312,21 +324,67 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode { // TODO: Support pattern. FLog.w(ReactConstants.TAG, "RNSVG: Color type " + colorType + " not supported!"); } + } protected Path getPath(@Nullable Canvas canvas, @Nullable Paint paint) { 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"); - } + path.setFillType(mFillRule); super.createPath(mD, path); return path; } + + @Override + public int hitTest(Point point, View view) { + Bitmap bitmap = Bitmap.createBitmap( + (int) mWidth, + (int) mHeight, + Bitmap.Config.ARGB_8888); + + Canvas canvas = new Canvas(bitmap); + canvas.concat(mMatrix); + + Paint paint = new Paint(); + clip(canvas, paint); + setHitTestFill(paint); + canvas.drawPath(mPath, paint); + + if (setHitTestStroke(paint)) { + canvas.drawPath(mPath, paint); + } + + canvas.setBitmap(bitmap); + try { + if (bitmap.getPixel(point.x, point.y) != 0) { + return view.getId(); + } + } catch (Exception e) { + return -1; + } finally { + bitmap.recycle(); + } + return -1; + } + + protected void setHitTestFill(Paint paint) { + paint.reset(); + paint.setARGB(255, 0, 0, 0); + paint.setFlags(Paint.ANTI_ALIAS_FLAG); + paint.setStyle(Paint.Style.FILL); + } + + protected boolean setHitTestStroke(Paint paint) { + if (mStrokeWidth == 0) { + return false; + } + + paint.reset(); + paint.setARGB(255, 0, 0, 0); + paint.setFlags(Paint.ANTI_ALIAS_FLAG); + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(mStrokeWidth * mScale); + paint.setStrokeCap(mStrokeLinecap); + paint.setStrokeJoin(mStrokeLinejoin); + return true; + } } diff --git a/android/src/main/java/com/horcrux/svg/RNSVGRectShadowNode.java b/android/src/main/java/com/horcrux/svg/RNSVGRectShadowNode.java index 0c0d2b1b..c1ed87a4 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGRectShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RNSVGRectShadowNode.java @@ -31,9 +31,9 @@ public class RNSVGRectShadowNode extends RNSVGPathShadowNode { private String mY; - private String mWidth; + private String mW; - private String mHeight; + private String mH; private String mRx; @@ -54,14 +54,14 @@ public class RNSVGRectShadowNode extends RNSVGPathShadowNode { @ReactProp(name = "width") public void setWidth(String width) { - mWidth = width; + mW = width; markUpdated(); } @ReactProp(name = "height") public void setHeight(String height) { - mHeight = height; + mH = height; markUpdated(); } @@ -88,16 +88,12 @@ public class RNSVGRectShadowNode extends RNSVGPathShadowNode { protected Path getPath(Canvas canvas, Paint paint) { Path path = new Path(); - Rect box = canvas.getClipBounds(); - float height = box.height(); - float width = box.width(); - - float x = PropHelper.fromPercentageToFloat(mX, width, 0, mScale); - float y = PropHelper.fromPercentageToFloat(mY, height, 0, mScale); - float w = PropHelper.fromPercentageToFloat(mWidth, width, 0, mScale); - float h = PropHelper.fromPercentageToFloat(mHeight, height, 0, mScale); - float rx = PropHelper.fromPercentageToFloat(mRx, width, 0, mScale); - float ry = PropHelper.fromPercentageToFloat(mRy, height, 0, mScale); + float x = PropHelper.fromPercentageToFloat(mX, mWidth, 0, mScale); + float y = PropHelper.fromPercentageToFloat(mY, mHeight, 0, mScale); + float w = PropHelper.fromPercentageToFloat(mW, mWidth, 0, mScale); + float h = PropHelper.fromPercentageToFloat(mH, mHeight, 0, mScale); + float rx = PropHelper.fromPercentageToFloat(mRx, mWidth, 0, mScale); + float ry = PropHelper.fromPercentageToFloat(mRy, mHeight, 0, mScale); if (rx != 0 || ry != 0) { if (rx == 0) { diff --git a/android/src/main/java/com/horcrux/svg/RNSVGRenderableView.java b/android/src/main/java/com/horcrux/svg/RNSVGRenderableView.java new file mode 100644 index 00000000..49721b1f --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/RNSVGRenderableView.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2015-present, Horcrux. + * All rights reserved. + * + * This source code is licensed under the MIT-style license found in the + * LICENSE file in the root directory of this source tree. + */ + + +package com.horcrux.svg; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Point; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; + +import com.facebook.infer.annotation.Assertions; +import com.facebook.react.common.SystemClock; +import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.UIManagerModule; +import com.facebook.react.uimanager.events.EventDispatcher; +import com.facebook.react.uimanager.events.TouchEvent; +import com.facebook.react.uimanager.events.TouchEventType; + +import javax.annotation.Nullable; + +// NativeGestureUtil.notifyNativeGestureStarted + +/** + * Custom {@link View} implementation that draws an RNSVGSvg React view and its \children. + */ +public class RNSVGRenderableView extends ViewGroup { + + public RNSVGRenderableView(Context context) { + super(context); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + + } +} diff --git a/android/src/main/java/com/horcrux/svg/RNSVGRenderableViewManager.java b/android/src/main/java/com/horcrux/svg/RNSVGRenderableViewManager.java index 7e01e899..9f0b6070 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGRenderableViewManager.java +++ b/android/src/main/java/com/horcrux/svg/RNSVGRenderableViewManager.java @@ -9,22 +9,32 @@ package com.horcrux.svg; +import android.util.Log; +import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; //import com.facebook.react.uimanager.ReactStylesDiffMap; +import com.facebook.react.common.SystemClock; +import com.facebook.react.touch.JSResponderHandler; +import com.facebook.react.touch.ReactInterceptingViewGroup; import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.UIManagerModule; +import com.facebook.react.uimanager.ViewGroupManager; import com.facebook.react.uimanager.ViewManager; +import com.facebook.react.uimanager.events.EventDispatcher; +import com.facebook.react.uimanager.events.TouchEvent; /** * ViewManager for all shadowed RNSVG views: Group, Path and Text. Since these never get rendered * into native views and don't need any logic (all the logic is in {@link RNSVGSvgView}), this * "stubbed" ViewManager is used for all of them. */ -public class RNSVGRenderableViewManager extends ViewManager { +public class RNSVGRenderableViewManager extends ViewGroupManager { /* package */ static final String CLASS_GROUP = "RNSVGGroup"; - /* package */ static final String CLASS_SVG = "RNSVGPath"; + /* package */ static final String CLASS_PATH = "RNSVGPath"; /* package */ static final String CLASS_TEXT = "RNSVGText"; /* package */ static final String CLASS_IMAGE = "RNSVGImage"; /* package */ static final String CLASS_CIRCLE = "RNSVGCircle"; @@ -34,12 +44,14 @@ public class RNSVGRenderableViewManager extends ViewManager getShadowNodeClass() { + switch (mClassName) { + case CLASS_GROUP: + return RNSVGGroupShadowNode.class; + case CLASS_PATH: + return RNSVGPathShadowNode.class; + case CLASS_CIRCLE: + return RNSVGCircleShadowNode.class; + case CLASS_ELLIPSE: + return RNSVGEllipseShadowNode.class; + case CLASS_LINE: + return RNSVGLineShadowNode.class; + case CLASS_RECT: + return RNSVGRectShadowNode.class; + case CLASS_TEXT: + return RNSVGTextShadowNode.class; + case CLASS_IMAGE: + return RNSVGImageShadowNode.class; + default: + throw new IllegalStateException("Unexpected type " + mClassName); } } @Override - public Class getShadowNodeClass() { - if (mClassName == CLASS_GROUP) { - return RNSVGGroupShadowNode.class; - } else if (mClassName == CLASS_SVG) { - return RNSVGPathShadowNode.class; - } else if (mClassName == CLASS_CIRCLE) { - return RNSVGCircleShadowNode.class; - } else if (mClassName == CLASS_ELLIPSE) { - return RNSVGEllipseShadowNode.class; - } else if (mClassName == CLASS_LINE) { - return RNSVGLineShadowNode.class; - } else if (mClassName == CLASS_RECT) { - return RNSVGRectShadowNode.class; - } else if (mClassName == CLASS_TEXT) { - return RNSVGTextShadowNode.class; - } else if (mClassName == CLASS_IMAGE) { - return RNSVGImageShadowNode.class; - } else { - throw new IllegalStateException("Unexpected type " + mClassName); - } - } - - @Override - protected View createViewInstance(ThemedReactContext reactContext) { - throw new IllegalStateException("RNSVGPath does not map into a native view"); - } - - @Override - public void updateExtraData(View root, Object extraData) { - throw new IllegalStateException("RNSVGPath does not map into a native view"); + protected ViewGroup createViewInstance(ThemedReactContext reactContext) { + return new RNSVGRenderableView(reactContext); } } diff --git a/android/src/main/java/com/horcrux/svg/RNSVGSvgView.java b/android/src/main/java/com/horcrux/svg/RNSVGSvgView.java index fd42a819..792d4b8a 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGSvgView.java +++ b/android/src/main/java/com/horcrux/svg/RNSVGSvgView.java @@ -14,15 +14,43 @@ import javax.annotation.Nullable; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Point; +import android.util.Log; +import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import com.facebook.infer.annotation.Assertions; +import com.facebook.react.common.SystemClock; +import com.facebook.react.touch.OnInterceptTouchEventListener; +import com.facebook.react.touch.ReactInterceptingViewGroup; +import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.TouchTargetHelper; +import com.facebook.react.uimanager.UIManagerModule; +import com.facebook.react.uimanager.events.TouchEvent; +import com.facebook.react.uimanager.events.TouchEventType; +import com.facebook.react.views.view.ReactClippingViewGroup; +import com.facebook.react.uimanager.events.EventDispatcher; +import com.facebook.react.uimanager.events.NativeGestureUtil; + +// NativeGestureUtil.notifyNativeGestureStarted /** - * Custom {@link View} implementation that draws an RNSVGSvg React view and its children. + * Custom {@link View} implementation that draws an RNSVGSvg React view and its \children. */ -public class RNSVGSvgView extends View { +public class RNSVGSvgView extends ViewGroup { private @Nullable Bitmap mBitmap; + private RNSVGSvgViewShadowNode mSvgViewShadowNode; + + private int mTargetTag; + + public RNSVGSvgView(Context context, RNSVGSvgViewShadowNode shadowNode) { + super(context); + mSvgViewShadowNode = shadowNode; + } + public RNSVGSvgView(Context context) { super(context); } @@ -42,4 +70,113 @@ public class RNSVGSvgView extends View { canvas.drawBitmap(mBitmap, 0, 0, null); } } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + mTargetTag = mSvgViewShadowNode.hitTest(new Point((int) event.getX(), (int) event.getY()), this); + if (mTargetTag != -1) { + EventDispatcher eventDispatcher = ((ThemedReactContext) this.getContext()).getNativeModule(UIManagerModule.class).getEventDispatcher(); + handleTouchEvent(event, eventDispatcher); + return true; + } + + return super.dispatchTouchEvent(event); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + + } + + public void handleTouchEvent(MotionEvent ev, EventDispatcher eventDispatcher) { + int action = ev.getAction() & MotionEvent.ACTION_MASK; + if (action == MotionEvent.ACTION_DOWN) { + eventDispatcher.dispatchEvent( + TouchEvent.obtain( + mTargetTag, + SystemClock.nanoTime(), + TouchEventType.START, + ev, + ev.getX(), + ev.getX())); + } else if (mTargetTag == -1) { + // All the subsequent action types are expected to be called after ACTION_DOWN thus target + // is supposed to be set for them. + Log.e( + "error", + "Unexpected state: received touch event but didn't get starting ACTION_DOWN for this " + + "gesture before"); + } else if (action == MotionEvent.ACTION_UP) { + // End of the gesture. We reset target tag to -1 and expect no further event associated with + // this gesture. + eventDispatcher.dispatchEvent( + TouchEvent.obtain( + mTargetTag, + SystemClock.nanoTime(), + TouchEventType.END, + ev, + ev.getX(), + ev.getY())); + mTargetTag = -1; + } else if (action == MotionEvent.ACTION_MOVE) { + // Update pointer position for current gesture + eventDispatcher.dispatchEvent( + TouchEvent.obtain( + mTargetTag, + SystemClock.nanoTime(), + TouchEventType.MOVE, + ev, + ev.getX(), + ev.getY())); + } else if (action == MotionEvent.ACTION_POINTER_DOWN) { + // New pointer goes down, this can only happen after ACTION_DOWN is sent for the first pointer + eventDispatcher.dispatchEvent( + TouchEvent.obtain( + mTargetTag, + SystemClock.nanoTime(), + TouchEventType.START, + ev, + ev.getX(), + ev.getY())); + } else if (action == MotionEvent.ACTION_POINTER_UP) { + // Exactly onw of the pointers goes up + eventDispatcher.dispatchEvent( + TouchEvent.obtain( + mTargetTag, + SystemClock.nanoTime(), + TouchEventType.END, + ev, + ev.getX(), + ev.getY())); + } else if (action == MotionEvent.ACTION_CANCEL) { + dispatchCancelEvent(ev, eventDispatcher); + mTargetTag = -1; + } else { + Log.w( + "IGNORE", + "Warning : touch event was ignored. Action=" + action + " Target=" + mTargetTag); + } + } + + private void dispatchCancelEvent(MotionEvent androidEvent, EventDispatcher eventDispatcher) { + // This means the gesture has already ended, via some other CANCEL or UP event. This is not + // expected to happen very often as it would mean some child View has decided to intercept the + // touch stream and start a native gesture only upon receiving the UP/CANCEL event. + if (mTargetTag == -1) { + Log.w( + "error", + "Can't cancel already finished gesture. Is a child View trying to start a gesture from " + + "an UP/CANCEL event?"); + return; + } + + Assertions.assertNotNull(eventDispatcher).dispatchEvent( + TouchEvent.obtain( + mTargetTag, + SystemClock.nanoTime(), + TouchEventType.CANCEL, + androidEvent, + androidEvent.getX(), + androidEvent.getY())); + } } diff --git a/android/src/main/java/com/horcrux/svg/RNSVGSvgViewManager.java b/android/src/main/java/com/horcrux/svg/RNSVGSvgViewManager.java index 48c4855f..5c5e388a 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGSvgViewManager.java +++ b/android/src/main/java/com/horcrux/svg/RNSVGSvgViewManager.java @@ -10,27 +10,27 @@ package com.horcrux.svg; import android.graphics.Bitmap; +import android.util.Log; import com.facebook.csslayout.CSSNode; import com.facebook.csslayout.MeasureOutput; import com.facebook.react.uimanager.BaseViewManager; import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.ViewGroupManager; + +import java.util.ArrayList; /** * ViewManager for RNSVGSvgView React views. Renders as a {@link RNSVGSvgView} and handles * invalidating the native view on shadow view updates happening in the underlying tree. */ -public class RNSVGSvgViewManager extends - BaseViewManager { +public class RNSVGSvgViewManager extends ViewGroupManager { private static final String REACT_CLASS = "RNSVGSvgView"; - private static final CSSNode.MeasureFunction MEASURE_FUNCTION = new CSSNode.MeasureFunction() { - @Override - public void measure(CSSNode node, float width, float height, MeasureOutput measureOutput) { - throw new IllegalStateException("SvgView should have explicit width and height set"); - } - }; + // TODO: use an ArrayList to connect RNSVGSvgViewShadowNode with RNSVGSvgView, not sure if there will be a race condition. + // TODO: find a better way to replace this + private ArrayList SvgShadowNodes = new ArrayList<>(); @Override public String getName() { @@ -40,7 +40,7 @@ public class RNSVGSvgViewManager extends @Override public RNSVGSvgViewShadowNode createShadowNodeInstance() { RNSVGSvgViewShadowNode node = new RNSVGSvgViewShadowNode(); - node.setMeasureFunction(MEASURE_FUNCTION); + SvgShadowNodes.add(node); return node; } @@ -51,7 +51,9 @@ public class RNSVGSvgViewManager extends @Override protected RNSVGSvgView createViewInstance(ThemedReactContext reactContext) { - return new RNSVGSvgView(reactContext); + RNSVGSvgViewShadowNode shadowNode = SvgShadowNodes.get(0); + SvgShadowNodes.remove(0); + return new RNSVGSvgView(reactContext, shadowNode); } @Override diff --git a/android/src/main/java/com/horcrux/svg/RNSVGSvgViewShadowNode.java b/android/src/main/java/com/horcrux/svg/RNSVGSvgViewShadowNode.java index 64b9e172..96d9fca4 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGSvgViewShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RNSVGSvgViewShadowNode.java @@ -12,6 +12,9 @@ package com.horcrux.svg; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.Point; +import android.util.Log; +import android.view.ViewGroup; import com.facebook.react.uimanager.LayoutShadowNode; import com.facebook.react.uimanager.UIViewOperationQueue; @@ -20,17 +23,6 @@ import com.facebook.react.uimanager.UIViewOperationQueue; * Shadow node for RNSVG virtual tree root - RNSVGSvgView */ public class RNSVGSvgViewShadowNode extends LayoutShadowNode { - - @Override - public boolean isVirtual() { - return false; - } - - @Override - public boolean isVirtualAnchor() { - return true; - } - @Override public void onCollectExtraUpdates(UIViewOperationQueue uiUpdater) { super.onCollectExtraUpdates(uiUpdater); @@ -40,17 +32,35 @@ public class RNSVGSvgViewShadowNode extends LayoutShadowNode { private Object drawOutput() { // TODO(7255985): Use TextureView and pass Svg from the view to draw on it asynchronously // instead of passing the bitmap (which is inefficient especially in terms of memory usage) + float width = (int) getLayoutWidth(); + float height = (int) getLayoutHeight(); Bitmap bitmap = Bitmap.createBitmap( - (int) getLayoutWidth(), - (int) getLayoutHeight(), + (int) width, + (int) height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); Paint paint = new Paint(); + for (int i = 0; i < getChildCount(); i++) { RNSVGVirtualNode child = (RNSVGVirtualNode) getChildAt(i); + child.setDimensions(width, height); child.draw(canvas, paint, 1f); child.markUpdateSeen(); } + return bitmap; } + + public int hitTest(Point point, ViewGroup view) { + int count = getChildCount(); + int viewTag = -1; + for (int i = count - 1; i >= 0; i--) { + viewTag = ((RNSVGVirtualNode) getChildAt(i)).hitTest(point, view.getChildAt(i)); + if (viewTag != -1) { + break; + } + } + + return viewTag; + } } diff --git a/android/src/main/java/com/horcrux/svg/RNSVGTextShadowNode.java b/android/src/main/java/com/horcrux/svg/RNSVGTextShadowNode.java index 18676429..5c591639 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGTextShadowNode.java +++ b/android/src/main/java/com/horcrux/svg/RNSVGTextShadowNode.java @@ -11,14 +11,18 @@ package com.horcrux.svg; import javax.annotation.Nullable; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; import android.text.TextUtils; import android.util.Log; +import android.view.View; +import android.view.ViewGroup; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; @@ -84,26 +88,26 @@ public class RNSVGTextShadowNode extends RNSVGPathShadowNode { RectF box = getBox(paint, text); if (setupStrokePaint(paint, opacity, box)) { - applyTextPropertiesToPaint(paint); - if (mPath == null) { - canvas.drawText(text, 0, -paint.ascent(), paint); - } else { - canvas.drawTextOnPath(text, mPath, 0, -paint.ascent(), paint); - } + drawText(canvas, paint, text); } if (setupFillPaint(paint, opacity, box)) { - applyTextPropertiesToPaint(paint); - - if (mPath == null) { - canvas.drawText(text, 0, -paint.ascent(), paint); - } else { - canvas.drawTextOnPath(text, mPath, 0, -paint.ascent(), paint); - } + drawText(canvas, paint, text); } + restoreCanvas(canvas); markUpdateSeen(); } + private void drawText(Canvas canvas, Paint paint, String text) { + applyTextPropertiesToPaint(paint); + + if (mPath == null) { + canvas.drawText(text, 0, -paint.ascent(), paint); + } else { + canvas.drawTextOnPath(text, mPath, 0, -paint.ascent(), paint); + } + } + private String formatText() { if (mFrame == null || !mFrame.hasKey(PROP_LINES)) { return null; @@ -170,6 +174,8 @@ public class RNSVGTextShadowNode extends RNSVGPathShadowNode { } } + + @Override protected Path getPath(Canvas canvas, Paint paint) { Path path = new Path(); @@ -178,6 +184,7 @@ public class RNSVGTextShadowNode extends RNSVGPathShadowNode { return path; } + // TODO: get path while TextPath is set. if (setupFillPaint(paint, 1.0f, getBox(paint, text))) { applyTextPropertiesToPaint(paint); paint.getTextPath(text, 0, text.length(), 0, -paint.ascent(), path); @@ -186,4 +193,43 @@ public class RNSVGTextShadowNode extends RNSVGPathShadowNode { return path; } + + @Override + public int hitTest(Point point, View view) { + Bitmap bitmap = Bitmap.createBitmap( + (int) mWidth, + (int) mHeight, + Bitmap.Config.ARGB_8888); + + Canvas canvas = new Canvas(bitmap); + canvas.concat(mMatrix); + + // todo clip detect + + String text = formatText(); + if (text == null) { + return -1; + } + + Paint paint = new Paint(); + clip(canvas, paint); + setHitTestFill(paint); + drawText(canvas, paint, text); + + if (setHitTestStroke(paint)) { + drawText(canvas, paint, text); + } + + canvas.setBitmap(bitmap); + try { + if (bitmap.getPixel(point.x, point.y) != 0) { + return view.getId(); + } + } catch (Exception e) { + return -1; + } finally { + bitmap.recycle(); + } + return -1; + } } diff --git a/android/src/main/java/com/horcrux/svg/RNSVGVirtualNode.java b/android/src/main/java/com/horcrux/svg/RNSVGVirtualNode.java index 7598073d..676686d6 100644 --- a/android/src/main/java/com/horcrux/svg/RNSVGVirtualNode.java +++ b/android/src/main/java/com/horcrux/svg/RNSVGVirtualNode.java @@ -16,19 +16,21 @@ import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; +import android.graphics.Point; import android.graphics.RectF; import android.graphics.Region; import android.util.Log; import android.view.View; +import android.view.ViewGroup; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.uimanager.DisplayMetricsHolder; +import com.facebook.react.uimanager.LayoutShadowNode; 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; @@ -36,18 +38,19 @@ 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 { +public abstract class RNSVGVirtualNode extends LayoutShadowNode { protected static Map 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; protected @Nullable Matrix mMatrix = new Matrix(); + + private @Nullable String mDefinedClipPathId; protected @Nullable Path mClipPath; - private @Nullable String mClipPathId; + protected @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; @@ -62,15 +65,13 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode { private boolean mClipRuleSet; private boolean mClipDataSet; + protected float mWidth = 0; + protected float mHeight = 0; + public RNSVGVirtualNode() { mScale = DisplayMetricsHolder.getWindowDisplayMetrics().density; } - @Override - public boolean isVirtual() { - return true; - } - public abstract void draw(Canvas canvas, Paint paint, float opacity); /** @@ -127,8 +128,8 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode { markUpdated(); } - @ReactProp(name = "transform") - public void setTransform(@Nullable ReadableArray transformArray) { + @ReactProp(name = "trans") + public void setTrans(@Nullable ReadableArray transformArray) { if (transformArray != null) { int matrixSize = PropHelper.toFloatArray(transformArray, sMatrixData); if (matrixSize == 6) { @@ -155,7 +156,6 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode { throw new JSApplicationIllegalArgumentException( "clipRule " + mClipRule + " unrecognized"); } - createPath(mClipData, mClipPath); } } @@ -204,7 +204,6 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode { int i = 0; while (i < data.length) { - int type = (int) data[i++]; switch (type) { case PATH_TYPE_MOVETO: @@ -270,16 +269,23 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode { } } + public void setDimensions(float width, float height) { + mWidth = width; + mHeight = height; + } + + abstract public int hitTest(Point point, View view); + protected void defineClipPath(Path clipPath, String clipPathId) { CLIP_PATHS.put(clipPathId, clipPath); mDefinedClipPathId = clipPathId; } + abstract protected Path getPath(Canvas canvas, Paint paint); + protected void finalize() { if (mDefinedClipPathId != null) { CLIP_PATHS.remove(mDefinedClipPathId); } } - - abstract protected Path getPath(Canvas canvas, Paint paint); } diff --git a/elements/Svg.js b/elements/Svg.js index 4c94190f..9cac9307 100644 --- a/elements/Svg.js +++ b/elements/Svg.js @@ -1,10 +1,16 @@ import React, {Children, Component, cloneElement, PropTypes} from 'react'; -import {View, requireNativeComponent} from 'react-native'; +import {View, requireNativeComponent, StyleSheet} from 'react-native'; import ViewBox from './ViewBox'; // Svg - Root node of all Svg elements let id = 0; +const styles = StyleSheet.create({ + svg: { + backgroundColor: 'transparent' + } +}); + class Svg extends Component{ static displayName = 'Svg'; static propTypes = { @@ -75,6 +81,7 @@ class Svg extends Component{ preserveAspectRatio={null} ref={ele => this.root = ele} style={[ + styles.svg, props.style, !isNaN(opacity) && { opacity