react-native-art path程式碼解析
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)
}
通過區分平臺,再具體實現上使用不同的方法,達到相同的效果。