react-native之ART绘图方法详解


Posted in Javascript onAugust 08, 2017

背景

在移动应用的开发过程中,绘制基本的二维图形或动画是必不可少的。然而,考虑到Android和iOS均有一套各自的API方案,因此采用一种更普遍接受的技术方案,更有利于代码的双平台兼容。

art是一个旨在多浏览器兼容的Node style CommonJS模块。在它的基础上,Facebook又开发了React-art ,封装art,使之可以被react.js所使用,即实现了前端的svg库。然而,考虑到react.js的JSX语法,已经支持将 等等svg标签直接插入到dom中(当然此时使用的就不是react-art库了)此外还有HTML canvas的存在,因此,在前端上,react-art并非不可替代。

然而,在移动端,考虑到跨平台的需求,加之web端的技术积累,react-art成为了现成的绘制图形的解决方案。react-native分别在0.10.0和0.18.0上添加了ios和android平台上对react-art的支持。

示例代码

React.js和React-Native的区别,只在于下文所述的ART获取上,然后该例子就可以同时应用在Web端和移动端上了。react-art自带的官方例子:Vector-Widget

Vector-Widget额外实现了旋转,以及鼠标点击事件的旋转加速响应。Web端可以看到点击加速,但是在移动端无效,原因是React Native并未对Group中onMouseDown和onMouseUp属性作处理。本文着重于静态svg的实现,暂时无视动画部分效果即可。

ART

在react native中ART是个非常重要的库,它让非常酷炫的绘图及动画变成了可能。需要注意的是,在React Native引入ART过程中,Android默认就包含ART库,IOS需要单独添加依赖库。

ios添加依赖库

1、使用xcode中打开React-native中的iOS项目,选中‘Libraries'目录 ——> 右键选择‘Add Files to 项目名称' ——> ‘node_modules/react-native/Libraries/ART/ART.xcodeproj' 添加;

react-native之ART绘图方法详解

2、选中项目根目录 ——> 点击'Build Phases‘ ——> 点击‘Link Binary With Libraries' ——> 点击左下方‘+' ——> 选中‘libART.a'添加。

react-native之ART绘图方法详解

基础组件

ART暴露的组件共有7个,本文介绍常用的四个组件:Surface、Group、Shape、Text。

  • Surface - 一个矩形可渲染的区域,是其他元素的容器
  • Group - 可容纳多个形状、文本和其他的分组
  • Shape - 形状定义,可填充
  • Text - 文本形状定义

属性

Surface

  • width : 渲染区域的宽
  • height : 定义渲染区域的高

Shape

  • d : 定义绘制路径
  • stroke : 描边颜色
  • strokeWidth : 描边宽度
  • strokeDash : 定义虚线
  • fill : 填充颜色

Text

  • funt : 字体样式,定义字体、大小、是否加粗 如: bold 35px Heiti SC

Path

  • moveTo(x,y) : 移动到坐标(x,y)
  • lineTo(x,y) : 连线到(x,y)
  • arc() : 绘制弧线
  • close() : 封闭空间

代码示例

绘制直线

react-native之ART绘图方法详解

import React from 'react'
import {
  View,
  ART
} from 'react-native'

export default class Line extends React.Component{

  render(){

    const path = ART.Path();
    path.moveTo(1,1); //将起始点移动到(1,1) 默认(0,0)
    path.lineTo(300,1); //连线到目标点(300,1)

    return(
      <View style={this.props.style}>
        <ART.Surface width={300} height={2}>
          <ART.Shape d={path} stroke="#000000" strokeWidth={1} />
        </ART.Surface>
      </View>
    )
  }
}

绘制虚线

了解strokeDash的参数,

[10,5] : 表示绘10像素实线在绘5像素空白,如此循环

[10,5,20,5] : 表示绘10像素实线在绘制5像素空白在绘20像素实线及5像素空白

react-native之ART绘图方法详解

