微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

react-native之ART绘图详解

原文地址

背景

在移动应用的开发过程中,绘制基本的二维图形或动画是必不可少的。然而,考虑到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,94)">//连线到目标点(300,1)

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

绘制虚线

了解strokeDash的参数,
[10,5] : 表示绘10像素实线在绘5像素空白,如此循环
[10,5,20,5] : 表示绘10像素实线在绘制5像素空白在绘20像素实线及5像素空白

import React from 'react' import { View,ART } 'react-native' const {Surface,Shape,Path} = ART; export class DashLine Component{ render(){ const path = Path() .moveto(1) .lineto(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做颜色填充.

class Rect const path = new Path()
            .moveto(99)
            .lineto(99,255)">1)
            .close();

        {100} height={100}> <"#000000" fill="#892265" strokeWidth={1} /> </View> ) } } 

绘圆

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

class Circle 50,255)">1) .arc(0,255)">25) .arc(-99,255)">25) .close(); {1}/> </View> ) } }

绘制文字

了解funt属性的使用,规则是“粗细 字号 字体”
注意: 字体应该是支持path属性的,应该是实现bug并没有不生效。 Android通过修改源码是可以解决的,IOS没看源码。

import React,{Component} 'react'; import { AppRegistry,StyleSheet,ART,View } 'react-native'; class ArtTextView extends Component { render() { return ( <{styles.container}> <Text strokeWidth={1} stroke="#000" font="bold 35px Heiti SC" path={new Path().moveto(40,40).lineto(99,10)} >React</Text> </View> ); } } const styles = StyleSheet.create({ container: { flex: justifyContent: 'center',alignItems: backgroundColor: '#F5FCFF',},});

绘制扇形

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

ottom:10px; padding:0px; word-wrap:break-word; overflow:auto; font-size:13px; line-height:1.42857143; font-family:Menlo,{ Component,PropTypes } from 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>. */
default class Wedge extends Component<void,any,116)">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;
        (any).radiansPerDegree = Math.PI / 180;
        (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 (any).circleradians;
        }
        return degrees * (any).radiansPerDegree % (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 {
        new Path();

        path.move(2,or)
            .arc(-or * if (ir) {
            path.move(or - ir,255)">0)
                .counterarc(ir * terarc(-ir * 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 : new Path();

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

        // central arc angle in radians
        const ca = sa > ea ? (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,large) // outer arc
            .line(dr * es,dr * -ec);   // width of arc or wedge

        if (ir) {
            path.counterarc(ir * -ds,ir * dc,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,ir);
        }

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

示例代码

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

class Fan Wedge outerRadius={50} startAngle={0} endAngle={60} originX={50} originY={50} fill="blue"/> </View> ) } } 

综合示例

相关代码

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

import {
    ART as Art,View,Dimensions,TouchableWithoutFeedback,Animated
} from 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,77,1)';
var GRAY_HEART_COLOR = "rgb(204,204,1)";

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

var PARTICLE_COLORS = [
    'rgb(158,202,250)',116)">'rgb(161,235,206)',116)">'rgb(208,148,246)',116)">'rgb(244,141,166)',116)">'rgb(234,171,104)',116)">'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 / 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,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 [3,255)">4,255)">5,255)">6].map((v,t) => {

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

            var moveUp = 14],255)">-15],114)">var moveDown = 15],114)">var color_top_particle = 8,255)">10,255)">12,255)">17,outputRange: shuffleArray(PARTICLE_COLORS)
            })

            var color_bottom_particle = var position = getXYParticle(7,radius)

            return (
                <Group
                    x={position.x + offset.x }
                    y={position.y + offset.y}
                    rotation={getRandomInt(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: [.01,255)">18,255)">28],255)">.1,255)">1.2,255)">1],116)">'clamp'
        });

        var heart_fill = 2],outputRange: [GRAY_HEART_COLOR,HEART_COLOR],116)">'clamp'
        })

        var heart_x = heart_scale.interpolate({
            inputRange: [90,})

        var heart_y = heart_scale.interpolate({
            inputRange: [75,114)">var circle_scale = 4],255)">.3,114)">var circle_stroke_width = 10],255)">15,114)">var circle_fill_colors = 4.99,255)">5],outputRange: FILL_COLORS,114)">var circle_opacity = 9.99,116)">'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(89,y: 75})}
                            </Group>
                        </Surface>
                    </View>
                </TouchableWithoutFeedback>
            </View>
        );
    }
};

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

var styles = StyleSheet.create({
    container: {
        flex: 原文地址

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