Flutter printing: ^5.11.1生成POS小票无法占满宽度及顶部留白问题
Flutter 使用
printing: ^5.11.1 生成POS小票的问题与解决方案 问题现象
- 已通过
PdfPageFormat设置自定义页面格式,但内容无法占满打印页宽度; - 选择「Fit to printable area」选项时,内容居中显示且页面顶部存在留白。
相关截图:
待解决疑问
- 如何确保小票内容占满打印页的全部宽度?
- 选择「Fit to printable area」时,如何调整布局让内容顶部对齐?
简化版代码
import 'package:pdf/pdf.dart'; import 'package:pdf/widgets.dart' as pw; import 'package:printing/printing.dart'; import 'package:get/get.dart'; class PrintTest { final PdfPageFormat pageFormat; final double fontSize; PrintTest({required this.pageFormat, required this.fontSize}); } const double defaultFontSize = 5; Future<void> printImgTest3() async { List<String> toStringList = [ "PdfPageFormat.a3", "PdfPageFormat.a4", "PdfPageFormat.a5", "PdfPageFormat.a6", "PdfPageFormat.legal", "PdfPageFormat.letter", "PdfPageFormat.roll57", "PdfPageFormat.roll80", "PdfPageFormat.undefined", "28 x 2", ]; Get.generalDialog<PrintTest>( pageBuilder: (_, __, ___) { TextEditingController widthController = TextEditingController(); TextEditingController heightController = TextEditingController(); TextEditingController marginController = TextEditingController(text: '2'); TextEditingController fontSizeController = TextEditingController(text: '$defaultFontSize'); final GlobalKey<FormState> formKey = GlobalKey<FormState>(); return Align( alignment: Alignment.center, child: Container( width: 400, margin: const EdgeInsets.all(20), padding: const EdgeInsets.all(20), child: Material( color: Colors.white, child: SingleChildScrollView( padding: const EdgeInsets.all(10), child: Column( mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 15), CustomTextFormFieldWithLabel( labelText: 'Font size', controller: fontSizeController, hintText: 'Default Value: $defaultFontSize', inputFormatterList: acceptDecimalValues(), ), const SizedBox(height: 10), Form( key: formKey, child: Column( children: [ CustomTextFormFieldWithLabel( labelText: 'Width', controller: widthController, inputFormatterList: acceptDecimalValues(), ), CustomTextFormFieldWithLabel( labelText: 'Height', controller: heightController, inputFormatterList: acceptDecimalValues(), ), CustomTextFormFieldWithLabel( labelText: 'Margin', controller: marginController, inputFormatterList: acceptDecimalValues(), ), ElevatedButton( onPressed: () { if (formKey.currentState!.validate()) { Get.back( result: PrintTest( pageFormat: PdfPageFormat( double.parse(widthController.text) * PdfPageFormat.mm, double.parse(heightController.text) * PdfPageFormat.mm, marginAll: double.parse(marginController.text) * PdfPageFormat.mm, ), fontSize: double.tryParse( fontSizeController.text) ?? defaultFontSize, ), ); } }, child: const Text('Select Custom Format'), ) ], ), ), const SizedBox(height: 10), Wrap( alignment: WrapAlignment.center, children: <PdfPageFormat>[ PdfPageFormat.a3, PdfPageFormat.a4, PdfPageFormat.a5, PdfPageFormat.a6, PdfPageFormat.legal, PdfPageFormat.letter, PdfPageFormat.roll57, PdfPageFormat.roll80, PdfPageFormat.undefined, const PdfPageFormat( 28 * PdfPageFormat.mm, double.infinity, marginAll: 2 * PdfPageFormat.mm, ), ] .mapIndexed( (e, i) => Padding( padding: const EdgeInsets.all(5), child: InkWell( onTap: () { Get.back( result: PrintTest( pageFormat: e, fontSize: double.tryParse( fontSizeController.text) ?? defaultFontSize, ), ); }, child: Padding( padding: const EdgeInsets.all(8.0), child: Text(toStringList[i]), ), ), ), ) .toList(), ), ], ), ), ), ), ); }, barrierDismissible: true, barrierLabel: 'test-print-3', ).then((PrintTest? value) async { if (value == null) { return; } final doc = pw.Document(); doc.addPage( pw.Page( pageFormat: value.pageFormat, orientation: pw.PageOrientation.portrait, build: (pw.Context context) { return pw.Column( crossAxisAlignment: pw.CrossAxisAlignment.center, mainAxisSize: pw.MainAxisSize.min, children: [ pw.FittedBox( alignment: pw.Alignment.center, fit: pw.BoxFit.scaleDown, child: pw.Text( 'Business Name', style: pw.TextStyle( fontWeight: pw.FontWeight.bold, fontSize: value.fontSize + 1, ), ), ), pw.SizedBox(height: 1), pw.FittedBox( alignment: pw.Alignment.center, fit: pw.BoxFit.scaleDown, child: pw.Text( 'Transaction Id: 32', style: pw.TextStyle( fontWeight: pw.FontWeight.bold, fontSize: value.fontSize, ), ), ), pw.FittedBox( alignment: pw.Alignment.center, fit: pw.BoxFit.scaleDown, child: pw.Text( 'Dec 2, 2034 1:08 AM', style: pw.TextStyle( fontWeight: pw.FontWeight.bold, fontSize: value.fontSize, ), ), ), pw.Row( children: [ pw.Expanded( flex: 3, child: pw.SizedBox( height: 10, child: pw.FittedBox( alignment: pw.Alignment.centerLeft, fit: pw.BoxFit.scaleDown, child: pw.Text( 'Product', style: pw.TextStyle( fontSize: value.fontSize, fontWeight: pw.FontWeight.bold, ), ), ), ), ), pw.Expanded( flex: 1, child: pw.SizedBox( height: 8, child: pw.FittedBox( alignment: pw.Alignment.center, fit: pw.BoxFit.scaleDown, child: pw.Text( 'Qnty.', style: pw.TextStyle( fontWeight: pw.FontWeight.bold, fontSize: value.fontSize, ), ), ), ), ), pw.Expanded( flex: 1, child: pw.SizedBox( height: 8, child: pw.FittedBox( alignment: pw.Alignment.center, fit: pw.BoxFit.scaleDown, child: pw.Text( 'Price', style: pw.TextStyle( fontWeight: pw.FontWeight.bold, fontSize: value.fontSize, ), ), ), ), ), ], ), ...[ 1, 2, 3, 4, 5, ].map( (e) => pw.Row( children: [ pw.Expanded( flex: 3, child: pw.Text( 'Product 1 jshkdh jhkash kafhjah fkahka ', style: pw.TextStyle( fontSize: value.fontSize - 1, ), ), ), pw.Expanded( flex: 1, child: pw.SizedBox( height: 7, child: pw.FittedBox( alignment: pw.Alignment.center, fit: pw.BoxFit.scaleDown, child: pw.Text( '2', style: pw.TextStyle( fontSize: value.fontSize - 1, ), ), ), ), ), pw.Expanded( flex: 1, child: pw.SizedBox( height: 7, child: pw.FittedBox( alignment: pw.Alignment.center, fit: pw.BoxFit.scaleDown, child: pw.Text( 'Rs.34534', style: pw.TextStyle( fontSize: value.fontSize - 1, ), ), ), ), ), ], ), ), ], ); }, ), ); await Printing.layoutPdf( format: value.pageFormat, onLayout: (PdfPageFormat ppf) => doc.save(), ); }); }
解决方案
1. 让小票内容占满打印页宽度
代码中大量使用pw.FittedBox并设置fit: pw.BoxFit.scaleDown,会强制文本缩小适应容器,同时根Column的crossAxisAlignment: pw.CrossAxisAlignment.center会限制子元素宽度。修改方式:
- 移除不必要的
pw.FittedBox,用pw.Text的textAlign属性控制对齐,配合Expanded占据横向空间; - 将
Column的crossAxisAlignment改为pw.CrossAxisAlignment.stretch,让子元素撑满可用宽度; - 过长文本用
softWrap: true和overflow: pw.TextOverflow.ellipsis处理,避免内容被缩小; - 检查
PdfPageFormat的边距设置,避免过大边距压缩内容宽度。
核心布局修改示例:
return pw.Column( crossAxisAlignment: pw.CrossAxisAlignment.stretch, mainAxisSize: pw.MainAxisSize.min, children: [ pw.Text( 'Business Name', textAlign: pw.TextAlign.center, style: pw.TextStyle( fontWeight: pw.FontWeight.bold, fontSize: value.fontSize + 1, ), ), pw.SizedBox(height: 1), pw.Text( 'Transaction Id: 32', textAlign: pw.TextAlign.center, style: pw.TextStyle( fontWeight: pw.FontWeight.bold, fontSize: value.fontSize, ), ), pw.Row( children: [ pw.Expanded( flex: 3, child: pw.Text( 'Product', textAlign: pw.TextAlign.left, style: pw.TextStyle( fontSize: value.fontSize, fontWeight: pw.FontWeight.bold, ), ), ), pw.Expanded( flex: 1, child: pw.Text( 'Qnty.', textAlign: pw.TextAlign.center, style: pw.TextStyle( fontWeight: pw.FontWeight.bold, fontSize: value.fontSize, ), ), ), pw.Expanded( flex: 1, child: pw.Text( 'Price', textAlign: pw.TextAlign.center, style: pw.TextStyle( fontWeight: pw.FontWeight.bold, fontSize: value.fontSize, ), ), ), ], ), // 商品列表部分移除SizedBox和FittedBox,直接用Expanded包裹Text ...[1,2,3,4,5].map((e) => pw.Row( children: [ pw.Expanded( flex:3, child: pw.Text( 'Product 1 jshkdh jhkash kafhjah fkahka ', style: pw.TextStyle(fontSize: value.fontSize-1), softWrap: true, ), ), pw.Expanded( flex:1, child: pw.Text( '2', textAlign: pw.TextAlign.center, style: pw.TextStyle(fontSize: value.fontSize-1), ), ), pw.Expanded( flex:1, child: pw.Text( 'Rs.34534', textAlign: pw.TextAlign.center, style: pw.TextStyle(fontSize: value.fontSize-1), ), ), ], )) ], );
2. 「Fit to printable area」时顶部对齐
该选项会让打印机自动缩放内容并居中,导致顶部留白。解决方式:
- 用
pw.Align包裹根布局,设置alignment: pw.Alignment.topCenter,强制内容从顶部开始; - 确保
Column的mainAxisAlignment为pw.MainAxisAlignment.start(默认值,若有额外padding需移除); - 检查
PdfPageFormat的marginTop设置,若不需要顶部边距可设为0; - 可尝试在
Printing.layoutPdf中添加usePrinterSettings: false,禁用打印机的自动调整(需根据打印机型号适配)。
修改后的pw.Page示例:
pw.Page( pageFormat: value.pageFormat, orientation: pw.PageOrientation.portrait, build: (pw.Context context) { return pw.Align( alignment: pw.Alignment.topCenter, child: pw.Column( crossAxisAlignment: pw.CrossAxisAlignment.stretch, mainAxisSize: pw.MainAxisSize.min, children: [ // 内容组件 ], ), ); }, ),
内容的提问来源于stack exchange,提问作者MUHAMMAD UMAIR BIN SAJID
相关产品推荐
相关产品推荐

