一、前言
对于RN图表,有许多功能非常强大的插件,,如native-echarts,react-native-charts-wrapper等,不过今天我们要用animate和art来绘制一个简单的扇形统计图。首先要求我们对Animated和Art有较好的掌握。
Animated和Art的api繁多。本文只介绍与demo相关的API,如下:
ART
- Path:表示一个绘制路径
- Surface: 相当于画板组件,其他的组件要包括在Surface中
- Group:可以用Group来包括多个画图组件
- Shape: 主要负责path的绘制
- Text:文字需要包括在Text中
Animated
- Animated.parallel():同时启动多个动画,并行执行
- Animated.timing() :推动一个值按照一个过渡曲线而随时间变化。
Animated.timing参数如下:
- duration: 动画的持续时间(毫秒)。默认值为500.
- easing: 一个用于定义曲线的渐变函数。默认值为Easing.inOut(Easing.ease).
- delay: 开始动画前的延迟时间(毫秒)。默认为0.
更多API详见:https://reactnative.cn/docs/animated.html#docsNav
二、绘制饼状图
扇形图的绘制
ART绘制扇形因为涉及角度计算而比较麻烦,我从一些画图插件源码中提取了一部分代码,仅供实现本文demo:
degreesToRadians(degrees) {
if (degrees !== 0 && degrees % 360 === 0) { // 360, 720.
return Math.PI * 2;
}
return (degrees * (Math.PI / 180)) % (Math.PI * 2);
}
//startAngle:相对于12点的起始角度
//endAngle:相对于12点的起始角度
//or:半径
createArcPath(startAngle, endAngle, or) {
const path = new Path();
const linePath = new Path();
// 弧度角度
const sa = this._degreesToRadians(startAngle);
const ea = this._degreesToRadians(endAngle);
// 以弧度表示的中心弧角
const ca = sa > ea
? ((Math.PI * 2) - sa) + ea
: ea - sa;
// 正弦和余弦值
const ss = Math.sin(sa);
const es = Math.sin(ea);
const sc = Math.cos(sa);
const ec = Math.cos(ea);
// 对比差异
const ds = es - ss;
const dc = ec - sc;
const dr = 0 - or;
const large = ca > Math.PI;
path.move(or + 100 + (or * ss), or - (or * sc) + 30) // 起点
.arc(or * ds, or * -dc, or, or, large) // 外弧
.line(dr * es, dr * -ec);
return path;
}
注:圆的极坐标方程:x = r*cos(θ), y = r*sin(θ)
动态创建扇形:
由于原生的组件并不能识别Value,需要将动画元素用Animated包裹起来,在内部处理数据变更与DOM操作。
声明组件:AnimatedShape = Animated.createAnimatedComponent(Shape);
//创建扇形
createChartItem = (arr, total, preAngle) => {
let angle = 360 * (arr[0] / total);//计算改扇形应占比角度
let startAngle = preAngle;//开始角应为上个扇形的结束角
let endAngle = startAngle + angle;
let path = this._createArcPath(startAngle, endAngle, 60, 0);
return (<Group>
<AnimatedShape d={path.path} stroke="#396700" strokeWidth={1} fill={arr[1]} scale={this.state.scale}>
</AnimatedShape>
</Group>)
}
createCharts = (data) => {
let total = 0, //所有项目总的数量
preAngle = 0;//上一个角度
for (let i in data) {
total += data[i][0];
}
let outerCircle = Path().moveTo(60, 20).lineTo(30, 20)
return (<Animated.View>
<Surface width={300} height={250} style={{ marginTop: 10 }}>
<Group>
{data.map((val, index, arr) => {
if (index) {
preAngle = preAngle + 360 * (arr[index - 1][0] / total)
}
return this.createChartItem(val, total, preAngle)
})}
</Group>
</Surface>
</Animated.View>)
}
创建左上角指标图
createTags = (data) => {
let total = 0;
for (let i in data) {
total += data[i][0];
}
return (
<View >
{data.map((val, index, arr) => {
return (<View style={styles.tagging}>
<Animated.View style={{ width: this.state.width, height: 20, backgroundColor: val[1], marginTop: 5, marginRight: 5 }} />
<Text style={{ lineHeight: 30 }}>{val[2]}:{parseInt(val[0] / total * 100) + "%"}</Text>
</View>)
})}
</View>
)
}
三、实现动画
实现动画分为实现左上角指示图的渐现动画与扇形图缩放动画
toPlay = () => {
Animated.parallel([
//左上角指示图的渐现动画
Animated.spring(this.state.width, {
toValue: 50,//目标值
duration: 1200,//时间
easing: Easing.linear
}),
//图表缩放
Animated.timing(this.state.scale, {
toValue: 1,
duration: 1200,
easing: Easing.linear
})
]).start();
}
调用:
render() {
return (
<View style={styles.page}>
<View>{
this.createTags(this.props.cdata)
}</View>
{
this.createCharts(this.props.cdata)
}
</View>
)
}
// 数据
//cdata = [[100, '#FFB6C1', '西瓜'], [180, '#00BFFF', '牛奶'], [100, '#3CB371', '菠萝']];
效果图:
总结:
RN的动画支持度还是挺广的, CSS实现的动画RN基本也可以实现。RN中的动画均为 JavaScript 动画,即通过 JavaScript 代码控制图像的各种参数值的变化,从而产生时间轴上的动画效果。