import React from 'react'
import {
  View,
  ART
} from 'react-native'

const {Surface, Shape, Path} = ART;

export default class DashLine extends React.Component{

  render(){

    const path = Path()
      .moveTo(1,1)
      .lineTo(300,1);

    return(
      <View style={this.props.style}>
        <Surface width={300} height={2}>
          <Shape d={path} stroke="#000000" strokeWidth={2} strokeDash={[10,5]}/>
        </Surface>
      </View>
    )
  }
}

绘制矩形

首先通过lineTo绘制三条边,在使用close链接第四条边。fill做颜色填充.

react-native之ART绘图方法详解

import React from 'react'
import {
  View,
  ART
} from 'react-native'

const {Surface, Shape, Path} = ART;

export default class Rect extends React.Component{

  render(){

    const path = new Path()
      .moveTo(1,1)
      .lineTo(1,99)
      .lineTo(99,99)
      .lineTo(99,1)
      .close();

    return(
      <View style={this.props.style}>
        <Surface width={100} height={100}>
          <Shape d={path} stroke="#000000" fill="#892265" strokeWidth={1} />
        </Surface>
      </View>
    )
  }
}

绘圆

了解arc(x,y,radius)的使用, 终点坐标距离起点坐标的相对距离。

react-native之ART绘图方法详解

import React from 'react'
import {
  View,
  ART
} from 'react-native'

const {Surface, Shape, Path} = ART;

export default class Circle extends React.Component{

  render(){

    const path = new Path()
      .moveTo(50,1)
      .arc(0,99,25)
      .arc(0,-99,25)
      .close();


    return(
      <View style={this.props.style}>
        <Surface width={100} height={100}>
          <Shape d={path} stroke="#000000" strokeWidth={1}/>
        </Surface>
      </View>
    )
  }
}

绘制文字

了解funt属性的使用,规则是“粗细 字号 字体”

注意: 字体应该是支持path属性的,应该是实现bug并没有不生效。 Android通过修改源码是可以解决的,IOS没看源码。

react-native之ART绘图方法详解

import React, {Component} from 'react';
import {
  AppRegistry,
  StyleSheet,
  ART,
  View
} from 'react-native';

const {Surface, Text, Path} = ART;

export default class ArtTextView extends Component {

  render() {

    return (
      <View style={styles.container}>
        <Surface width={100} height={100}>
          <Text strokeWidth={1} stroke="#000" font="bold 35px Heiti SC" path={new Path().moveTo(40,40).lineTo(99,10)} >React</Text>
        </Surface>

      </View>

    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },

});

绘制扇形

react-native之ART绘图方法详解

在这里需要使用arc做路径绘制。

Wedge.js

import React, { Component, PropTypes } from 'react';
import { ART } from 'react-native';
const { Shape, Path } = ART;

/**
 * Wedge is a React component for drawing circles, wedges and arcs. Like other
 * ReactART components, it must be used in a <Surface>.
 */
export default class Wedge extends Component<void, any, any> {

  static propTypes = {
    outerRadius: PropTypes.number.isRequired,
    startAngle: PropTypes.number.isRequired,
    endAngle: PropTypes.number.isRequired,
    originX: PropTypes.number.isRequired,
    originY: PropTypes.number.isRequired,
    innerRadius: PropTypes.number,
  };


  constructor(props : any) {
    super(props);
    (this:any).circleRadians = Math.PI * 2;
    (this:any).radiansPerDegree = Math.PI / 180;
    (this:any)._degreesToRadians = this._degreesToRadians.bind(this);
  }

  /**
   * _degreesToRadians(degrees)
   *
   * Helper function to convert degrees to radians
   *
   * @param {number} degrees
   * @return {number}
   */
  _degreesToRadians(degrees : number) : number {
    if (degrees !== 0 && degrees % 360 === 0) { // 360, 720, etc.
      return (this:any).circleRadians;
    }
    return degrees * (this:any).radiansPerDegree % (this:any).circleRadians;
  }

