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

适配屏幕尺寸的动态高度计算及卡片等高实现方案

解决方案:动态适配高度、避免栈溢出并统一卡片高度

问题根源分析

  1. 栈溢出:水平ListView设置了shrinkWrap: true,同时父容器固定高度,导致ListView尝试递归计算所有子项高度,触发布局循环。
  2. 高度适配问题:仅用安全区域比例计算高度,未考虑系统大字体缩放,导致大字体下文本溢出、卡片高度异常;固定比例在不同设备上适配性差。
  3. 卡片高度不一致:子项卡片内的Column使用MainAxisSize.min,会根据内容自动收缩,无法保证所有卡片高度统一。

具体修复步骤

1. 解决栈溢出

直接移除ListView.separated中的shrinkWrap: true——父容器已经通过SizedBox指定了固定高度,水平ListView不需要自动收缩,这个配置多余且会引发布局递归计算。

2. 动态适配高度(兼容大字体)

  • 加入文本缩放因子:通过MediaQuery.textScaleFactorOf(context)获取系统字体缩放比例,调整高度计算,避免大字体下文本溢出。
  • 优化高度计算逻辑:结合屏幕安全区域高度和文本缩放,让高度在不同设备和字体配置下更合理。

3. 确保所有卡片高度一致

将子项卡片内的Column的mainAxisSize改为MainAxisSize.max,配合Expanded按比例分配图片和文本区域的高度,强制所有卡片填充父容器高度,实现统一。


修改后的完整代码

// 简化AppSizeConfig,新增文本缩放因子适配大字体
class AppSizeConfig {
  late final double width;
  late final double height;
  late final double textScaleFactor;
  late final double safeAreaVertical;
  late final double safeAreaBlockVertical;
  late final double safeAreaBlockHorizontal;

  AppSizeConfig(BuildContext context) {
    final data = MediaQuery.of(context);
    width = data.size.width;
    height = data.size.height;
    textScaleFactor = data.textScaleFactor;
    safeAreaVertical = data.padding.top + data.padding.bottom;
    safeAreaBlockVertical = (height - safeAreaVertical) / 100;
    safeAreaBlockHorizontal = (width - (data.padding.left + data.padding.right)) / 100;
  }
}

class _StoreCategoryListResults extends StatelessWidget {
  final StoreCategory item;

  const _StoreCategoryListResults(this.item, {super.key});

  @override
  Widget build(BuildContext context) {
    final size = AppSizeConfig(context);
    // 结合文本缩放因子动态调整高度,大字体下适度缩放避免过高
    final containerHeight = size.safeAreaBlockVertical * 30 * (size.textScaleFactor > 1.2 ? size.textScaleFactor * 0.9 : 1);
    return SizedBox(
      height: containerHeight,
      child: ListView.separated(
        itemCount: item.articles.length,
        scrollDirection: Axis.horizontal,
        separatorBuilder: (_, __) => const SizedBox(width: 7.0),
        itemBuilder: (context, index) => _StoreCategoryListContentResult(item.articles[index]),
      ),
    );
  }
}

class _StoreCategoryListContentResult extends StatelessWidget {
  final Article item;

  const _StoreCategoryListContentResult(this.item, {super.key});

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final textTheme = theme.textTheme;
    final size = AppSizeConfig(context);

    return SizedBox(
      width: size.safeAreaBlockHorizontal * 30,
      child: Card(
        margin: EdgeInsets.zero,
        clipBehavior: Clip.antiAliasWithSaveLayer,
        child: Column(
          // 改为MainAxisSize.max,强制填充父容器高度,保证所有卡片高度一致
          mainAxisSize: MainAxisSize.max,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Expanded(
              flex: 3,
              child: Stack(
                alignment: AlignmentDirectional.center,
                children: [
                  Align(
                    alignment: Alignment.center,
                    child: ImageNetwork(
                      src: item.product.imurl,
                      fromSize: const Size(150, 150),
                    ),
                  ),
                ],
              ),
            ),
            Expanded(
              flex: 2,
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  mainAxisAlignment: MainAxisAlignment.start,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      item.product.contentNetWithUnitTypeCode,
                      maxLines: 1,
                      style: textTheme.bodySmall!.copyWith(
                        color: Colors.black54,
                        fontWeight: FontWeight.w300,
                      ),
                    ),
                    Text(
                      item.basePrice.thousandWithSymbol,
                      overflow: TextOverflow.ellipsis,
                      style: textTheme.bodyMedium!.copyWith(
                        fontWeight: FontWeight.w500,
                      ),
                    ),
                    Text(
                      item.product.name,
                      maxLines: 2,
                      style: textTheme.bodyMedium,
                      overflow: TextOverflow.ellipsis,
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

额外优化建议

  • 可以把AppSizeConfig改为单例或者直接用MediaQuery获取参数,避免每次build都新建实例,提升性能。
  • 如果需要更精准的适配,可使用LayoutBuilder获取父容器的约束,动态计算卡片宽高,替代固定比例方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 11:37:07