Flutter应用BoxConstraints无限宽度错误求助
Flutter约束错误:无限宽度问题解决
错误信息
FlutterError (BoxConstraints forces an infinite width. These invalid constraints were provided to RenderParagraph's layout() function by the following function, which probably computed the invalid constraints in question: _RenderListTile._layoutBox (package:flutter/src/material/list_tile.dart:1274:9) The offending constraints were: BoxConstraints(w=Infinity, 0.0<=h<=Infinity))
问题场景
尝试在Column中嵌套SingleChildScrollView实现血液捐赠请求卡片的横向滚动,期望卡片能在屏幕宽度内平滑滚动展示所有请求,但出现上述无限宽度约束错误。
原代码片段
import 'package:blood_token_app/models/services_model/blood_request_model.dart'; import 'package:blood_token_app/screens/main_screens/details_screen.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_svg/svg.dart'; class MainScreen extends StatefulWidget { const MainScreen({Key? key}) : super(key: key); @override State<MainScreen> createState() => _MainScreenState(); } class _MainScreenState extends State<MainScreen> { @override Widget build(BuildContext context) { final Size size = MediaQuery.of(context).size; return Scaffold( appBar: AppBar( title: Text('Blood Token'), ), body: Container( height: size.height, width: size.width, child: SingleChildScrollView( child: Column( children: [ SizedBox( child: Padding( padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10), child: Center( child: Card( color: Theme.of(context).scaffoldBackgroundColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ SvgPicture.asset( "assets/logos/032-blood_bag.svg", height: size.height * 0.18, ), Text( "Donate Blood\n Save Lives", textAlign: TextAlign.center, style: TextStyle( fontSize: size.height * 0.030, color: Colors.black, ), ), ], ), ), ), ), ), Column( children: [ ListTile( title: Text( 'Current Requests', style: TextStyle(fontSize: 20.0), ), trailing: Text('See all'), ), Expanded( child: StreamBuilder<QuerySnapshot>( stream: FirebaseFirestore.instance .collection('blood_requests') .snapshots(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return AppUtils.customProgressIndicator(); } if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } final List<BloodRequestModel> bloodRequests = snapshot .data!.docs .map((doc) => BloodRequestModel.fromJson( doc.data() as Map<String, dynamic>)) .toList(); return Container( width: MediaQuery.of(context) .size .width, // Ensure finite width child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: bloodRequests.map((value) { return Padding( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 10), child: Container( width: 300, // Set a finite width as per your design decoration: BoxDecoration( shape: BoxShape.rectangle, boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.5), spreadRadius: 5, blurRadius: 7, offset: Offset(0, 3), ), ], color: Theme.of(context) .scaffoldBackgroundColor, borderRadius: BorderRadius.circular(20.0), border: Border.all( color: Colors.grey.shade100, width: 0.0, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ListTile( title: Text( "Requester Name", textAlign: TextAlign.left, overflow: TextOverflow.ellipsis, ), subtitle: Text( "${value.requesterName}", textAlign: TextAlign.left, overflow: TextOverflow.ellipsis, ), trailing: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text("Urgency Level"), Text( "${value.urgencyLevel}", textAlign: TextAlign.left, overflow: TextOverflow.ellipsis, ), ], ), ), ListTile( title: Text("Location"), subtitle: Text("${value.customLocation}"), trailing: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text("Blood Type"), Text("${value.bloodType}"), ], ), ), ListTile( leading: Icon(Icons.watch_outlined), title: Text( "${value.formattedTimestamp()}"), ), MaterialButton( shape: RoundedRectangleBorder( borderRadius: BorderRadius.only( bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20), ), ), height: size.height * 0.060, minWidth: size.width, color: AppUtils.redColor, onPressed: () { if (mounted) { Navigator.push(context, MaterialPageRoute( builder: (_) { return DetailsScreen( requesterName: value.requesterName, bloodType: value.bloodType, quantityNeeded: value.quantityNeeded, urgencyLevel: value.urgencyLevel, location: value.location, contactNumber: value.contactNumber, patientName: value.patientName, date: value .formattedTimestamp(), customLocation: value.customLocation, // Convert latitude string to double latitude: double.parse(value .location! .split(', ')[0]), // Convert longitude string to double longitude: double.parse( value.location! .split(', ')[1])); })); } }, child: Text( "Details", style: TextStyle( color: AppUtils.whiteColor, ), ), ), ], ), ), ); }).toList(), ), ), ); }, ), ), ], ), ], ), ), ), ); } }
问题根源
- Expanded在无限高度容器中失效:最外层
SingleChildScrollView允许垂直无限滚动,内部Column高度无限制,导致嵌套的Expanded无法获取有效高度约束,进而引发子组件宽度约束异常。 - 按钮宽度设置错误:卡片内的
MaterialButton使用minWidth: size.width,强制按钮宽度等于屏幕宽度,但卡片本身仅300宽度,打破布局约束。
修复方案
步骤1:调整布局结构,给请求卡片区域设置固定高度
将包含StreamBuilder的嵌套Column包裹在固定高度的Container中,确保Expanded能获取有效高度约束。
步骤2:修正按钮宽度
将MaterialButton的minWidth改为double.infinity,使其自适应卡片宽度。
步骤3:移除不必要的容器约束
删除最外层Container的height和width设置,Scaffold的body默认填充屏幕。
修复后的代码
import 'package:blood_token_app/models/services_model/blood_request_model.dart'; import 'package:blood_token_app/screens/main_screens/details_screen.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_svg/svg.dart'; class MainScreen extends StatefulWidget { const MainScreen({Key? key}) : super(key: key); @override State<MainScreen> createState() => _MainScreenState(); } class _MainScreenState extends State<MainScreen> { @override Widget build(BuildContext context) { final Size size = MediaQuery.of(context).size; return Scaffold( appBar: AppBar( title: Text('Blood Token'), ), body: SingleChildScrollView( child: Column( children: [ SizedBox( child: Padding( padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10), child: Center( child: Card( color: Theme.of(context).scaffoldBackgroundColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ SvgPicture.asset( "assets/logos/032-blood_bag.svg", height: size.height * 0.18, ), Text( "Donate Blood\n Save Lives", textAlign: TextAlign.center, style: TextStyle( fontSize: size.height * 0.030, color: Colors.black, ), ), ], ), ), ), ), ), // 给请求卡片区域设置固定高度 Container( height: size.height * 0.6, child: Column( children: [ ListTile( title: Text( 'Current Requests', style: TextStyle(fontSize: 20.0), ), trailing: Text('See all'), ), Expanded( child: StreamBuilder<QuerySnapshot>( stream: FirebaseFirestore.instance .collection('blood_requests') .snapshots(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return AppUtils.customProgressIndicator(); } if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } final List<BloodRequestModel> bloodRequests = snapshot .data!.docs .map((doc) => BloodRequestModel.fromJson( doc.data() as Map<String, dynamic>)) .toList(); return SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: bloodRequests.map((value) { return Padding( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 10), child: Container( width: 300, decoration: BoxDecoration( shape: BoxShape.rectangle, boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.5), spreadRadius: 5, blurRadius: 7, offset: Offset(0, 3), ), ], color: Theme.of(context) .scaffoldBackgroundColor, borderRadius: BorderRadius.circular(20.0), border: Border.all( color: Colors.grey.shade100, width: 0.0, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ListTile( title: Text( "Requester Name", textAlign: TextAlign.left, overflow: TextOverflow.ellipsis, ), subtitle: Text( "${value.requesterName}", textAlign: TextAlign.left, overflow: TextOverflow.ellipsis, ), trailing: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text("Urgency Level"), Text( "${value.urgencyLevel}", textAlign: TextAlign.left, overflow: TextOverflow.ellipsis, ), ], ), ), ListTile( title: Text("Location"), subtitle: Text("${value.customLocation}"), trailing: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text("Blood Type"), Text("${value.bloodType}"), ], ), ), ListTile( leading: Icon(Icons.watch_outlined), title: Text( "${value.formattedTimestamp()}"), ), MaterialButton( shape: RoundedRectangleBorder( borderRadius: BorderRadius.only( bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20), ), ), height: size.height * 0.060, minWidth: double.infinity, // 自适应卡片宽度 color: AppUtils.redColor, onPressed: () { if (mounted) { Navigator.push(context, MaterialPageRoute( builder: (_) { return DetailsScreen( requesterName: value.requesterName, bloodType: value.bloodType, quantityNeeded: value.quantityNeeded, urgencyLevel: value.urgencyLevel, location: value.location, contactNumber: value.contactNumber, patientName: value.patientName, date: value .formattedTimestamp(), customLocation: value.customLocation, latitude: double.parse(value .location! .split(', ')[0]), longitude: double.parse( value.location! .split(', ')[1])); })); } }, child: Text( "Details", style: TextStyle( color: AppUtils.whiteColor, ), ), ), ], ), ), ); }).toList(), ), ); }, ), ), ], ), ), ], ), ), ); } }
内容的提问来源于stack exchange,提问作者Muhammad Ali
相关产品推荐
相关产品推荐

