当前位置: 移动技术网 > IT编程>开发语言>JavaScript > react-native之ART绘图方法详解

react-native之ART绘图方法详解

2017年12月12日  | 移动技术网IT编程  | 我要评论

背景

在移动应用的开发过程中,绘制基本的二维图形或动画是必不可少的。然而,考虑到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' 添加;

这里写图片描述

2、选中项目根目录 ——> 点击'build phases‘ ——> 点击‘link binary with libraries' ——> 点击左下方‘+' ——> 选中‘libart.a'添加。

这里写图片描述

基础组件

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() : 封闭空间

代码示例

绘制直线

这里写图片描述

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像素空白

这里写图片描述

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做颜色填充.

这里写图片描述

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)的使用, 终点坐标距离起点坐标的相对距离。

这里写图片描述

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没看源码。

这里写图片描述

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',
  },

});

绘制扇形

这里写图片描述

在这里需要使用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>
    )
  }
}

综合示例

这里写图片描述

相关代码:

/**
 * 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,
  }
});

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网