1. 程式人生 > >react-native-art path程式碼解析

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 資料夾,如圖:
react-native目錄
到這裡,我們首先要看一下package.json 檔案,這是react-native 所用的元件列表,如圖:
package.json
我們可以看到react-native引用了一個庫叫做art,但這裡是小寫的,應該是ART所用的依賴,還不確定是我們要找到ART庫在哪裡,我們繼續找。
接下來,我們來看看Libraries資料夾,這裡通常放著元件,這裡我們看到了大寫的ART,如圖:
ReactNativeArt


在這裡我們可以看到大多都是原生程式碼,在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個元件這裡都能看到出處,那到這裡,其實我們關心那部分程式碼,就可以具體看哪裡了,原始碼在這裡其實已經暴露無疑了。

這部分的結論:

  1. surface,shape,group,text都是原生元件,
  2. ClippingRectangle是封裝的一層group,
  3. LinearGradient,RadialGradient,Pattern分別是3個func,
  4. 而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的不同

  1. 漸變色(安卓不能漸變)
  2. 畫扇形圖(安卓繪製方式不同)

這裡說一下畫扇形,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)
}

通過區分平臺,再具體實現上使用不同的方法,達到相同的效果。