  /**
   * _createCirclePath(or, ir)
   *
   * Creates the ReactART Path for a complete circle.
   *
   * @param {number} or The outer radius of the circle
   * @param {number} ir The inner radius, greater than zero for a ring
   * @return {object}
   */
  _createCirclePath(or : number, ir : number) : Path {
    const path = new Path();

    path.move(0, or)
      .arc(or * 2, 0, or)
      .arc(-or * 2, 0, or);

    if (ir) {
      path.move(or - ir, 0)
        .counterArc(ir * 2, 0, ir)
        .counterArc(-ir * 2, 0, ir);
    }

    path.close();

    return path;
  }

  /**
   * _createArcPath(sa, ea, ca, or, ir)
   *
   * Creates the ReactART Path for an arc or wedge.
   *
   * @param {number} startAngle The starting degrees relative to 12 o'clock
   * @param {number} endAngle The ending degrees relative to 12 o'clock
   * @param {number} or The outer radius in pixels
   * @param {number} ir The inner radius in pixels, greater than zero for an arc
   * @return {object}
   */
  _createArcPath(originX : number, originY : number, startAngle : number, endAngle : number, or : number, ir : number) : Path {
    const path = new Path();

    // angles in radians
    const sa = this._degreesToRadians(startAngle);
    const ea = this._degreesToRadians(endAngle);

    // central arc angle in radians
    const ca = sa > ea ? (this:any).circleRadians - sa + ea : ea - sa;

    // cached sine and cosine values
    const ss = Math.sin(sa);
    const es = Math.sin(ea);
    const sc = Math.cos(sa);
    const ec = Math.cos(ea);

    // cached differences
    const ds = es - ss;
    const dc = ec - sc;
    const dr = ir - or;

    // if the angle is over pi radians (180 degrees)
    // we will need to let the drawing method know.
    const large = ca > Math.PI;

    // TODO (sema) Please improve theses comments to make the math
    // more understandable.
    //
    // Formula for a point on a circle at a specific angle with a center
    // at (0, 0):
    // x = radius * Math.sin(radians)
    // y = radius * Math.cos(radians)
    //
    // For our starting point, we offset the formula using the outer
    // radius because our origin is at (top, left).
    // In typical web layout fashion, we are drawing in quadrant IV
    // (a.k.a. Southeast) where x is positive and y is negative.
    //
    // The arguments for path.arc and path.counterArc used below are:
    // (endX, endY, radiusX, radiusY, largeAngle)

    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

    if (ir) {
      path.counterArc(ir * -ds, ir * dc, ir, ir, large); // inner arc
    }

    return path;
  }

  render() : any {
    // angles are provided in degrees
    const startAngle = this.props.startAngle;
    const endAngle = this.props.endAngle;
    // if (startAngle - endAngle === 0) {
    // return null;
    // }

    // radii are provided in pixels
    const innerRadius = this.props.innerRadius || 0;
    const outerRadius = this.props.outerRadius;

    const { originX, originY } = this.props;

    // sorted radii
    const ir = Math.min(innerRadius, outerRadius);
    const or = Math.max(innerRadius, outerRadius);

    let path;
    if (endAngle >= startAngle + 360) {
      path = this._createCirclePath(or, ir);
    } else {
      path = this._createArcPath(originX, originY, startAngle, endAngle, or, ir);
    }

    return <Shape {...this.props} d={path} />;
  }
}

示例代码:

import React from 'react'
import {
  View,
  ART
} from 'react-native'

const {Surface} = ART;
import Wedge from './Wedge'

export default class Fan extends React.Component{

  render(){

    return(
      <View style={this.props.style}>
        <Surface width={100} height={100}>
          <Wedge
           outerRadius={50}
           startAngle={0}
           endAngle={60}
           originX={50}
           originY={50}
           fill="blue"/>

        </Surface>
      </View>
    )
  }
}

