You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中实现文本列最大长度适配与小数对齐的方法

Flutter实现两列对齐:首列自适应最长项宽度,次列按小数位对齐

需求说明

需要实现两种对齐效果:

  • 第一列宽度由列表中最长文本的长度决定,所有首列文本左对齐且宽度一致
  • 第二列按小数点对齐,确保所有数字的小数点处于同一竖线位置

给定数据:

Map<String,dynamic> map={
  'FootBall':200.30,
  'Hockey':3.30,
  'Golf':30.400
};

现有代码无法实现目标对齐效果,以下是解决方案:

实现步骤与代码

1. 计算首列最大宽度

通过TextPainter计算所有首列文本的渲染宽度,取最大值作为首列固定宽度,保证所有项宽度统一。

2. 拆分数字实现小数点对齐

将数字拆分为整数和小数部分,整数部分右对齐、小数部分左对齐,配合基线对齐确保小数点位置一致。

完整代码示例

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(child: const AlignDemo()),
      ),
    );
  }
}

class AlignDemo extends StatefulWidget {
  const AlignDemo({super.key});

  @override
  State<AlignDemo> createState() => _AlignDemoState();
}

class _AlignDemoState extends State<AlignDemo> {
  final Map<String, dynamic> map = {
    'FootBall': 200.30,
    'Hockey': 3.30,
    'Golf': 30.400,
  };

  // 计算首列最长文本的渲染宽度
  double getMaxLabelWidth(BuildContext context) {
    double maxWidth = 0;
    final textStyle = DefaultTextStyle.of(context).style;
    for (var key in map.keys) {
      final textPainter = TextPainter(
        text: TextSpan(text: key, style: textStyle),
        textDirection: TextDirection.ltr,
      )..layout(minWidth: 0, maxWidth: double.infinity);
      if (textPainter.width > maxWidth) maxWidth = textPainter.width;
    }
    return maxWidth;
  }

  // 将数字拆分为整数和小数部分
  Map<String, String> splitNumber(num number) {
    String numStr = number.toString();
    if (numStr.contains('.')) {
      List<String> parts = numStr.split('.');
      return {'integer': parts[0], 'decimal': parts[1]};
    } else {
      return {'integer': numStr, 'decimal': ''};
    }
  }

  // 计算整数部分的最大渲染宽度(优化对齐精度)
  double getMaxIntegerWidth(BuildContext context) {
    double maxWidth = 0;
    final textStyle = DefaultTextStyle.of(context).style;
    for (var value in map.values) {
      String integerPart = splitNumber(value)['integer']!;
      final textPainter = TextPainter(
        text: TextSpan(text: integerPart, style: textStyle),
        textDirection: TextDirection.ltr,
      )..layout(minWidth: 0, maxWidth: double.infinity);
      if (textPainter.width > maxWidth) maxWidth = textPainter.width;
    }
    return maxWidth;
  }

  @override
  Widget build(BuildContext context) {
    final maxLabelWidth = getMaxLabelWidth(context);
    final maxIntegerWidth = getMaxIntegerWidth(context);

    return Container(
      color: Colors.grey,
      height: 200,
      width: 300,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: map.keys.map((e) {
          final numParts = splitNumber(map[e]);
          return Padding(
            padding: const EdgeInsets.symmetric(vertical: 4),
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.baseline,
              textBaseline: TextBaseline.alphabetic,
              children: [
                // 首列固定宽度,左对齐
                SizedBox(
                  width: maxLabelWidth,
                  child: Text(e, textAlign: TextAlign.left),
                ),
                const SizedBox(width: 8),
                const Text(':'),
                const SizedBox(width: 8),
                // 整数部分右对齐,确保小数点对齐
                SizedBox(
                  width: maxIntegerWidth,
                  child: Text(numParts['integer']!, textAlign: TextAlign.right),
                ),
                // 显示小数部分
                if (numParts['decimal']!.isNotEmpty)
                  Text('.${numParts['decimal']}'),
              ],
            ),
          );
        }).toList(),
      ),
    );
  }
}

效果说明

运行代码后将得到目标对齐效果:

FootBall : 200.30
Hockey   :   3.30
Golf     :  30.400

内容的提问来源于stack exchange,提问作者Irfan Ganatra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 15:38:26