mirror of
https://github.com/zoriya/react-native-svg.git
synced 2025-12-20 22:05:14 +00:00
add touch responder system on android
This commit is contained in:
66
Example/android/app/BUCK
Normal file
66
Example/android/app/BUCK
Normal 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',
|
||||
],
|
||||
)
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
4
Example/android/app/proguard-rules.pro
vendored
4
Example/android/app/proguard-rules.pro
vendored
@@ -61,7 +61,3 @@
|
||||
-dontwarn java.nio.file.*
|
||||
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
||||
-dontwarn okio.**
|
||||
|
||||
# stetho
|
||||
|
||||
-dontwarn com.facebook.stetho.**
|
||||
|
||||
@@ -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<ReactPackage> getPackages() {
|
||||
return Arrays.<ReactPackage>asList(
|
||||
new RNSvgPackage(),
|
||||
new MainReactPackage()
|
||||
);
|
||||
return Arrays.<ReactPackage>asList(
|
||||
new MainReactPackage(),
|
||||
new RNSvgPackage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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<View, ReactShadowNode> {
|
||||
public class RNSVGRenderableViewManager extends ViewGroupManager<ViewGroup> {
|
||||
|
||||
/* 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<View, ReactShadowNod
|
||||
|
||||
private final String mClassName;
|
||||
|
||||
protected RNSVGVirtualNode mVirtualNode;
|
||||
|
||||
public static RNSVGRenderableViewManager createRNSVGGroupViewManager() {
|
||||
return new RNSVGRenderableViewManager(CLASS_GROUP);
|
||||
}
|
||||
|
||||
public static RNSVGRenderableViewManager createRNSVGPathViewManager() {
|
||||
return new RNSVGRenderableViewManager(CLASS_SVG);
|
||||
return new RNSVGRenderableViewManager(CLASS_PATH);
|
||||
}
|
||||
|
||||
public static RNSVGRenderableViewManager createRNSVGTextViewManager() {
|
||||
@@ -76,58 +88,66 @@ public class RNSVGRenderableViewManager extends ViewManager<View, ReactShadowNod
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactShadowNode createShadowNodeInstance() {
|
||||
if (mClassName == CLASS_GROUP) {
|
||||
return new RNSVGGroupShadowNode();
|
||||
} else if (mClassName == CLASS_SVG) {
|
||||
return new RNSVGPathShadowNode();
|
||||
} else if (mClassName == CLASS_CIRCLE) {
|
||||
return new RNSVGCircleShadowNode();
|
||||
} else if (mClassName == CLASS_ELLIPSE) {
|
||||
return new RNSVGEllipseShadowNode();
|
||||
} else if (mClassName == CLASS_LINE) {
|
||||
return new RNSVGLineShadowNode();
|
||||
} else if (mClassName == CLASS_RECT) {
|
||||
return new RNSVGRectShadowNode();
|
||||
} else if (mClassName == CLASS_TEXT) {
|
||||
return new RNSVGTextShadowNode();
|
||||
} else if (mClassName == CLASS_IMAGE) {
|
||||
return new RNSVGImageShadowNode();
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected type " + mClassName);
|
||||
public RNSVGVirtualNode createShadowNodeInstance() {
|
||||
switch (mClassName) {
|
||||
case CLASS_GROUP:
|
||||
mVirtualNode = new RNSVGGroupShadowNode();
|
||||
break;
|
||||
case CLASS_PATH:
|
||||
mVirtualNode = new RNSVGPathShadowNode();
|
||||
break;
|
||||
case CLASS_CIRCLE:
|
||||
mVirtualNode = new RNSVGCircleShadowNode();
|
||||
break;
|
||||
case CLASS_ELLIPSE:
|
||||
mVirtualNode = new RNSVGEllipseShadowNode();
|
||||
break;
|
||||
case CLASS_LINE:
|
||||
mVirtualNode = new RNSVGLineShadowNode();
|
||||
break;
|
||||
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);
|
||||
}
|
||||
|
||||
return mVirtualNode;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends RNSVGVirtualNode> 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<? extends ReactShadowNode> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<RNSVGSvgView, RNSVGSvgViewShadowNode> {
|
||||
public class RNSVGSvgViewManager extends ViewGroupManager<RNSVGSvgView> {
|
||||
|
||||
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<RNSVGSvgViewShadowNode> 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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, Path> CLIP_PATHS = new HashMap<>();
|
||||
|
||||
protected static final float MIN_OPACITY_FOR_DRAW = 0.01f;
|
||||
|
||||
private static final float[] sMatrixData = new float[9];
|
||||
private static final float[] sRawMatrix = new float[9];
|
||||
private @Nullable String mDefinedClipPathId;
|
||||
protected float mOpacity = 1f;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user