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
Shape
Text
Path
代码示例绘制直线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的参数, 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,Path} = ART;
export default class Rect extends React.Component{
render(){
const path = new Path()
.moveTo(1,1)
.lineTo(1,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,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属性的使用,规则是“粗细 字号 字体” 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做路径绘制。 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,or);
if (ir) {
path.move(or - ir,0)
.counterArc(ir * 2,ir)
.counterArc(-ir * 2,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,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} />;
}
}
示例代码: 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,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,1)";
var FILL_COLORS = [
'rgba(221,70,136,1)','rgba(212,106,191,'rgba(204,142,245,'rgba(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,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,t) => { var scaleOut = this.state.animation.interpolate({ inputRange: [0,5.99,6,13.99,14,21],outputRange: [0,0],extrapolate: 'clamp' }); var moveUp = this.state.animation.interpolate({ inputRange: [0,14],-15],extrapolate: 'clamp' }); var moveDown = this.state.animation.interpolate({ inputRange: [0,15],extrapolate: 'clamp' }); var color_top_particle = this.state.animation.interpolate({ inputRange: [6,8,10,12,17,outputRange: shuffleArray(PARTICLE_COLORS) }) var color_bottom_particle = this.state.animation.interpolate({ inputRange: [6,outputRange: shuffleArray(PARTICLE_COLORS) }) var position = getXYParticle(7,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,18,28],outputRange: [1,.1,1.2,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,outputRange: [90,}) var heart_y = heart_scale.interpolate({ inputRange: [0,outputRange: [75,}) var circle_scale = this.state.animation.interpolate({ inputRange: [0,4],.3,extrapolate: 'clamp' }); var circle_stroke_width = this.state.animation.interpolate({ inputRange: [0,7,10],15,extrapolate: 'clamp' }); var circle_fill_colors = this.state.animation.interpolate({ inputRange: [1,4.99,5],outputRange: FILL_COLORS,extrapolate: 'clamp' }) var circle_opacity = this.state.animation.interpolate({ inputRange: [1,9.99,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,} });
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- react.js初学之环境搭建
- 【cocos2d-html5、cocos2dx+jsbinding、cocos2dx-lua】
- 以纬度和经度初始化swift中的CLLocation对象
- 使用xslt将CSV转换为XML – 如何使用递增列名称
- ruby-on-rails – 在rails中改变一周的第一天
- 有没有快捷方式在Xcode中的窗口之间切换?
- 正则表达式 – 解析mysql:/// sqlite:/// URL
- bonecp所依赖jar包
- ruby-on-rails – Rails 5 devise_token_auth无法验证CSRF令
- ruby-on-rails – rails使用bootstrap-sass而不是twitter-b