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), ), ), ), ], ), ); } }
关键修复说明:
- 文本省略号生效:所有需要截断的
Text组件添加了maxLines: 1,这是TextOverflow.ellipsis生效的必要条件,确保文本超出容器宽度时显示省略号。 - 消除RenderFlex错误:移除硬编码的固定宽度,改用
Expanded配合flex值按比例分配列宽;名称列的文本容器用Flexible包裹,约束文本在可用空间内显示。 - 表头固定:表头位于列表上方的
Column中,垂直滚动时始终保持在顶部。 - 同步水平滚动:使用共享的
ScrollController让表头和列表行的水平滚动保持同步,解决宽屏内容的横向溢出问题。 - 动态宽度适配:通过flex值按比例分配各列空间,自动适配不同屏幕尺寸。
内容的提问来源于stack exchange,提问作者Shaikh Faizan
相关产品推荐
相关产品推荐

