Harmonyos应用实例106:百分数与分数互化器
·
应用实例六:百分数与分数互化器
知识点:理解百分数的意义,掌握分数、小数、百分数之间的互化。
功能:一个三联动的转换器。输入分数,自动转换为小数和百分数;或者输入百分数,自动转换为分数和小数。图形化显示百分数的含义(如“80%”显示为100个格子填充了80个)。
// FractionDecimalPercentConverter.ets
interface ConverterState {
fraction: string
decimal: string
percent: string
isValid: boolean
errorMessage: string
}
interface ConversionResult {
fraction: string
decimal: string
percent: string
}
interface FractionParts {
numerator: number
denominator: number
}
@Entry
@Component
struct FractionDecimalPercentConverter {
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
@State private state: ConverterState = {
fraction: '',
decimal: '',
percent: '',
isValid: true,
errorMessage: ''
}
@State private activeInput: 'fraction' | 'decimal' | 'percent' | '' = ''
build() {
Column({ space: 12 }) {
Text('🔄 分数-小数-百分数转换器')
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor('#2C3E50')
if (!this.state.isValid) {
Text(this.state.errorMessage)
.fontSize(12)
.fontColor('#E74C3C')
.padding(8)
.backgroundColor('#FADBD8')
.borderRadius(8)
.width('95%')
}
Column({ space: 12 }) {
this.InputField('分数', this.state.fraction, 'fraction')
this.InputField('小数', this.state.decimal, 'decimal')
this.InputField('百分数', this.state.percent, 'percent')
}
.width('95%')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(8)
Canvas(this.context)
.width(340)
.height(120)
.backgroundColor('#F8F9FA')
.borderRadius(8)
.shadow({ radius: 3, color: '#00000010' })
.onReady(() => {
this.drawPercentageGrid()
})
Column() {
Text('💡 转换原理')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor('#2C3E50')
Text('• 分数 → 小数:分子 ÷ 分母')
.fontSize(11)
.fontColor('#7F8C8D')
.margin({ top: 4 })
Text('• 小数 → 百分数:小数 × 100%')
.fontSize(11)
.fontColor('#7F8C8D')
.margin({ top: 2 })
Text('• 百分数 → 分数:百分数 ÷ 100,约分')
.fontSize(11)
.fontColor('#7F8C8D')
.margin({ top: 2 })
Text('• 百分数网格:100个格子代表100%,填充部分代表具体百分比')
.fontSize(11)
.fontColor('#3498DB')
.margin({ top: 2 })
}
.width('95%')
.padding(12)
.backgroundColor('#EBF5FB')
.borderRadius(8)
.alignItems(HorizontalAlign.Start)
Row({ space: 12 }) {
Button('🔄 转换')
.fontSize(12)
.height(36)
.width('30%')
.backgroundColor('#3498DB')
.onClick(() => this.convertAll())
Button('🗑️ 清空')
.fontSize(12)
.height(36)
.width('30%')
.backgroundColor('#95A5A6')
.onClick(() => this.clearAll())
}
.width('95%')
.justifyContent(FlexAlign.Center)
}
.width('100%')
.height('100%')
.backgroundColor('#F0F3F6')
.padding(12)
}
@Builder
InputField(label: string, value: string, type: 'fraction' | 'decimal' | 'percent') {
Row({ space: 8 }) {
Text(label)
.fontSize(12)
.fontColor('#2C3E50')
.width(50)
TextInput({
placeholder: `输入${label}`,
text: value
})
.width('100%')
.height(44)
.backgroundColor('#F8F9FA')
.borderRadius(6)
.padding({ left: 12, right: 12 })
.onFocus(() => {
this.activeInput = type
})
.onBlur(() => {
if (this.activeInput === type) {
if (type === 'fraction') {
this.processInput(type, this.state.fraction)
} else if (type === 'decimal') {
this.processInput(type, this.state.decimal)
} else {
this.processInput(type, this.state.percent)
}
}
})
.onChange((newValue: string) => {
if (this.activeInput === type) {
this.updateState(type, newValue)
}
})
.onSubmit(() => {
if (type === 'fraction') {
this.processInput(type, this.state.fraction)
} else if (type === 'decimal') {
this.processInput(type, this.state.decimal)
} else {
this.processInput(type, this.state.percent)
}
})
}
}
private updateState(type: 'fraction' | 'decimal' | 'percent', value: string) {
let newState: ConverterState
if (type === 'fraction') {
newState = {
fraction: value,
decimal: this.state.decimal,
percent: this.state.percent,
isValid: this.state.isValid,
errorMessage: this.state.errorMessage
}
} else if (type === 'decimal') {
newState = {
fraction: this.state.fraction,
decimal: value,
percent: this.state.percent,
isValid: this.state.isValid,
errorMessage: this.state.errorMessage
}
} else {
newState = {
fraction: this.state.fraction,
decimal: this.state.decimal,
percent: value,
isValid: this.state.isValid,
errorMessage: this.state.errorMessage
}
}
this.state = newState
}
private processInput(type: 'fraction' | 'decimal' | 'percent', value: string) {
try {
let result: ConversionResult
if (type === 'fraction') {
result = this.fractionToOthers(value)
} else if (type === 'decimal') {
result = this.decimalToOthers(value)
} else {
result = this.percentToOthers(value)
}
this.state = {
fraction: result.fraction,
decimal: result.decimal,
percent: result.percent,
isValid: true,
errorMessage: ''
} as ConverterState
this.drawPercentageGrid()
} catch (error) {
this.state = {
fraction: this.state.fraction,
decimal: this.state.decimal,
percent: this.state.percent,
isValid: false,
errorMessage: error.message
} as ConverterState
}
}
private fractionToOthers(fraction: string): ConversionResult {
if (!fraction) {
return { fraction: '', decimal: '', percent: '' }
}
const match = fraction.match(/^(\d+)(?:\/(\d+))?$/)
if (!match) {
throw new Error('请输入正确的分数格式,如 3/4')
}
const numerator = parseInt(match[1])
const denominator = match[2] ? parseInt(match[2]) : 1
if (denominator === 0) {
throw new Error('分母不能为0')
}
const decimal = (numerator / denominator).toFixed(4)
const percent = (parseFloat(decimal) * 100).toFixed(2) + '%'
return {
fraction: this.simplifyFraction(numerator, denominator),
decimal: decimal.replace(/\.?0+$/, ''),
percent: percent
}
}
private decimalToOthers(decimal: string): ConversionResult {
if (!decimal) {
return { fraction: '', decimal: '', percent: '' }
}
const value = parseFloat(decimal)
if (isNaN(value)) {
throw new Error('请输入正确的小数')
}
const percent = (value * 100).toFixed(2) + '%'
const fractionParts = this.decimalToFraction(value)
const fraction = this.simplifyFraction(fractionParts.numerator, fractionParts.denominator)
return {
fraction: fraction,
decimal: decimal,
percent: percent
}
}
private percentToOthers(percent: string): ConversionResult {
if (!percent) {
return { fraction: '', decimal: '', percent: '' }
}
const value = parseFloat(percent.replace('%', ''))
if (isNaN(value)) {
throw new Error('请输入正确的百分数')
}
const decimal = (value / 100).toFixed(4).replace(/\.?0+$/, '')
const fractionParts = this.decimalToFraction(value / 100)
const fraction = this.simplifyFraction(fractionParts.numerator, fractionParts.denominator)
return {
fraction: fraction,
decimal: decimal,
percent: value.toFixed(2) + '%'
}
}
private decimalToFraction(decimal: number): FractionParts {
let numerator = decimal
let denominator = 1
while (numerator % 1 !== 0) {
numerator *= 10
denominator *= 10
}
return { numerator: Math.round(numerator), denominator: denominator }
}
private simplifyFraction(numerator: number, denominator: number): string {
const gcd = this.gcd(numerator, denominator)
const simplifiedNum = numerator / gcd
const simplifiedDen = denominator / gcd
if (simplifiedDen === 1) {
return simplifiedNum.toString()
}
return `${simplifiedNum}/${simplifiedDen}`
}
private gcd(a: number, b: number): number {
return b === 0 ? a : this.gcd(b, a % b)
}
private clearAll(): void {
this.state = {
fraction: '',
decimal: '',
percent: '',
isValid: true,
errorMessage: ''
}
this.activeInput = ''
this.drawPercentageGrid()
}
private convertAll(): void {
if (this.activeInput === 'fraction' && this.state.fraction) {
this.processInput('fraction', this.state.fraction)
} else if (this.activeInput === 'decimal' && this.state.decimal) {
this.processInput('decimal', this.state.decimal)
} else if (this.activeInput === 'percent' && this.state.percent) {
this.processInput('percent', this.state.percent)
} else if (this.state.fraction) {
this.processInput('fraction', this.state.fraction)
} else if (this.state.decimal) {
this.processInput('decimal', this.state.decimal)
} else if (this.state.percent) {
this.processInput('percent', this.state.percent)
}
}
private drawPercentageGrid(): void {
const ctx = this.context
const w = 340
const h = 120
ctx.clearRect(0, 0, w, h)
ctx.fillStyle = '#F8F9FA'
ctx.fillRect(0, 0, w, h)
const gridSize = 10
const cellSize = 10
const startX = 20
const startY = 20
let percent = 0
const percentMatch = this.state.percent.match(/^(\d+(?:\.\d+)?)%$/)
if (percentMatch) {
percent = parseFloat(percentMatch[1])
}
const filledCells = Math.min(Math.round(percent), 100)
ctx.strokeStyle = '#E0E0E0'
ctx.lineWidth = 1
for (let i = 0; i < gridSize; i++) {
for (let j = 0; j < gridSize; j++) {
const x = startX + j * cellSize
const y = startY + i * cellSize
const cellIndex = i * gridSize + j
if (cellIndex < filledCells) {
ctx.fillStyle = '#3498DB'
ctx.fillRect(x, y, cellSize, cellSize)
}
ctx.strokeRect(x, y, cellSize, cellSize)
}
}
ctx.fillStyle = '#2C3E50'
ctx.font = '12px sans-serif'
ctx.textAlign = 'center'
ctx.fillText(`${percent}%`, startX + gridSize * cellSize / 2, startY + gridSize * cellSize + 25)
ctx.fillStyle = '#7F8C8D'
ctx.font = '10px sans-serif'
ctx.fillText('100个格子 = 100%', startX + gridSize * cellSize / 2, startY + gridSize * cellSize + 40)
}
}
更多推荐



所有评论(0)