import * as React from 'react';
// @ts-ignore
import * as RART from 'react-art';
import {
Animated,
ART,
Easing,
EasingFunction,
Platform,
View,
} from 'react-native';
import Doughnut from './doughnut';
// @ts-ignore
const { Surface } = Platform.OS === 'web' ? RART : ART;
const AnimatedDoughnut = Animated.createAnimatedComponent(Doughnut);
export interface IMDDoughnutArcSet {
color: string;
proportion: number; // todo
}
export interface IMDDoughnutProps {
// 时间属性
radius: number;
strokeWidth: number;
animate?: boolean;
animateTime?: number;
data: IMDDoughnutArcSet[];
}
export interface IMDDoughnutState {
arcProportAnimatedValues: Animated.Value[];
arcProportValues: number[];
}
export default class MDDoughnut extends React.Component<
IMDDoughnutProps,
IMDDoughnutState
> {
public static defaultProps = {
animate: true,
animateTime: 1000,
};
constructor (props: IMDDoughnutProps) {
super(props);
this.state = {
arcProportAnimatedValues: [],
arcProportValues: [],
};
}
private isAnimating: boolean = false;
private data: IMDDoughnutArcSet[] = [];
public componentDidMount () {
if (this.props.animate && Platform.OS !== 'web') {
this.isAnimating = true;
setTimeout(() => {
this.startAnimatedFill();
}, 100);
}
}
public componentDidUpdate () {
if (this.props.animate) {
this.isAnimating = true;
setTimeout(() => {
this.startAnimatedFill();
}, 100);
}
}
public shouldComponentUpdate (nextProps: IMDDoughnutProps) {
if (
JSON.stringify(this.props.data) === JSON.stringify(nextProps.data) ||
this.isAnimating
) {
return false;
}
return true;
}
public componentWillReceiveProps (nextProps: IMDDoughnutProps) {
if (JSON.stringify(this.props.data) !== JSON.stringify(nextProps.data))
this.setState({
arcProportValues: [],
arcProportAnimatedValues: [],
});
}
public render () {
const { radius, strokeWidth, data } = this.props;
this.data = [];
this.data.push(...data);
const diameter = radius * 2;
if (!this.data || !this.data.length) {
return null;
}
return (
{this.combineCircle(this.data, diameter, strokeWidth)}
);
}
private startAnimatedFill () {
const values = this.state.arcProportValues;
const animatedValues = this.state.arcProportAnimatedValues;
const arcWalker = (index: number) => {
if (index < values.length) {
Animated.timing(animatedValues[index], {
duration: this.animateDuration(index, values.length),
easing: this.animateType(),
toValue: values[index],
}).start(() => {
setTimeout(() => {
arcWalker(++index);
}, 0);
});
} else {
this.isAnimating = false;
}
};
setTimeout(() => {
values.length && arcWalker(0);
}, 0);
}
private animateDuration (index: number, maxLength: number) {
const per = this.props.animateTime! / maxLength;
return index === maxLength - 1 ? per / 2 : (maxLength - index) * per;
}
private animateType (): EasingFunction {
return Easing.in(Easing.sin);
}
private combineCircle (
data: IMDDoughnutArcSet[],
diameter: number,
strokeWidth: number
) {
if (!data || !data.length) {
return null;
}
// 为最后一个环带添加尾部用于遮盖第一个环带
const arcNum = data.length;
const lastArc = data[arcNum - 1];
lastArc.proportion -= 3.5;
data.push({
color: lastArc.color,
proportion: 3,
});
let lastArcDegree = 0;
const arcs = [];
for (let index = 0, len = data.length; index < len; index++) {
const arcData = data[index];
const arcProportion = this.extractProportion(arcData.proportion);
const arcProportionAnimatedValue = new Animated.Value(0);
this.state.arcProportValues.push(arcProportion);
this.state.arcProportAnimatedValues.push(arcProportionAnimatedValue);
const arc = this.renderArc(
this.props.animate === true
? this.state.arcProportAnimatedValues[index]
: this.state.arcProportValues[index],
diameter,
lastArcDegree,
strokeWidth,
arcData.color,
index
);
// 计算上个环带的角度,用于下一个环带的rotation
lastArcDegree += Doughnut.arcDegree(arcProportion);
/**
* 后面的Path在前一个Path上层,无法用zIndex更改
* 后出现的环带在下层,倒序插入渲染数组中
* 最后的环带尾部添加渲染数字最后
*/
if (index < len - 1) {
arcs.unshift(arc);
} else {
arcs.push(arc);
}
}
return arcs;
}
private renderArc (
proportion: number | Animated.Value,
diameter: number,
rotation: number,
strokeWidth: number,
color: string,
index: number
) {
return (
);
}
private extractProportion = (proportion: number) =>
Math.min(100, Math.max(0, proportion))
}