UNPKG

react-native-circular-progress

Version:

React Native component for creating animated, circular progress with react-native-svg

170 lines (156 loc) 4.86 kB
import React from 'react'; import PropTypes from 'prop-types'; import { Animated, View } from 'react-native'; import { Svg, Path, G } from 'react-native-svg'; export default class CircularProgress extends React.PureComponent { polarToCartesian(centerX, centerY, radius, angleInDegrees) { var angleInRadians = ((angleInDegrees - 90) * Math.PI) / 180.0; return { x: centerX + radius * Math.cos(angleInRadians), y: centerY + radius * Math.sin(angleInRadians), }; } circlePath(x, y, radius, startAngle, endAngle) { var start = this.polarToCartesian(x, y, radius, endAngle * 0.9999999); var end = this.polarToCartesian(x, y, radius, startAngle); var largeArcFlag = endAngle - startAngle <= 180 ? '0' : '1'; var d = ['M', start.x, start.y, 'A', radius, radius, 0, largeArcFlag, 0, end.x, end.y]; return d.join(' '); } clampFill = fill => Math.min(100, Math.max(0, fill)); render() { const { size, width, backgroundWidth, tintColor, tintTransparency, backgroundColor, style, rotation, lineCap, fillLineCap = lineCap, arcSweepAngle, fill, children, childrenContainerStyle, padding, renderCap, dashedBackground, dashedTint } = this.props; const maxWidthCircle = backgroundWidth ? Math.max(width, backgroundWidth) : width; const sizeWithPadding = size / 2 + padding / 2; const radius = size / 2 - maxWidthCircle / 2 - padding / 2; const currentFillAngle = (arcSweepAngle * this.clampFill(fill)) / 100; const backgroundPath = this.circlePath( sizeWithPadding, sizeWithPadding, radius, tintTransparency ? 0 : currentFillAngle, arcSweepAngle ); const circlePath = this.circlePath( sizeWithPadding, sizeWithPadding, radius, 0, currentFillAngle ); const coordinate = this.polarToCartesian( sizeWithPadding, sizeWithPadding, radius, currentFillAngle ); const cap = this.props.renderCap ? this.props.renderCap({ center: coordinate }) : null; const offset = size - maxWidthCircle * 2; const localChildrenContainerStyle = { ...{ position: 'absolute', left: maxWidthCircle + padding / 2, top: maxWidthCircle + padding / 2, width: offset, height: offset, borderRadius: offset / 2, alignItems: 'center', justifyContent: 'center', overflow: 'hidden', }, ...childrenContainerStyle, } const strokeDasharrayTint = dashedTint.gap > 0 ? Object.values(dashedTint) .map(value => parseInt(value)) : null; const strokeDasharrayBackground = dashedBackground.gap > 0 ? Object.values(dashedBackground) .map(value => parseInt(value)) : null; return ( <View style={style}> <Svg width={size + padding} height={size + padding}> <G rotation={rotation} originX={(size + padding) / 2} originY={(size + padding) / 2}> {backgroundColor && ( <Path d={backgroundPath} stroke={backgroundColor} strokeWidth={backgroundWidth || width} strokeLinecap={lineCap} strokeDasharray={strokeDasharrayBackground} fill="transparent" /> )} {fill > 0 && ( <Path d={circlePath} stroke={tintColor} strokeWidth={width} strokeLinecap={fillLineCap} strokeDasharray={strokeDasharrayTint} fill="transparent" /> )} {cap} </G> </Svg> {children && <View style={localChildrenContainerStyle}>{children(fill)}</View>} </View> ); } } CircularProgress.propTypes = { style: PropTypes.oneOfType([ PropTypes.object, PropTypes.array, ]), size: PropTypes.oneOfType([ PropTypes.number, PropTypes.instanceOf(Animated.Value), ]).isRequired, fill: PropTypes.number.isRequired, width: PropTypes.number.isRequired, backgroundWidth: PropTypes.number, tintColor: PropTypes.string, tintTransparency: PropTypes.bool, backgroundColor: PropTypes.string, rotation: PropTypes.number, lineCap: PropTypes.string, arcSweepAngle: PropTypes.number, children: PropTypes.func, childrenContainerStyle: PropTypes.object, padding: PropTypes.number, renderCap: PropTypes.func, dashedBackground: PropTypes.object, dashedTint: PropTypes.object }; CircularProgress.defaultProps = { tintColor: 'black', tintTransparency: true, rotation: 90, lineCap: 'butt', arcSweepAngle: 360, padding: 0, dashedBackground: { width: 0, gap: 0 }, dashedTint: { width: 0, gap: 0 }, };