add touch responder system on android

This commit is contained in:
Horcrux
2016-05-18 17:33:54 +08:00
parent 75e03f87e9
commit ce74efc58d
21 changed files with 629 additions and 223 deletions

66
Example/android/app/BUCK Normal file
View File

@@ -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',
],
)

View File

@@ -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 * 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 * 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 * 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 = [ * project.ext.react = [
* // the name of the generated asset file containing your JS bundle * // 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). * // 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 * // 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, * // bundleInFreeDebug: true,
* // bundleInPaidRelease: true, * // bundleInPaidRelease: true,
* // bundleInBeta: 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: * Set this to true to create two separate APKs instead of one:
* - A universal APK that works on all devices
* - An APK that only works on ARM devices * - An APK that only works on ARM devices
* - An APK that only works on x86 devices * - An APK that only works on x86 devices
* The advantage is the size of the APK is reduced by about 4MB. * The advantage is the size of the APK is reduced by about 4MB.
@@ -91,9 +92,9 @@ android {
} }
splits { splits {
abi { abi {
enable enableSeparateBuildPerCPUArchitecture
universalApk false
reset() reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false // If true, also generate a universal APK
include "armeabi-v7a", "x86" include "armeabi-v7a", "x86"
} }
} }
@@ -119,9 +120,15 @@ android {
} }
dependencies { dependencies {
compile project(':react-native-svg')
compile fileTree(dir: "libs", include: ["*.jar"]) compile fileTree(dir: "libs", include: ["*.jar"])
compile "com.android.support:appcompat-v7:23.0.1" compile "com.android.support:appcompat-v7:23.0.1"
compile "com.facebook.react:react-native:0.20.+" compile "com.facebook.react:react-native:+" // From node_modules
}
compile project(':react-native-svg')
// 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'
} }

View File

@@ -61,7 +61,3 @@
-dontwarn java.nio.file.* -dontwarn java.nio.file.*
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-dontwarn okio.** -dontwarn okio.**
# stetho
-dontwarn com.facebook.stetho.**

View File

@@ -1,12 +1,12 @@
package com.artsvgexample; package com.artsvgexample;
import com.facebook.react.ReactActivity; import com.facebook.react.ReactActivity;
import com.horcrux.svg.RNSvgPackage;
import com.facebook.react.ReactPackage; import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage; import com.facebook.react.shell.MainReactPackage;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import com.horcrux.svg.RNSvgPackage;
public class MainActivity extends ReactActivity { public class MainActivity extends ReactActivity {
@@ -35,8 +35,8 @@ public class MainActivity extends ReactActivity {
@Override @Override
protected List<ReactPackage> getPackages() { protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList( return Arrays.<ReactPackage>asList(
new RNSvgPackage(), new MainReactPackage(),
new MainReactPackage() new RNSvgPackage()
); );
} }
} }

View File

@@ -16,5 +16,9 @@ allprojects {
repositories { repositories {
mavenLocal() mavenLocal()
jcenter() jcenter()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$projectDir/../../node_modules/react-native/android"
}
} }
} }

View File

@@ -1,6 +1,5 @@
rootProject.name = 'ArtSvgExample' rootProject.name = 'ArtSvgExample'
include ':app' include ':app'
include ':react-native-svg' include ':react-native-svg'
project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android') project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android')

View File

