React-Native-ART代码解析
一、探寻源码
1.如何使用
安卓自己集成,不需要额外操作,iOS需要pod添加ART库,如下:
pod 'React', :path => '../rn-source', :subspecs => [
'Core',
'RCTActionSheet',
'RCTText',
'RCTImage',
'ART',
# needed for debugging
# Add any other subspecs you want to use in your project
]
2.js中的使用
import { ART } from 'react-native'
const { Surface, Shape, Path, Group } = ART;
这样我们就能够使用ART的组件,总共有10个。部分组件的具体使用方法可以参考我之前写的。
3.寻找ART的源码
首先我们知道ART是在react-native
中引用的,所以我们到node_modules下找到react-native
文件夹,如图:
到这里,我们首先要看一下package.json
文件,这是react-native
所用的组件列表,如图:
我们可以看到react-native引用了一个库叫做art,但这里是小写的,应该是ART所用的依赖,还不确定是我们要找到ART库在哪里,我们继续找。
接下来,我们来看看Libraries文件夹,这里通常放着组件,这里我们看到了大写的ART,如图:
在这里我们可以看到大多都是原生代码,在ReactNativeART.js
中export出了一个组件ReactART,这个是我们要找的ART吗?,现在还不能确定。
我们继续看,在Libraries下我们找到react-native文件夹,可以看到react-native.js就静静的躺在这里,这个应该就是整个react-native组件的入口位置,在这里我们看到:
get ART() { return require('ReactNativeART'); },
现在,我们可以确定ReactNativeART就是我们要找的ART。
二、ART库源码
让我们回过头来看看ReactNativeART的入口文件(也就是Libraries下的ART文件夹下的 ReactNativeART.js),拉到下面我们可以看到:
var ReactART = {
LinearGradient: LinearGradient,
RadialGradient: RadialGradient,
Pattern: Pattern,
Transform: Transform,
Path: Path,
Surface: Surface,
Group: Group,
ClippingRectangle: ClippingRectangle,
Shape: Shape,
Text: Text,
};
这个就是ART库输出的所有组件了,我们就可以从这里开始阅读代码,我们可以看到:
var Path = require('ARTSerializablePath');
var Transform = require('art/core/transform');
// Native Components
var NativeSurfaceView = createReactNativeComponentClass({
validAttributes: SurfaceViewAttributes,
uiViewClassName: 'ARTSurfaceView',
});
var NativeGroup = createReactNativeComponentClass({
validAttributes: GroupAttributes,
uiViewClassName: 'ARTGroup',
});
var NativeShape = createReactNativeComponentClass({
validAttributes: ShapeAttributes,
uiViewClassName: 'ARTShape',
});
var NativeText = createReactNativeComponentClass({
validAttributes: TextAttributes,
uiViewClassName: 'ARTText',
});
function LinearGradient ...
function RadialGradient ...
function Pattern ...
class ClippingRectangle extends React.Component {
render() {
var props = this.props;
var x = extractNumber(props.x, 0);
var y = extractNumber(props.y, 0);
var w = extractNumber(props.width, 0);
var h = extractNumber(props.height, 0);
var clipping = [x, y, w, h];
// The current clipping API requires x and y to be ignored in the transform
var propsExcludingXAndY = merge(props);
delete propsExcludingXAndY.x;
delete propsExcludingXAndY.y;
return (
<NativeGroup
clipping={clipping}
opacity={extractOpacity(props)}
transform={extractTransform(propsExcludingXAndY)}>
{this.props.children}
</NativeGroup>
);
}
}
可以看到,输出的10个组件这里都能看到出处,那到这里,其实我们关心那部分代码,就可以具体看哪里了,源码在这里其实已经暴露无疑了。
这部分的结论:
- surface,shape,group,text都是原生组件,
- ClippingRectangle是封装的一层group,
- LinearGradient,RadialGradient,Pattern分别是3个func,
- 而path和Transform则是引用的art文件,在上面我们已经看到了react-native的package.json引用了art组件。
三、ART里的path源码
上面我们看到
var Path = require('ARTSerializablePath');
在ARTSerializablePath.js
中,我们又能看到
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ARTSerializablePath
*/
'use strict';
// TODO: Move this into an ART mode called "serialized" or something
var Class = require('art/core/class.js');
var Path = require('art/core/path.js');
var MOVE_TO = 0;
var CLOSE = 1;
var LINE_TO = 2;
var CURVE_TO = 3;
var ARC = 4;
var SerializablePath = Class(Path, {
initialize: function(path) {
this.reset();
if (path instanceof SerializablePath) {
this.path = path.path.slice(0);
} else if (path) {
if (path.applyToPath) {
path.applyToPath(this);
} else {
this.push(path);
}
}
},
onReset: function() {
this.path = [];
},
onMove: function(sx, sy, x, y) {
this.path.push(MOVE_TO, x, y);
},
onLine: function(sx, sy, x, y) {
this.path.push(LINE_TO, x, y);
},
onBezierCurve: function(sx, sy, p1x, p1y, p2x, p2y, x, y) {
this.path.push(CURVE_TO, p1x, p1y, p2x, p2y, x, y);
},
_arcToBezier: Path.prototype.onArc,
onArc: function(sx, sy, ex, ey, cx, cy, rx, ry, sa, ea, ccw, rotation) {
if (rx !== ry || rotation) {
return this._arcToBezier(
sx, sy, ex, ey, cx, cy, rx, ry, sa, ea, ccw, rotation
);
}
this.path.push(ARC, cx, cy, rx, sa, ea, ccw ? 0 : 1);
},
onClose: function() {
this.path.push(CLOSE);
},
toJSON: function() {
return this.path;
}
});
module.exports = SerializablePath;
说明我们的path直接就使用的art/core/path.js
文件,而看这里的实现,全部使用的是this.path.push()
+参数的方法,我们接下来去看下art/core/path.js
的push方法,如下:
push: function(){
var p = Array.prototype.join.call(arguments, ' ')
.match(/[a-df-z]|[\-+]?(?:[\d\.]e[\-+]?|[^\s\-+,a-z])+/ig);
if (!p) return this;
var last, cmd = p[0], i = 1;
while (cmd){
switch (cmd){
case 'm': this.move(p[i++], p[i++]); break;
case 'l': this.line(p[i++], p[i++]); break;
case 'c': this.curve(p[i++], p[i++], p[i++], p[i++], p[i++], p[i++]); break;
case 's': this.curve(p[i++], p[i++], null, null, p[i++], p[i++]); break;
case 'q': this.curve(p[i++], p[i++], p[i++], p[i++]); break;
case 't': this.curve(p[i++], p[i++]); break;
case 'a': this.arc(p[i+5], p[i+6], p[i], p[i+1], p[i+3], !+p[i+4], p[i+2]); i += 7; break;
case 'h': this.line(p[i++], 0); break;
case 'v': this.line(0, p[i++]); break;
case 'M': this.moveTo(p[i++], p[i++]); break;
case 'L': this.lineTo(p[i++], p[i++]); break;
case 'C': this.curveTo(p[i++], p[i++], p[i++], p[i++], p[i++], p[i++]); break;
case 'S': this.curveTo(p[i++], p[i++], null, null, p[i++], p[i++]); break;
case 'Q': this.curveTo(p[i++], p[i++], p[i++], p[i++]); break;
case 'T': this.curveTo(p[i++], p[i++]); break;
case 'A': this.arcTo(p[i+5], p[i+6], p[i], p[i+1], p[i+3], !+p[i+4], p[i+2]); i += 7; break;
case 'H': this.lineTo(p[i++], this.penY); break;
case 'V': this.lineTo(this.penX, p[i++]); break;
case 'Z': case 'z': this.close(); break;
default: cmd = last; i--; continue;
}
last = cmd;
if (last == 'm') last = 'l';
else if (last == 'M') last = 'L';
cmd = p[i++];
}
return this;
},
可以看到svg的方法,说明我们其实是可以直接使用push做svg的实现的,
命令 | 名称 | 参数 |
---|---|---|
M | moveto | 移动到 (x y)+ |
Z | closepath | 关闭路径 (none) |
L | lineto | 画线到 (x y)+ |
H | horizontal lineto | 水平线到 x+ |
V | vertical lineto | 垂直线到 y+ |
C | curveto | 三次贝塞尔曲线到 (x1 y1 x2 y2 x y)+ |
S | smooth curveto | 光滑三次贝塞尔曲线到 (x2 y2 x y)+ |
Q | quadratic Bézier curveto | 二次贝塞尔曲线到 (x1 y1 x y)+ |
T | smooth quadratic Bézier curveto | 光滑二次贝塞尔曲线到 (x y)+ |
A | elliptical arc | 椭圆弧 (rx ry x-axis-rotation large-arc-flag sweep-flag x y)+ |
R | Catmull-Rom curveto* | Catmull-Rom曲线 x1 y1 (x y)+ |
其实这里我有个疑虑,并不了解ARTSerializablePath.js
中调用push的原理,貌似指定了0-4的参数,
var MOVE_TO = 0;
var CLOSE = 1;
var LINE_TO = 2;
var CURVE_TO = 3;
var ARC = 4;
就可以调用相应的方法函数,还望看的懂的同学说明一下,
这里我们发现ART是可以使用SVG函数的,所以SVG的能力,我们在这里可以直接使用,比如画内塞尔曲线等等,例如:
_path = new Path("M0 0 Q5 20……");
四、Android与iOS使用ART的不同
- 渐变色(安卓不能渐变)
- 画扇形图(安卓绘制方式不同)
这里说一下画扇形,iOS直接使用ART提供的path根据我之前写的使用方法就可以画出来想要的图案,但是安卓却不能,这里可以使用更底层的push方法代替,例如画扇形:
if (Platform.OS === 'ios') {
path.move(or + or * ss, or - or * sc). // move to starting point
arc(or * ds, or * -dc, or, or, large). // outer arc
line(dr * es, dr * -ec); // width of arc or wedge
} else {
path.path.push(ARC, CIRCLE_X, CIRCLE_Y, RX, startAngle / 360 * TwoPI, (startAngle / 360 * TwoPI) - ((endAngle - startAngle) / 360 * TwoPI), 0)
}
通过区分平台,再具体实现上使用不同的方法,达到相同的效果。