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

Flutter中ListView.Builder的Text宽度管理及滑动列表、固定表头实现

解决方案

以下是修复后的完整代码,解决了文本溢出、RenderFlex错误、表头固定和动态宽度适配的问题:

// ignore_for_file: prefer_const_literals_to_create_immutables, prefer_const_constructors, unused_field, unused_element, unused_import
import 'dart:math';

import 'package:cryptocornars/common/widget/common_widget.dart';
import 'package:cryptocornars/constants/constants.dart';
import 'package:cryptocornars/features/market/screen/view_model/coins_view_model.dart';
import 'package:cryptocornars/model/market/coins_model.dart';
import 'package:cryptocornars/provider/filter_provider.dart';
import 'package:cryptocornars/provider/token_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:provider/provider.dart';

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

  @override
  State<CoinsPage> createState() => _CoinsPageState();
}

class _CoinsPageState extends State<CoinsPage> {
  final List _peopleData = List.generate(100, (index) {
    return {"name": "Person #$index", "age": Random().nextInt(90) + 10};
  });
  late CoinsViewModel viewModel = CoinsViewModel();
  List<Coins>? item;
  final ScrollController _horizontalScrollController = ScrollController();

  Widget _buildItemRow(int index) {
    final items = item![index];
    final data = items.data![index];
    final regularStyle = TextStyle(color: Colors.black, fontSize: 15);
    final subtextStyle = TextStyle(color: Colors.black45);

    return Container(
      margin: EdgeInsets.all(10),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          SizedBox(width: 16),
          Expanded(
            flex: 1,
            child: Text(
              "#$index",
              style: regularStyle,
            ),
          ),
          SizedBox(width: 16),
          Expanded(
            flex: 3,
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                Image.network(
                  data.coinLogo!,
                  width: 25,
                  height: 40,
                ),
                SizedBox(width: 5),
                Flexible(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        data.coinName!,
                        style: regularStyle,
                        overflow: TextOverflow.ellipsis,
                        maxLines: 1,
                      ),
                      Text(
                        "324.44Bn",
                        style: subtextStyle,
                        overflow: TextOverflow.ellipsis,
                        maxLines: 1,
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
          SizedBox(width: 16),
          Expanded(
            flex: 2,
            child: Center(
              child: Text(
                data.currentPrice.toString(),
                style: regularStyle,
                overflow: TextOverflow.ellipsis,
                maxLines: 1,
              ),
            ),
          ),
          SizedBox(width: 16),
          Expanded(
            flex: 2,
            child: Text(
              data.priceChange24hPercentage!.toString(),
              style: regularStyle,
              overflow: TextOverflow.ellipsis,
              maxLines: 1,
            ),
          ),
          SizedBox(width: 16),
        ],
      ),
    );
  }

  @override
  void initState() {
    super.initState();
    viewModel.loadCoinData("marketCap", "desc", "100", "1");
  }

