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

Flutter:根据设备字体大小调整ListView容器高度,保持元素等高

解决ListView元素高度适配系统字体且保持一致的问题

问题根源

你的代码中存在多处硬编码的固定高度(如列表项容器高度、文本容器高度),同时ListView外层设置了固定高度,导致系统字体放大时,文本无法撑开容器而被截断;另外,默认情况下横向ListView的子元素高度会各自自适应,无法保证统一高度。

解决方案步骤

1. 移除所有硬编码的固定高度

去掉所有限制文本或容器高度的固定值,让Widget根据内容自动调整高度:

  • 移除_title、_subtitle、_price中的height属性
  • 移除ListView外层容器的height: sliderHeight固定值
  • 保留图片容器、按钮的固定高度(如需统一这类元素的尺寸)

2. 确保所有列表项高度一致

使用IntrinsicHeight包裹每个列表项的Column,让所有子元素高度匹配列表中最高的项;同时添加Spacer()让底部元素对齐,避免布局错乱。

3. 适配系统字体缩放

不要使用固定的fontSize,改用Flutter的文本主题,确保字体大小随系统设置自动缩放。

修改后的完整代码

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(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
        textTheme: const TextTheme(
          titleMedium: TextStyle(
            fontSize: 14,
            fontFamily: "Proxima Nova, Condensed",
          ),
          bodyMedium: TextStyle(
            fontSize: 14,
            fontFamily: "Proxima Nova",
          ),
          labelMedium: TextStyle(
            fontSize: 14,
            fontFamily: "Proxima Nova, Regular",
          ),
          titleLarge: TextStyle(
            fontSize: 20,
            fontFamily: "Proxima Nova, Medium",
            fontWeight: FontWeight.w500,
          ),
        ),
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late double screenSize;

  List<dynamic> products = [
    {
      'title': 'Item 1',
      'title_complement': ' Lorem ipsum dolor sit amet',
      'subtitle':
          'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum in metus turpis.',
      'price': '100',
    },
    {
      'title': 'Item 2',
      'title_complement': ' Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum',
      'subtitle': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
      'price': '1000',
    },
    {
      'title': 'Item 3',
      'title_complement':
          ' Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
      'subtitle': 'Lorem ipsum dolor sit amet.',
      'price': '10',
    }
  ];

  Widget _productImage() {
    return Padding(
      padding: const EdgeInsets.only(top: 24),
      child: Container(
        height: 205, // 保留图片固定高度以保证尺寸统一
        decoration: BoxDecoration(
          shape: BoxShape.rectangle,
          borderRadius: BorderRadius.circular(4),
        ),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Container(
              height: 44,
              decoration: BoxDecoration(borderRadius: BorderRadius.circular(2)),
              margin: const EdgeInsets.all(8),
              padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 5),
            ),
          ],
        ),
      ),
    );
  }

  Widget _price(BuildContext context, Map<String, dynamic> product) {
    return Container(
      alignment: Alignment.centerRight,
      child: Text(
        product['price'],
        style: Theme.of(context).textTheme.titleMedium?.copyWith(
              color: Colors.black,
              fontWeight: FontWeight.bold,
              fontFamily: "Proxima Nova, Bold",
            ),
      ),
    );
  }

  Widget _buyButtom(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 12),
      child: InkWell(
        onTap: () {},
        child: Container(
          height: 32, // 保留按钮固定高度
          decoration: BoxDecoration(
            color: Colors.black,
            borderRadius: BorderRadius.circular(4),
          ),
          child: Center(
            child: Text(
              "Button",
              style: Theme.of(context).textTheme.labelMedium?.copyWith(
                    color: Colors.white,
                  ),
            ),
          ),
        ),
      ),
    );
  }

  Widget _priceRow(BuildContext context, Map<String, dynamic> product) {
    return Container(
      margin: const EdgeInsets.fromLTRB(0, 16, 0, 16),
      child: Row(
        children: [
          Flexible(child: _price(context, product)),
        ],
      ),
    );
  }

  Widget _title(BuildContext context, Map<String, dynamic> product) {
    return Container(
      alignment: Alignment.topLeft,
      margin: const EdgeInsets.only(top: 12),
      child: Text(
        product['title'] + product['title_complement'],
        maxLines: 3,
        overflow: TextOverflow.ellipsis,
        style: Theme.of(context).textTheme.titleMedium?.copyWith(
              fontWeight: FontWeight.bold,
            ),
      ),
    );
  }

  Widget _subtitle(BuildContext context, Map<String, dynamic> product) {
    return Container(
      alignment: Alignment.topLeft,
      margin: const EdgeInsets.fromLTRB(0, 8, 0, 0),
      child: Card(
        elevation: 0,
        color: Colors.transparent,
        child: Text(
          product['subtitle'],
          maxLines: 3,
          overflow: TextOverflow.ellipsis,
          style: Theme.of(context).textTheme.bodyMedium?.copyWith(
                color: const Color(0xff666666),
              ),
        ),
      ),
    );
  }

  Widget body(BuildContext context, Map<String, dynamic> product) {
    return GestureDetector(
      onTap: () {},
      child: SizedBox(
        width: (screenSize - 47) / 2,
        child: IntrinsicHeight(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              _productImage(),
              _title(context, product),
              _subtitle(context, product),
              const Spacer(), // 让底部元素自动对齐到列表项底部
              _priceRow(context, product),
              _buyButtom(context),
            ],
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    screenSize = MediaQuery.of(context).size.width;

    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 20),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.start,
          mainAxisSize: MainAxisSize.min,
          children: [
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text(
                    "Items".toUpperCase(),
                    style: Theme.of(context).textTheme.titleLarge,
                  ),
                ],
              ),
            ),
            // 移除固定高度容器,让ListView自适应高度
            ListView(
              scrollDirection: Axis.horizontal,
              shrinkWrap: true, // 让ListView只占用所需高度
              children: List<Widget>.generate(
                products.length,
                (index) => Padding(
                  padding: const EdgeInsets.only(left: 10),
                  child: body(context, products[index]),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

关键说明

  • IntrinsicHeight:强制所有子元素高度与最高的子元素一致,保证列表项高度统一
  • Spacer():在文本区域和底部按钮之间填充空白,让按钮始终对齐到列表项底部,提升视觉一致性
  • 使用主题文本样式:替代固定字体大小,自动适配系统字体缩放设置
  • shrinkWrap: true:让ListView只占用子元素所需的高度,避免不必要的空白

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 02:57:05