综合示例

react-native之ART绘图方法详解

相关代码:

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, {
  Component
}from 'react';
import {
  ART as Art,
  StyleSheet,
  View,
  Dimensions,
  TouchableWithoutFeedback,
  Animated
} from 'react-native';

var HEART_SVG = "M130.4-0.8c25.4 0 46 20.6 46 46.1 0 13.1-5.5 24.9-14.2 33.3L88 153.6 12.5 77.3c-7.9-8.3-12.8-19.6-12.8-31.9 0-25.5 20.6-46.1 46-46.2 19.1 0 35.5 11.7 42.4 28.4C94.9 11 111.3-0.8 130.4-0.8"
var HEART_COLOR = 'rgb(226,38,77,1)';
var GRAY_HEART_COLOR = "rgb(204,204,204,1)";

var FILL_COLORS = [
  'rgba(221,70,136,1)',
  'rgba(212,106,191,1)',
  'rgba(204,142,245,1)',
  'rgba(204,142,245,1)',
  'rgba(204,142,245,1)',
  'rgba(0,0,0,0)'
];

var PARTICLE_COLORS = [
  'rgb(158, 202, 250)',
  'rgb(161, 235, 206)',
  'rgb(208, 148, 246)',
  'rgb(244, 141, 166)',
  'rgb(234, 171, 104)',
  'rgb(170, 163, 186)'
]

getXYParticle = (total, i, radius) => {
  var angle = ( (2 * Math.PI) / total ) * i;

  var x = Math.round((radius * 2) * Math.cos(angle - (Math.PI / 2)));
  var y = Math.round((radius * 2) * Math.sin(angle - (Math.PI / 2)));
  return {
    x: x,
    y: y,
  }
}

getRandomInt = (min, max) => {
  return Math.floor(Math.random() * (max - min)) + min;
}

shuffleArray = (array) => {
  for (var i = array.length - 1; i > 0; i--) {
    var j = Math.floor(Math.random() * (i + 1));
    var temp = array[i];
    array[i] = array[j];
    array[j] = temp;
  }
  return array;
}


var {
  Surface,
  Group,
  Shape,
  Path
} = Art;

//使用Animated.createAnimatedComponent对其他组件创建对话
//创建一个灰色的新型图片
var AnimatedShape = Animated.createAnimatedComponent(Shape);

var {
  width: deviceWidth,
  height: deviceHeight
} = Dimensions.get('window');

export default class ArtAnimView extends Component {
  constructor(props) {
    super(props);

    this.state = {
      animation: new Animated.Value(0)
    };
  }

  explode = () => {
    Animated.timing(this.state.animation, {
      duration: 1500,
      toValue: 28
    }).start(() => {
      this.state.animation.setValue(0);
      this.forceUpdate();
    });
  }

  getSmallExplosions = (radius, offset) => {
    return [0, 1, 2, 3, 4, 5, 6].map((v, i, t) => {

      var scaleOut = this.state.animation.interpolate({
        inputRange: [0, 5.99, 6, 13.99, 14, 21],
        outputRange: [0, 0, 1, 1, 1, 0],
        extrapolate: 'clamp'
      });

      var moveUp = this.state.animation.interpolate({
        inputRange: [0, 5.99, 14],
        outputRange: [0, 0, -15],
        extrapolate: 'clamp'
      });

      var moveDown = this.state.animation.interpolate({
        inputRange: [0, 5.99, 14],
        outputRange: [0, 0, 15],
        extrapolate: 'clamp'
      });

      var color_top_particle = this.state.animation.interpolate({
        inputRange: [6, 8, 10, 12, 17, 21],
        outputRange: shuffleArray(PARTICLE_COLORS)
      })

      var color_bottom_particle = this.state.animation.interpolate({
        inputRange: [6, 8, 10, 12, 17, 21],
        outputRange: shuffleArray(PARTICLE_COLORS)
      })

      var position = getXYParticle(7, i, radius)

      return (
        <Group
          x={position.x + offset.x }
          y={position.y + offset.y}
          rotation={getRandomInt(0, 40) * i}
        >
          <AnimatedCircle
            x={moveUp}
            y={moveUp}
            radius={15}
            scale={scaleOut}
            fill={color_top_particle}
          />
          <AnimatedCircle
            x={moveDown}
            y={moveDown}
            radius={8}
            scale={scaleOut}
            fill={color_bottom_particle}
          />
        </Group>
      )
    }, this)
  }