  @override
  Widget build(BuildContext context) {
    var token = Provider.of<AccessToken>(context, listen: false).token;
    final boldHeaderStyle = TextStyle(
      color: Colors.black,
      fontFamily: 'Roboto-Bold',
      fontWeight: FontWeight.bold,
    );

    return ChangeNotifierProvider<CoinsViewModel>(
        create: (BuildContext context) => viewModel,
        child: Consumer<CoinsViewModel>(
          builder: (context, viewModel, _) {
            debugPrint("length ${viewModel.coinsModel.length}");
            item = viewModel.coinsModel;

            return Scaffold(
              body: Column(children: [
                // 固定表头
                Container(
                  padding: const EdgeInsets.symmetric(vertical: 10),
                  color: Colors.white,
                  child: SingleChildScrollView(
                    controller: _horizontalScrollController,
                    scrollDirection: Axis.horizontal,
                    child: Row(
                      crossAxisAlignment: CrossAxisAlignment.center,
                      children: [
                        SizedBox(width: 16),
                        Expanded(
                          flex: 1,
                          child: Text('#', style: boldHeaderStyle),
                        ),
                        SizedBox(width: 16),
                        Expanded(
                          flex: 3,
                          child: Text('Market Cap', style: boldHeaderStyle),
                        ),
                        SizedBox(width: 16),
                        Expanded(
                          flex: 2,
                          child: Text('Price(USD)', style: boldHeaderStyle),
                        ),
                        SizedBox(width: 16),
                        Expanded(
                          flex: 2,
                          child: Text('24h%', style: boldHeaderStyle),
                        ),
                        SizedBox(width: 16),
                      ],
                    ),
                  ),
                ),
                // 可滚动列表
                Expanded(
                  child: viewModel.getCoinDataResponse.isLoading &&
                          viewModel.coinsModel.isEmpty
                      ? const Center(
                          child: CircularProgressIndicator(),
                        )
                      : viewModel.getCoinDataResponse.isError
                          ? Center(
                              child: emptyWidget(
                                  viewModel.getCoinDataResponse.message ??
                                      someThingWentWrong,
                                  '',
                                  'assets/images/blank_report.png'),
                            )
                          : SingleChildScrollView(
                              controller: _horizontalScrollController,
                              scrollDirection: Axis.horizontal,
                              child: ListView.builder(
                                physics: const AlwaysScrollableScrollPhysics(),
                                shrinkWrap: true,
                                itemCount: viewModel.coinsModel.length,
                                itemBuilder: (ctx, index) {
                                  return Slidable(
                                    endActionPane: ActionPane(
                                      motion: const ScrollMotion(),
                                      children: [
                                        SlidableAction(
                                          onPressed: (ctx) {
                                            doNothing(ctx, "Alerts");
                                          },
                                          backgroundColor: Color(0xff1C2B5E),
                                          foregroundColor: Colors.white,
                                          icon: Icons.notifications,
                                          label: 'Alerts',
                                          padding: EdgeInsets.all(0),
                                          borderRadius: BorderRadius.only(
                                              topLeft: Radius.circular(8),
                                              bottomLeft: Radius.circular(8)),
                                        ),
                                        SlidableAction(
                                          onPressed: (ctx) {
                                            doNothing(ctx, "Watchlist");
                                          },
                                          backgroundColor:
                                              const Color(0xFF0096FF),
                                          foregroundColor: Colors.white,
                                          icon: Icons.star,
                                          label: 'Watchlist',
                                          borderRadius: BorderRadius.only(
                                              topRight: Radius.circular(8),
                                              bottomRight: Radius.circular(8)),
                                          padding: EdgeInsets.all(0),
                                          spacing: 5,
                                        ),
                                      ],
                                    ),
                                    child: _buildItemRow(index),
                                  );
                                },
                              ),
                            ),
                )
              ]),
            );
          },
        ));
  }

  void doNothing(BuildContext context, String text) {
    showDialog(
      context: context,
      builder: (ctx) => AlertDialog(
        title: const Text("Alert Dialog Box"),
        content: Text("You have raised a Alert Dialog Box, $text "),
        actions: <Widget>[
          TextButton(
            onPressed: () {
              Navigator.of(ctx).pop();
            },
            child: Container(
              color: Colors.black45,
              padding: const EdgeInsets.all(14),
              child: const Text(
                "okay",
                style: TextStyle(color: Colors.white),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

关键修复说明:

  1. 文本省略号生效:所有需要截断的Text组件添加了maxLines: 1,这是TextOverflow.ellipsis生效的必要条件,确保文本超出容器宽度时显示省略号。
  2. 消除RenderFlex错误:移除硬编码的固定宽度,改用Expanded配合flex值按比例分配列宽;名称列的文本容器用Flexible包裹,约束文本在可用空间内显示。
  3. 表头固定:表头位于列表上方的Column中,垂直滚动时始终保持在顶部。
  4. 同步水平滚动:使用共享的ScrollController让表头和列表行的水平滚动保持同步,解决宽屏内容的横向溢出问题。
  5. 动态宽度适配:通过flex值按比例分配各列空间,自动适配不同屏幕尺寸。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 22:49:57