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)) }