  render() {
    var heart_scale = this.state.animation.interpolate({
      inputRange: [0, .01, 6, 10, 12, 18, 28],
      outputRange: [1, 0, .1, 1, 1.2, 1, 1],
      extrapolate: 'clamp'
    });

    var heart_fill = this.state.animation.interpolate({
      inputRange: [0, 2],
      outputRange: [GRAY_HEART_COLOR, HEART_COLOR],
      extrapolate: 'clamp'
    })

    var heart_x = heart_scale.interpolate({
      inputRange: [0, 1],
      outputRange: [90, 0],
    })

    var heart_y = heart_scale.interpolate({
      inputRange: [0, 1],
      outputRange: [75, 0],
    })

    var circle_scale = this.state.animation.interpolate({
      inputRange: [0, 1, 4],
      outputRange: [0, .3, 1],
      extrapolate: 'clamp'
    });

    var circle_stroke_width = this.state.animation.interpolate({
      inputRange: [0, 5.99, 6, 7, 10],
      outputRange: [0, 0, 15, 8, 0],
      extrapolate: 'clamp'
    });

    var circle_fill_colors = this.state.animation.interpolate({
      inputRange: [1, 2, 3, 4, 4.99, 5],
      outputRange: FILL_COLORS,
      extrapolate: 'clamp'
    })

    var circle_opacity = this.state.animation.interpolate({
      inputRange: [1, 9.99, 10],
      outputRange: [1, 1, 0],
      extrapolate: 'clamp'
    })


    return (
      <View style={styles.container}>
        <TouchableWithoutFeedback onPress={this.explode} style={styles.container}>
          <View style={{transform: [{scale: .8}]}}>
            <Surface width={deviceWidth} height={deviceHeight}>
              <Group x={75} y={200}>
                <AnimatedShape
                  d={HEART_SVG}
                  x={heart_x}
                  y={heart_y}
                  scale={heart_scale}
                  fill={heart_fill}
                />
                <AnimatedCircle
                  x={89}
                  y={75}
                  radius={150}
                  scale={circle_scale}
                  strokeWidth={circle_stroke_width}
                  stroke={FILL_COLORS[2]}
                  fill={circle_fill_colors}
                  opacity={circle_opacity}
                />

                {this.getSmallExplosions(75, {x: 89, y: 75})}
              </Group>
            </Surface>
          </View>
        </TouchableWithoutFeedback>
      </View>
    );
  }
};

class AnimatedCircle extends Component {
  render() {
    var radius = this.props.radius;
    var path = Path().moveTo(0, -radius)
      .arc(0, radius * 2, radius)
      .arc(0, radius * -2, radius)
      .close();
    return React.createElement(AnimatedShape);
  }
}