@@ -62,19 +62,14 @@ public class RNSVGCircleShadowNode extends RNSVGPathShadowNode {
@Override @Override
protected Path getPath(Canvas canvas, Paint paint) { protected Path getPath(Canvas canvas, Paint paint) {
Path path = new Path(); Path path = new Path();
float cx = PropHelper.fromPercentageToFloat(mCx, mWidth, 0, mScale);
Rect box = canvas.getClipBounds(); float cy = PropHelper.fromPercentageToFloat(mCy, mHeight, 0, mScale);
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 r; float r;
if (PropHelper.isPercentage(mR)) { if (PropHelper.isPercentage(mR)) {
r = PropHelper.fromPercentageToFloat(mR, 1, 0, 1); r = PropHelper.fromPercentageToFloat(mR, 1, 0, 1);
float powX = (float)Math.pow((width * r), 2); float powX = (float)Math.pow((mWidth * r), 2);
float powY = (float)Math.pow((height * r), 2); float powY = (float)Math.pow((mHeight * r), 2);
r = (float)Math.sqrt(powX + powY) / (float)Math.sqrt(2); r = (float)Math.sqrt(powX + powY) / (float)Math.sqrt(2);
} else { } else {
r = Float.parseFloat(mR) * mScale; r = Float.parseFloat(mR) * mScale;

View File

@@ -65,15 +65,11 @@ public class RNSVGEllipseShadowNode extends RNSVGPathShadowNode {
protected Path getPath(Canvas canvas, Paint paint) { protected Path getPath(Canvas canvas, Paint paint) {
Path path = new Path(); Path path = new Path();
Rect box = canvas.getClipBounds();
float height = box.height();
float width = box.width();
// draw ellipse // draw ellipse
float cx = PropHelper.fromPercentageToFloat(mCx, width, 0, mScale); float cx = PropHelper.fromPercentageToFloat(mCx, mWidth, 0, mScale);
float cy = PropHelper.fromPercentageToFloat(mCy, height, 0, mScale); float cy = PropHelper.fromPercentageToFloat(mCy, mHeight, 0, mScale);
float rx = PropHelper.fromPercentageToFloat(mRx, width, 0, mScale); float rx = PropHelper.fromPercentageToFloat(mRx, mWidth, 0, mScale);
float ry = PropHelper.fromPercentageToFloat(mRy, height, 0, mScale); float ry = PropHelper.fromPercentageToFloat(mRy, mHeight, 0, mScale);
RectF oval = new RectF(cx - rx, cy - ry, cx + rx, cy + ry); RectF oval = new RectF(cx - rx, cy - ry, cx + rx, cy + ry);
path.addOval(oval, Path.Direction.CW); path.addOval(oval, Path.Direction.CW);

View File

@@ -12,7 +12,10 @@ package com.horcrux.svg;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Path; import android.graphics.Path;
import android.graphics.Point;
import android.util.Log; import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.annotations.ReactProp;
@@ -29,12 +32,6 @@ public class RNSVGGroupShadowNode extends RNSVGVirtualNode {
markUpdated(); markUpdated();
} }
@Override
public boolean isVirtual() {
return true;
}
public void draw(Canvas canvas, Paint paint, float opacity) { public void draw(Canvas canvas, Paint paint, float opacity) {
opacity *= mOpacity; opacity *= mOpacity;
if (opacity > MIN_OPACITY_FOR_DRAW) { if (opacity > MIN_OPACITY_FOR_DRAW) {
@@ -43,6 +40,7 @@ public class RNSVGGroupShadowNode extends RNSVGVirtualNode {
if (mAsClipPath == null) { if (mAsClipPath == null) {
for (int i = 0; i < getChildCount(); i++) { for (int i = 0; i < getChildCount(); i++) {
RNSVGVirtualNode child = (RNSVGVirtualNode) getChildAt(i); RNSVGVirtualNode child = (RNSVGVirtualNode) getChildAt(i);
child.setDimensions(mWidth, mHeight);
child.draw(canvas, paint, opacity); child.draw(canvas, paint, opacity);
child.markUpdateSeen(); child.markUpdateSeen();
} }
@@ -62,4 +60,23 @@ public class RNSVGGroupShadowNode extends RNSVGVirtualNode {
} }
return path; 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;
}
} }

View File

@@ -78,6 +78,7 @@ public class RNSVGImageShadowNode extends RNSVGPathShadowNode {
bitmap = BitmapFactory.decodeStream(url.openConnection().getInputStream()); bitmap = BitmapFactory.decodeStream(url.openConnection().getInputStream());
} }
} catch (Exception e) { } catch (Exception e) {
Log.e("URI", "" + e);
} }
return bitmap; return bitmap;
@@ -131,6 +132,7 @@ public class RNSVGImageShadowNode extends RNSVGPathShadowNode {
@Override @Override
public void draw(Canvas canvas, Paint paint, float opacity) { public void draw(Canvas canvas, Paint paint, float opacity) {
canvas.saveLayer(0f, 0f, 0f, 0f, paint, Canvas.ALL_SAVE_FLAG); canvas.saveLayer(0f, 0f, 0f, 0f, paint, Canvas.ALL_SAVE_FLAG);
Log.e("Count", "" + canvas.getSaveCount());
loadBitmap(getResourceDrawableId(getThemedContext(), null), canvas, paint); loadBitmap(getResourceDrawableId(getThemedContext(), null), canvas, paint);
} }

View File

@@ -69,14 +69,10 @@ public class RNSVGLineShadowNode extends RNSVGPathShadowNode {
protected Path getPath(Canvas canvas, Paint paint) { protected Path getPath(Canvas canvas, Paint paint) {
Path path = new Path(); Path path = new Path();
Rect box = canvas.getClipBounds(); float x1 = PropHelper.fromPercentageToFloat(mX1, mWidth, 0, mScale);
float height = box.height(); float y1 = PropHelper.fromPercentageToFloat(mY1, mHeight, 0, mScale);
float width = box.width(); float x2 = PropHelper.fromPercentageToFloat(mX2, mWidth, 0, mScale);
float y2 = PropHelper.fromPercentageToFloat(mY2, mHeight, 0, mScale);
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);
path.moveTo(x1, y1); path.moveTo(x1, y1);
path.lineTo(x2, y2); path.lineTo(x2, y2);

View File

@@ -11,6 +11,7 @@ package com.horcrux.svg;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.DashPathEffect; import android.graphics.DashPathEffect;
import android.graphics.Paint; import android.graphics.Paint;
@@ -26,6 +27,8 @@ import android.graphics.Region;
import android.graphics.Shader; import android.graphics.Shader;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.util.Log; import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.common.logging.FLog; import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
@@ -53,14 +56,14 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
private @Nullable float[] mStrokeDasharray; private @Nullable float[] mStrokeDasharray;
private float mStrokeWidth = 1; private float mStrokeWidth = 1;
private float mStrokeDashoffset = 0; private float mStrokeDashoffset = 0;
private int mStrokeLinecap = CAP_ROUND; private Paint.Cap mStrokeLinecap = Paint.Cap.ROUND;
private int mStrokeLinejoin = JOIN_ROUND; private Paint.Join mStrokeLinejoin = Paint.Join.ROUND;
private int mFillRule = FILL_RULE_NONZERO; private Path.FillType mFillRule = Path.FillType.WINDING;
private boolean mFillRuleSet; private boolean mFillRuleSet;
protected Path mPath; protected Path mPath;
private boolean mPathSet; private boolean mPathSet;
private float[] mD; private float[] mD;
private Point mPaint;
@ReactProp(name = "d") @ReactProp(name = "d")
public void setPath(@Nullable ReadableArray shapePath) { public void setPath(@Nullable ReadableArray shapePath) {
@@ -79,7 +82,17 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
@ReactProp(name = "fillRule", defaultInt = FILL_RULE_NONZERO) @ReactProp(name = "fillRule", defaultInt = FILL_RULE_NONZERO)
public void setFillRule(int fillRule) { 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; mFillRuleSet = true;
setupPath(); setupPath();
markUpdated(); markUpdated();
@@ -117,13 +130,39 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
@ReactProp(name = "strokeLinecap", defaultInt = CAP_ROUND) @ReactProp(name = "strokeLinecap", defaultInt = CAP_ROUND)
public void setStrokeLinecap(int strokeLinecap) { 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(); markUpdated();
} }
@ReactProp(name = "strokeLinejoin", defaultInt = JOIN_ROUND) @ReactProp(name = "strokeLinejoin", defaultInt = JOIN_ROUND)
public void setStrokeLinejoin(int strokeLinejoin) { 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(); 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. * if the fill should be drawn, {@code false} if not.
*/ */
protected boolean setupFillPaint(Paint paint, float opacity, @Nullable RectF box) { 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. * if the stroke should be drawn, {@code false} if not.
*/ */
protected boolean setupStrokePaint(Paint paint, float opacity, @Nullable RectF box) { protected boolean setupStrokePaint(Paint paint, float opacity, @Nullable RectF box) {
@@ -203,35 +242,8 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
paint.reset(); paint.reset();
paint.setFlags(Paint.ANTI_ALIAS_FLAG); paint.setFlags(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.STROKE); paint.setStyle(Paint.Style.STROKE);
switch (mStrokeLinecap) { paint.setStrokeCap(mStrokeLinecap);
case CAP_BUTT: paint.setStrokeJoin(mStrokeLinejoin);
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.setStrokeWidth(mStrokeWidth * mScale); paint.setStrokeWidth(mStrokeWidth * mScale);
setupPaint(paint, opacity, mStrokeColor, box); setupPaint(paint, opacity, mStrokeColor, box);
@@ -312,21 +324,67 @@ public class RNSVGPathShadowNode extends RNSVGVirtualNode {
// TODO: Support pattern. // TODO: Support pattern.
FLog.w(ReactConstants.TAG, "RNSVG: Color type " + colorType + " not supported!"); FLog.w(ReactConstants.TAG, "RNSVG: Color type " + colorType + " not supported!");
} }
} }
protected Path getPath(@Nullable Canvas canvas, @Nullable Paint paint) { protected Path getPath(@Nullable Canvas canvas, @Nullable Paint paint) {
Path path = new Path(); Path path = new Path();
switch (mFillRule) { path.setFillType(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); super.createPath(mD, path);
return 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;
}
} }

View File

@@ -31,9 +31,9 @@ public class RNSVGRectShadowNode extends RNSVGPathShadowNode {
private String mY; private String mY;
private String mWidth; private String mW;
private String mHeight; private String mH;
private String mRx; private String mRx;
@@ -54,14 +54,14 @@ public class RNSVGRectShadowNode extends RNSVGPathShadowNode {
@ReactProp(name = "width") @ReactProp(name = "width")
public void setWidth(String width) { public void setWidth(String width) {
mWidth = width; mW = width;
markUpdated(); markUpdated();
} }
@ReactProp(name = "height") @ReactProp(name = "height")
public void setHeight(String height) { public void setHeight(String height) {
mHeight = height; mH = height;
markUpdated(); markUpdated();
} }
@@ -88,16 +88,12 @@ public class RNSVGRectShadowNode extends RNSVGPathShadowNode {
protected Path getPath(Canvas canvas, Paint paint) { protected Path getPath(Canvas canvas, Paint paint) {
Path path = new Path(); Path path = new Path();
Rect box = canvas.getClipBounds(); float x = PropHelper.fromPercentageToFloat(mX, mWidth, 0, mScale);
float height = box.height(); float y = PropHelper.fromPercentageToFloat(mY, mHeight, 0, mScale);
float width = box.width(); float w = PropHelper.fromPercentageToFloat(mW, mWidth, 0, mScale);
float h = PropHelper.fromPercentageToFloat(mH, mHeight, 0, mScale);
float x = PropHelper.fromPercentageToFloat(mX, width, 0, mScale); float rx = PropHelper.fromPercentageToFloat(mRx, mWidth, 0, mScale);
float y = PropHelper.fromPercentageToFloat(mY, height, 0, mScale); float ry = PropHelper.fromPercentageToFloat(mRy, mHeight, 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);
if (rx != 0 || ry != 0) { if (rx != 0 || ry != 0) {
if (rx == 0) { if (rx == 0) {

View File

@@ -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) {
}
}

View File

@@ -9,22 +9,32 @@
package com.horcrux.svg; package com.horcrux.svg;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
//import com.facebook.react.uimanager.ReactStylesDiffMap; //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.ReactShadowNode;
import com.facebook.react.uimanager.ThemedReactContext; 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.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 * 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 * 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. * "stubbed" ViewManager is used for all of them.
*/ */
public class RNSVGRenderableViewManager extends ViewManager<View, ReactShadowNode> { public class RNSVGRenderableViewManager extends ViewGroupManager<ViewGroup> {
/* package */ static final String CLASS_GROUP = "RNSVGGroup"; /* 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_TEXT = "RNSVGText";
/* package */ static final String CLASS_IMAGE = "RNSVGImage"; /* package */ static final String CLASS_IMAGE = "RNSVGImage";
/* package */ static final String CLASS_CIRCLE = "RNSVGCircle"; /* package */ static final String CLASS_CIRCLE = "RNSVGCircle";
@@ -34,12 +44,14 @@ public class RNSVGRenderableViewManager extends ViewManager<View, ReactShadowNod
private final String mClassName; private final String mClassName;
protected RNSVGVirtualNode mVirtualNode;
public static RNSVGRenderableViewManager createRNSVGGroupViewManager() { public static RNSVGRenderableViewManager createRNSVGGroupViewManager() {
return new RNSVGRenderableViewManager(CLASS_GROUP); return new RNSVGRenderableViewManager(CLASS_GROUP);
} }
public static RNSVGRenderableViewManager createRNSVGPathViewManager() { public static RNSVGRenderableViewManager createRNSVGPathViewManager() {
return new RNSVGRenderableViewManager(CLASS_SVG); return new RNSVGRenderableViewManager(CLASS_PATH);
} }
public static RNSVGRenderableViewManager createRNSVGTextViewManager() { public static RNSVGRenderableViewManager createRNSVGTextViewManager() {
@@ -76,58 +88,66 @@ public class RNSVGRenderableViewManager extends ViewManager<View, ReactShadowNod
} }
@Override @Override
public ReactShadowNode createShadowNodeInstance() { public RNSVGVirtualNode createShadowNodeInstance() {
if (mClassName == CLASS_GROUP) { switch (mClassName) {
return new RNSVGGroupShadowNode(); case CLASS_GROUP:
} else if (mClassName == CLASS_SVG) { mVirtualNode = new RNSVGGroupShadowNode();
return new RNSVGPathShadowNode(); break;
} else if (mClassName == CLASS_CIRCLE) { case CLASS_PATH:
return new RNSVGCircleShadowNode(); mVirtualNode = new RNSVGPathShadowNode();
} else if (mClassName == CLASS_ELLIPSE) { break;
return new RNSVGEllipseShadowNode(); case CLASS_CIRCLE:
} else if (mClassName == CLASS_LINE) { mVirtualNode = new RNSVGCircleShadowNode();
return new RNSVGLineShadowNode(); break;
} else if (mClassName == CLASS_RECT) { case CLASS_ELLIPSE:
return new RNSVGRectShadowNode(); mVirtualNode = new RNSVGEllipseShadowNode();
} else if (mClassName == CLASS_TEXT) { break;
return new RNSVGTextShadowNode(); case CLASS_LINE:
} else if (mClassName == CLASS_IMAGE) { mVirtualNode = new RNSVGLineShadowNode();
return new RNSVGImageShadowNode(); break;
} else { case CLASS_RECT:
mVirtualNode = new RNSVGRectShadowNode();
break;
case CLASS_TEXT:
mVirtualNode = new RNSVGTextShadowNode();
break;
case CLASS_IMAGE:
mVirtualNode = new RNSVGImageShadowNode();
break;
default:
throw new IllegalStateException("Unexpected type " + mClassName); throw new IllegalStateException("Unexpected type " + mClassName);
} }
return mVirtualNode;
} }
@Override @Override
public Class<? extends ReactShadowNode> getShadowNodeClass() { public Class<? extends RNSVGVirtualNode> getShadowNodeClass() {
if (mClassName == CLASS_GROUP) { switch (mClassName) {
case CLASS_GROUP:
return RNSVGGroupShadowNode.class; return RNSVGGroupShadowNode.class;
} else if (mClassName == CLASS_SVG) { case CLASS_PATH:
return RNSVGPathShadowNode.class; return RNSVGPathShadowNode.class;
} else if (mClassName == CLASS_CIRCLE) { case CLASS_CIRCLE:
return RNSVGCircleShadowNode.class; return RNSVGCircleShadowNode.class;
} else if (mClassName == CLASS_ELLIPSE) { case CLASS_ELLIPSE:
return RNSVGEllipseShadowNode.class; return RNSVGEllipseShadowNode.class;
} else if (mClassName == CLASS_LINE) { case CLASS_LINE:
return RNSVGLineShadowNode.class; return RNSVGLineShadowNode.class;
} else if (mClassName == CLASS_RECT) { case CLASS_RECT:
return RNSVGRectShadowNode.class; return RNSVGRectShadowNode.class;
} else if (mClassName == CLASS_TEXT) { case CLASS_TEXT:
return RNSVGTextShadowNode.class; return RNSVGTextShadowNode.class;
} else if (mClassName == CLASS_IMAGE) { case CLASS_IMAGE:
return RNSVGImageShadowNode.class; return RNSVGImageShadowNode.class;
} else { default:
throw new IllegalStateException("Unexpected type " + mClassName); throw new IllegalStateException("Unexpected type " + mClassName);
} }
} }
@Override @Override
protected View createViewInstance(ThemedReactContext reactContext) { protected ViewGroup createViewInstance(ThemedReactContext reactContext) {
throw new IllegalStateException("RNSVGPath does not map into a native view"); return new RNSVGRenderableView(reactContext);
}
@Override
public void updateExtraData(View root, Object extraData) {
throw new IllegalStateException("RNSVGPath does not map into a native view");
} }
} }

View File

@@ -14,15 +14,43 @@ import javax.annotation.Nullable;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Point;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View; 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 @Nullable Bitmap mBitmap;
private RNSVGSvgViewShadowNode mSvgViewShadowNode;
private int mTargetTag;
public RNSVGSvgView(Context context, RNSVGSvgViewShadowNode shadowNode) {
super(context);
mSvgViewShadowNode = shadowNode;
}
public RNSVGSvgView(Context context) { public RNSVGSvgView(Context context) {
super(context); super(context);
} }
@@ -42,4 +70,113 @@ public class RNSVGSvgView extends View {
canvas.drawBitmap(mBitmap, 0, 0, null); 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()));
}
} }

View File

@@ -10,27 +10,27 @@
package com.horcrux.svg; package com.horcrux.svg;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.util.Log;
import com.facebook.csslayout.CSSNode; import com.facebook.csslayout.CSSNode;
import com.facebook.csslayout.MeasureOutput; import com.facebook.csslayout.MeasureOutput;
import com.facebook.react.uimanager.BaseViewManager; import com.facebook.react.uimanager.BaseViewManager;
import com.facebook.react.uimanager.ThemedReactContext; 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 * 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. * invalidating the native view on shadow view updates happening in the underlying tree.
*/ */
public class RNSVGSvgViewManager extends public class RNSVGSvgViewManager extends ViewGroupManager<RNSVGSvgView> {
BaseViewManager<RNSVGSvgView, RNSVGSvgViewShadowNode> {
private static final String REACT_CLASS = "RNSVGSvgView"; private static final String REACT_CLASS = "RNSVGSvgView";
private static final CSSNode.MeasureFunction MEASURE_FUNCTION = new CSSNode.MeasureFunction() { // TODO: use an ArrayList to connect RNSVGSvgViewShadowNode with RNSVGSvgView, not sure if there will be a race condition.
@Override // TODO: find a better way to replace this
public void measure(CSSNode node, float width, float height, MeasureOutput measureOutput) { private ArrayList<RNSVGSvgViewShadowNode> SvgShadowNodes = new ArrayList<>();
throw new IllegalStateException("SvgView should have explicit width and height set");
}
};
@Override @Override
public String getName() { public String getName() {
@@ -40,7 +40,7 @@ public class RNSVGSvgViewManager extends
@Override @Override
public RNSVGSvgViewShadowNode createShadowNodeInstance() { public RNSVGSvgViewShadowNode createShadowNodeInstance() {
RNSVGSvgViewShadowNode node = new RNSVGSvgViewShadowNode(); RNSVGSvgViewShadowNode node = new RNSVGSvgViewShadowNode();
node.setMeasureFunction(MEASURE_FUNCTION); SvgShadowNodes.add(node);
return node; return node;
} }
@@ -51,7 +51,9 @@ public class RNSVGSvgViewManager extends
@Override @Override
protected RNSVGSvgView createViewInstance(ThemedReactContext reactContext) { protected RNSVGSvgView createViewInstance(ThemedReactContext reactContext) {
return new RNSVGSvgView(reactContext); RNSVGSvgViewShadowNode shadowNode = SvgShadowNodes.get(0);
SvgShadowNodes.remove(0);
return new RNSVGSvgView(reactContext, shadowNode);
} }
@Override @Override

View File

@@ -12,6 +12,9 @@ package com.horcrux.svg;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Paint; 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.LayoutShadowNode;
import com.facebook.react.uimanager.UIViewOperationQueue; import com.facebook.react.uimanager.UIViewOperationQueue;
@@ -20,17 +23,6 @@ import com.facebook.react.uimanager.UIViewOperationQueue;
* Shadow node for RNSVG virtual tree root - RNSVGSvgView * Shadow node for RNSVG virtual tree root - RNSVGSvgView
*/ */
public class RNSVGSvgViewShadowNode extends LayoutShadowNode { public class RNSVGSvgViewShadowNode extends LayoutShadowNode {
@Override
public boolean isVirtual() {
return false;
}
@Override
public boolean isVirtualAnchor() {
return true;
}
@Override @Override
public void onCollectExtraUpdates(UIViewOperationQueue uiUpdater) { public void onCollectExtraUpdates(UIViewOperationQueue uiUpdater) {
super.onCollectExtraUpdates(uiUpdater); super.onCollectExtraUpdates(uiUpdater);
@@ -40,17 +32,35 @@ public class RNSVGSvgViewShadowNode extends LayoutShadowNode {
private Object drawOutput() { private Object drawOutput() {
// TODO(7255985): Use TextureView and pass Svg from the view to draw on it asynchronously // 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) // 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( Bitmap bitmap = Bitmap.createBitmap(
(int) getLayoutWidth(), (int) width,
(int) getLayoutHeight(), (int) height,
Bitmap.Config.ARGB_8888); Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap); Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint(); Paint paint = new Paint();
for (int i = 0; i < getChildCount(); i++) { for (int i = 0; i < getChildCount(); i++) {
RNSVGVirtualNode child = (RNSVGVirtualNode) getChildAt(i); RNSVGVirtualNode child = (RNSVGVirtualNode) getChildAt(i);
child.setDimensions(width, height);
child.draw(canvas, paint, 1f); child.draw(canvas, paint, 1f);
child.markUpdateSeen(); child.markUpdateSeen();
} }
return bitmap; 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;
}
} }

View File

@@ -11,14 +11,18 @@ package com.horcrux.svg;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Path; import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.RectF; import android.graphics.RectF;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMap;
@@ -84,14 +88,17 @@ public class RNSVGTextShadowNode extends RNSVGPathShadowNode {
RectF box = getBox(paint, text); RectF box = getBox(paint, text);
if (setupStrokePaint(paint, opacity, box)) { if (setupStrokePaint(paint, opacity, box)) {
applyTextPropertiesToPaint(paint); drawText(canvas, paint, text);
if (mPath == null) {
canvas.drawText(text, 0, -paint.ascent(), paint);
} else {
canvas.drawTextOnPath(text, mPath, 0, -paint.ascent(), paint);
}
} }
if (setupFillPaint(paint, opacity, box)) { if (setupFillPaint(paint, opacity, box)) {
drawText(canvas, paint, text);
}
restoreCanvas(canvas);
markUpdateSeen();
}
private void drawText(Canvas canvas, Paint paint, String text) {
applyTextPropertiesToPaint(paint); applyTextPropertiesToPaint(paint);
if (mPath == null) { if (mPath == null) {
@@ -100,9 +107,6 @@ public class RNSVGTextShadowNode extends RNSVGPathShadowNode {
canvas.drawTextOnPath(text, mPath, 0, -paint.ascent(), paint); canvas.drawTextOnPath(text, mPath, 0, -paint.ascent(), paint);
} }
} }
restoreCanvas(canvas);
markUpdateSeen();
}
private String formatText() { private String formatText() {
if (mFrame == null || !mFrame.hasKey(PROP_LINES)) { if (mFrame == null || !mFrame.hasKey(PROP_LINES)) {
@@ -170,6 +174,8 @@ public class RNSVGTextShadowNode extends RNSVGPathShadowNode {
} }
} }
@Override
protected Path getPath(Canvas canvas, Paint paint) { protected Path getPath(Canvas canvas, Paint paint) {
Path path = new Path(); Path path = new Path();
@@ -178,6 +184,7 @@ public class RNSVGTextShadowNode extends RNSVGPathShadowNode {
return path; return path;
} }
// TODO: get path while TextPath is set.
if (setupFillPaint(paint, 1.0f, getBox(paint, text))) { if (setupFillPaint(paint, 1.0f, getBox(paint, text))) {
applyTextPropertiesToPaint(paint); applyTextPropertiesToPaint(paint);
paint.getTextPath(text, 0, text.length(), 0, -paint.ascent(), path); paint.getTextPath(text, 0, text.length(), 0, -paint.ascent(), path);
@@ -186,4 +193,43 @@ public class RNSVGTextShadowNode extends RNSVGPathShadowNode {
return 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);
// 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;
}
} }

View File

@@ -16,19 +16,21 @@ import android.graphics.Canvas;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Path; import android.graphics.Path;
import android.graphics.Point;
import android.graphics.RectF; import android.graphics.RectF;
import android.graphics.Region; import android.graphics.Region;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.DisplayMetricsHolder; import com.facebook.react.uimanager.DisplayMetricsHolder;
import com.facebook.react.uimanager.LayoutShadowNode;
import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.ReactShadowNode;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -36,18 +38,19 @@ import java.util.Map;
* Base class for RNSVGView virtual nodes: {@link RNSVGGroupShadowNode}, {@link RNSVGPathShadowNode} and * Base class for RNSVGView virtual nodes: {@link RNSVGGroupShadowNode}, {@link RNSVGPathShadowNode} and
* indirectly for {@link RNSVGTextShadowNode}. * indirectly for {@link RNSVGTextShadowNode}.
*/ */
public abstract class RNSVGVirtualNode extends ReactShadowNode { public abstract class RNSVGVirtualNode extends LayoutShadowNode {
protected static Map<String, Path> CLIP_PATHS = new HashMap<>(); protected static Map<String, Path> CLIP_PATHS = new HashMap<>();
protected static final float MIN_OPACITY_FOR_DRAW = 0.01f; protected static final float MIN_OPACITY_FOR_DRAW = 0.01f;
private static final float[] sMatrixData = new float[9]; private static final float[] sMatrixData = new float[9];
private static final float[] sRawMatrix = new float[9]; private static final float[] sRawMatrix = new float[9];
private @Nullable String mDefinedClipPathId;
protected float mOpacity = 1f; protected float mOpacity = 1f;
protected @Nullable Matrix mMatrix = new Matrix(); protected @Nullable Matrix mMatrix = new Matrix();
private @Nullable String mDefinedClipPathId;
protected @Nullable Path mClipPath; 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_ARC = 4;
private static final int PATH_TYPE_CLOSE = 1; private static final int PATH_TYPE_CLOSE = 1;
private static final int PATH_TYPE_CURVETO = 3; private static final int PATH_TYPE_CURVETO = 3;
@@ -62,15 +65,13 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode {
private boolean mClipRuleSet; private boolean mClipRuleSet;
private boolean mClipDataSet; private boolean mClipDataSet;
protected float mWidth = 0;
protected float mHeight = 0;
public RNSVGVirtualNode() { public RNSVGVirtualNode() {
mScale = DisplayMetricsHolder.getWindowDisplayMetrics().density; mScale = DisplayMetricsHolder.getWindowDisplayMetrics().density;
} }
@Override
public boolean isVirtual() {
return true;
}
public abstract void draw(Canvas canvas, Paint paint, float opacity); public abstract void draw(Canvas canvas, Paint paint, float opacity);
/** /**
@@ -127,8 +128,8 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode {
markUpdated(); markUpdated();
} }
@ReactProp(name = "transform") @ReactProp(name = "trans")
public void setTransform(@Nullable ReadableArray transformArray) { public void setTrans(@Nullable ReadableArray transformArray) {
if (transformArray != null) { if (transformArray != null) {
int matrixSize = PropHelper.toFloatArray(transformArray, sMatrixData); int matrixSize = PropHelper.toFloatArray(transformArray, sMatrixData);
if (matrixSize == 6) { if (matrixSize == 6) {
@@ -155,7 +156,6 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode {
throw new JSApplicationIllegalArgumentException( throw new JSApplicationIllegalArgumentException(
"clipRule " + mClipRule + " unrecognized"); "clipRule " + mClipRule + " unrecognized");
} }
createPath(mClipData, mClipPath); createPath(mClipData, mClipPath);
} }
} }
@@ -204,7 +204,6 @@ public abstract class RNSVGVirtualNode extends ReactShadowNode {
int i = 0; int i = 0;
while (i < data.length) { while (i < data.length) {
int type = (int) data[i++]; int type = (int) data[i++];
switch (type) { switch (type) {
case PATH_TYPE_MOVETO: 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) { protected void defineClipPath(Path clipPath, String clipPathId) {
CLIP_PATHS.put(clipPathId, clipPath); CLIP_PATHS.put(clipPathId, clipPath);
mDefinedClipPathId = clipPathId; mDefinedClipPathId = clipPathId;
} }
abstract protected Path getPath(Canvas canvas, Paint paint);
protected void finalize() { protected void finalize() {
if (mDefinedClipPathId != null) { if (mDefinedClipPathId != null) {
CLIP_PATHS.remove(mDefinedClipPathId); CLIP_PATHS.remove(mDefinedClipPathId);
} }
} }
abstract protected Path getPath(Canvas canvas, Paint paint);
} }

View File

@@ -1,10 +1,16 @@
import React, {Children, Component, cloneElement, PropTypes} from 'react'; 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'; import ViewBox from './ViewBox';
// Svg - Root node of all Svg elements // Svg - Root node of all Svg elements
let id = 0; let id = 0;
const styles = StyleSheet.create({
svg: {
backgroundColor: 'transparent'
}
});
class Svg extends Component{ class Svg extends Component{
static displayName = 'Svg'; static displayName = 'Svg';
static propTypes = { static propTypes = {
@@ -75,6 +81,7 @@ class Svg extends Component{
preserveAspectRatio={null} preserveAspectRatio={null}
ref={ele => this.root = ele} ref={ele => this.root = ele}
style={[ style={[
styles.svg,
props.style, props.style,
!isNaN(opacity) && { !isNaN(opacity) && {
opacity opacity