import * as React from 'react';
import { Animated, StyleSheet, Text, TextStyle, View } from 'react-native';
import accounting from '../../_utils/accounting';
export interface IMDAmountProps {
style?: TextStyle | TextStyle[];
amount: number | string;
precision?: number;
symbol?: string;
thousand?: string;
decimal?: string;
format?: string;
fontFamily?: string;
fontSize?: number;
color?: string;
mask?: boolean;
// for transition
transition?: boolean;
fontHeight?: number;
containerHeight?: number;
containerStyle?: TextStyle | TextStyle[];
duration?: number;
autoStart?: boolean;
}
export interface IMDAmountState {
animatedValue: Animated.Value;
}
/**
* 金额展示组件
* 格式化的参数参考:http://openexchangerates.github.io/accounting.js/#documentation
*/
export default class MDAmount extends React.Component<
IMDAmountProps,
IMDAmountState
> {
public static defaultProps = {
precision: 2,
symbol: '',
thousand: ',',
decimal: '.',
format: '%s%v',
fontFamily: 'FD+_Number',
mask: false,
startAnim: true, // 默认创建DidMount后启动动画
transition: false,
};
constructor (props: IMDAmountProps) {
super(props);
if (props.amount === undefined) {
throw new Error('The MDAmount need set amount');
}
const { transition, containerHeight, fontHeight } = props;
if (transition) {
if (!containerHeight) {
throw new Error(
'The MDAmount in transition mode need set containerHeight'
);
}
if (!fontHeight) {
throw new Error('The MDAmount in transition mode need set fontHeight');
}
}
this.state = {
animatedValue: new Animated.Value(0),
};
this.startTransition = this.startTransition.bind(this);
}
private oldAmount: number | string = 0;
public componentDidMount () {
const { transition, autoStart, amount } = this.props;
if (transition && autoStart) {
this.startTransition(() => {
this.oldAmount = amount || 0;
});
}
}
public componentWillReceiveProps (nextProps: IMDAmountProps) {
const { amount, transition } = nextProps;
const { transition: _transition, amount: _amount } = this.props;
if (transition && amount !== _amount) {
this.setState(
{
animatedValue: new Animated.Value(0),
},
() => {
this.startTransition(() => {
this.oldAmount = amount || 0;
});
}
);
}
if (transition && !_transition) {
this.oldAmount = _amount === amount ? 0 : _amount;
this.setState(
{
animatedValue: new Animated.Value(0),
},
() => {
this.startTransition(() => {
this.oldAmount = amount || 0;
});
}
);
}
}
public render () {
const { amount, mask, transition, fontHeight, containerHeight } = this.props;
const oldAmount = this.oldAmount;
const oldFormatAmount = this.formatMoney(oldAmount);
const formatAmount = this.formatMoney(amount);
const fontStyle: TextStyle = this.genFontStyle();
if (transition && fontHeight && containerHeight && !mask) {
return this.renderAnimAmount(oldFormatAmount, formatAmount, fontStyle);
}
return {mask ? '****' : formatAmount};
}
private formatMoney (amount?: number | string): string {
const { precision, symbol, thousand, decimal, format } = this.props;
return amount !== undefined
? accounting.formatMoney(amount, {
precision,
symbol,
thousand,
decimal,
format,
})
: 0;
}
private genFontStyle (): TextStyle {
const { fontFamily, fontSize, color, style, fontHeight } = this.props;
return StyleSheet.flatten([
{
fontFamily,
fontSize,
color,
lineHeight: fontHeight,
letterSpacing: -1,
},
style,
]);
}
private renderAnimAmount (
oldFormatAmount: any,
formatAmount: any,
fontStyle: TextStyle
) {
const { fontHeight, containerHeight: height, containerStyle } = this.props;
let oldAmounts = oldFormatAmount.split('');
let formatAmounts = formatAmount.split('');
if (oldAmounts.length < formatAmounts.length) {
oldAmounts = new Array(formatAmounts.length - oldAmounts.length)
.fill('0')
.concat(oldAmounts);
} else if (oldAmounts.length > formatAmounts.length) {
formatAmounts = new Array(oldAmounts.length - formatAmounts.length)
.fill('')
.concat(formatAmounts);
}
const { columns, transitions, rowHeights } = this.renderColumnCells(
formatAmounts,
oldAmounts,
fontHeight!,
fontStyle
);
const animColumnViews = columns.map(
(column: React.ReactNode, i: number) => {
return (
{column}
);
}
);
return (
{animColumnViews}
);
}
private renderColumnCells (
formatAmounts: string[],
oldAmounts: string[],
fontHeight: number,
fontStyle: TextStyle
) {
const transitions: any[] = [];
const rowHeights: number[] = [];
const columns = formatAmounts.map((num: string, k: number) => {
const oldNum = isNaN(+oldAmounts[k]) ? 0 : +oldAmounts[k];
const newNum = +num;
let n = 1; // 多少个 cell,第一列个数的基数为 1,第二列为 11,以此类推
if (num.match(/\d/g)) {
n = 10 * k + 1;
if (oldNum > newNum) {
// 7 -> 2
n += 10 - oldNum + newNum;
} else if (oldNum < newNum) {
// 2 -> 7
n += newNum - oldNum;
}
}
const _transition = this.state.animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [0, -(fontHeight! * (n - 1))],
});
transitions.push(_transition);
rowHeights.push(fontHeight! * n);
// @ts-ignore
return new Array(n).fill(0).map((v, i) => {
return (
{num.match(/\d/g) ? (i + oldNum) % 10 : num}
);
});
});
return { columns, transitions, rowHeights };
}
private startTransition (cb?: () => void) {
const duration = this.props.duration || 2000;
Animated.timing(this.state.animatedValue, {
duration,
toValue: 1,
useNativeDriver: true,
}).start(() => {
cb && cb();
});
}
}
const styles = StyleSheet.create({
container: {
overflow: 'hidden',
flexDirection: 'row',
borderColor: 'transparent',
},
});