var styles = StyleSheet.create({
  container: {
    flex: 1,
  }
});

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
图片连续滚动代码[兼容IE/firefox]
Jun 11 Javascript
js简单实现根据身份证号码识别性别年龄生日
Nov 29 Javascript
给ListBox添加双击事件示例代码
Dec 02 Javascript
把字符串按照特定的字母顺序进行排序的js代码
Jan 28 Javascript
jquery实现不包含当前项的选择器实例
Jun 25 Javascript
js实现无限级树形导航列表效果代码
Sep 23 Javascript
获取JavaScript异步函数的返回值
Dec 21 Javascript
vue2.0多条件搜索组件使用详解
Mar 26 Javascript
node koa2实现上传图片并且同步上传到七牛云存储
Jul 31 Javascript
React Native验证码倒计时工具类分享
Oct 24 Javascript
Vue中正确使用jQuery的方法
Oct 30 jQuery
JavaScript实现指定数量的并发限制的示例代码
Mar 10 Javascript
jQuery Easyui Treegrid实现显示checkbox功能
Aug 08 #jQuery
jQuery EasyUI的TreeGrid查询功能实现方法
Aug 08 #jQuery
EasyUI的TreeGrid的过滤功能的解决思路
Aug 08 #Javascript
angular+ionic返回上一页并刷新页面
Aug 08 #Javascript
微信小程序movable view移动图片和双指缩放实例代码
Aug 08 #Javascript
原生JS+Canvas实现五子棋游戏
May 28 #Javascript
React-router v4 路由配置方法小结
Aug 08 #Javascript
You might like
PHP 文件上传源码分析(RFC1867)
2009/10/30 PHP
php生成随机密码的三种方法小结
2010/09/04 PHP
php使用pack处理二进制文件的方法
2014/07/03 PHP
php实现的css文件背景图片下载器代码
2014/11/11 PHP
windows下配置php5.5开发环境及开发扩展
2014/12/25 PHP
PHP微信开发之文本自动回复
2016/06/23 PHP
Yii2框架dropDownList下拉菜单用法实例分析
2016/07/18 PHP
jquery remove方法应用详解
2012/11/22 Javascript
js根据鼠标移动速度背景图片自动旋转的方法
2015/02/28 Javascript
javascript的 {} 语句块详解
2016/02/27 Javascript
基于JavaScript实现快速转换文本语言(繁体中文和简体中文)
2016/03/07 Javascript
JavaScript实现格式化字符串函数String.format
2016/12/16 Javascript
详解@angular/cli 改变默认启动端口两种方式
2018/11/29 Javascript
TypeScript中的方法重载详解
2019/04/12 Javascript
JS中的算法与数据结构之栈(Stack)实例详解
2019/08/20 Javascript
js对象数组和对象的使用实例详解
2019/08/27 Javascript
vue + elementUI实现省市县三级联动的方法示例
2019/10/29 Javascript
如何用JS模拟实现数组的map方法
2020/07/30 Javascript
Ant Design Vue table中列超长显示...并加提示语的实例
2020/10/31 Javascript
[03:24]DOTA2超级联赛专访hao 大翻盘就是逆袭
2013/05/24 DOTA
[00:32]2018DOTA2亚洲邀请赛Liquid出场
2018/04/03 DOTA
python测试驱动开发实例
2014/10/08 Python
Python Sleep休眠函数使用简单实例
2015/02/02 Python
解决python通过cx_Oracle模块连接Oracle乱码的问题
2018/10/18 Python
对Python多线程读写文件加锁的实例详解
2019/01/14 Python
Python初学者常见错误详解
2019/07/02 Python
Form表单及django的form表单的补充
2019/07/25 Python
windows10 pycharm下安装pyltp库和加载模型实现语义角色标注的示例代码
2020/05/07 Python
python实现xlwt xlrd 指定条件给excel行添加颜色
2020/07/14 Python
三只松鼠官方旗舰店:全网坚果销售第1
2017/11/25 全球购物
泰国折扣酒店预订:Hotels2Thailand
2018/03/20 全球购物
美国波西米亚风格精品店:South Moon Under
2019/10/26 全球购物
迪卡侬(Decathlon)加拿大官网:源自法国的运动专业超市
2020/11/22 全球购物
施华洛世奇新加坡官网:SWAROVSKI新加坡
2020/10/06 全球购物
专科应届毕业生求职信
2014/06/04 职场文书
MySQL中IF()、IFNULL()、NULLIF()、ISNULL()函数的使用详解
2021/06/26 MySQL