Flutter中BottomNavigationBar视图间如何传递值?
Flutter Tab间传递扫码值的解决方案
你当前的核心问题是用Navigator.push打开了新的SecondView实例,而非更新Tab栏中已存在的那个。另外,Flutter不推荐全局变量(类似PHP的global $var)是因为全局状态不可控,大型应用中维护成本极高,组件化的状态管理能让数据流更清晰,保证UI与状态同步可靠。
以下是针对你的需求的具体解决方案:
核心修改思路
- 状态提升:将扫码结果的状态放到Tab的父组件
HomeView中,让所有Tab共享该状态 - 回调传值:给
QRViewExample添加回调函数,扫描完成后将结果传递给父组件 - 动态构建Tab页面:每次构建UI时生成最新的
SecondView实例,确保它能获取到最新的扫描结果
修改后的完整代码
import 'dart:core'; import 'dart:developer'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:qr_code_scanner/qr_code_scanner.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter App', theme: ThemeData( visualDensity: VisualDensity.adaptivePlatformDensity, ), home: HomeView(), ); } } class HomeView extends StatefulWidget { @override _HomeViewState createState() => _HomeViewState(); } class _HomeViewState extends State<HomeView> { int _currentIndex = 0; String? _scannedResult = '暂无扫描结果'; @override Widget build(BuildContext context) { // 动态创建Tab页面,确保SecondView拿到最新的扫描结果 final tabs = [ QRViewExample( onScanCompleted: (result) { setState(() { _scannedResult = result; _currentIndex = 1; // 扫描完成自动切换到第二个Tab }); }, ), SecondView(res: _scannedResult), ]; return Scaffold( appBar: AppBar( toolbarHeight: 40.0, elevation: 0, centerTitle: true, title: Text('Flutter App'), ), body: tabs[_currentIndex], bottomNavigationBar: BottomNavigationBar( backgroundColor: Colors.red, currentIndex: _currentIndex, type: BottomNavigationBarType.fixed, selectedItemColor: Colors.white, unselectedItemColor: Colors.white.withOpacity(0.5), items: [ BottomNavigationBarItem( icon: Icon(Icons.qr_code), label: 'Scan', ), BottomNavigationBarItem( icon: Icon(Icons.list), label: 'List', ), ], onTap: (index) { setState(() { _currentIndex = index; }); }, ), ); } } // SECOND TAB WIDGET (custom) class SecondView extends StatelessWidget { const SecondView({Key? key, required this.res}) : super(key: key); final String? res; @override Widget build(BuildContext context) { return Container( child: Center( child: Text(res!), ), ); } } // FIRST TAB WIDGET (qrcode) class QRViewExample extends StatefulWidget { final Function(String) onScanCompleted; const QRViewExample({Key? key, required this.onScanCompleted}) : super(key: key); @override State<StatefulWidget> createState() => _QRViewExampleState(); } class _QRViewExampleState extends State<QRViewExample> { Barcode? result; QRViewController? controller; final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); @override void reassemble() { super.reassemble(); if (Platform.isAndroid) { controller!.pauseCamera(); } controller!.resumeCamera(); } @override Widget build(BuildContext context) { return Scaffold( body: Container( height: 500, child: Padding( padding: EdgeInsets.all(8.0), child: Column( children: <Widget>[ Expanded(flex: 4, child: _buildQrView(context)), Expanded( flex: 1, child: FittedBox( fit: BoxFit.contain, child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ if (result != null) Text( 'Barcode Type: ${describeEnum(result!.format)} Data: ${result!.code}') else const Text('Scan a code'), Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ Container( margin: const EdgeInsets.all(8), child: ElevatedButton( onPressed: () async { await controller?.toggleFlash(); setState(() {}); }, child: FutureBuilder( future: controller?.getFlashStatus(), builder: (context, snapshot) { return Text('Flash: ${snapshot.data}'); }, )), ), Container( margin: const EdgeInsets.all(8), child: ElevatedButton( onPressed: () async { await controller?.flipCamera(); setState(() {}); }, child: FutureBuilder( future: controller?.getCameraInfo(), builder: (context, snapshot) { if (snapshot.data != null) { return Text( 'Camera facing ${describeEnum(snapshot.data!)}'); } else { return const Text('loading'); } }, )), ) ], ), Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ Container( margin: const EdgeInsets.all(8), child: ElevatedButton( onPressed: () async { await controller?.pauseCamera(); }, child: const Text('pause', style: TextStyle(fontSize: 20)), ), ), Container( margin: const EdgeInsets.all(8), child: ElevatedButton( onPressed: () async { await controller?.resumeCamera(); }, child: const Text('resume', style: TextStyle(fontSize: 20)), ), ) ], ), ], ), ), ) ], ), ), ), ); } Widget _buildQrView(BuildContext context) { var scanArea = (MediaQuery.of(context).size.width < 400 || MediaQuery.of(context).size.height < 400) ? 150.0 : 300.0; return QRView( key: qrKey, onQRViewCreated: _onQRViewCreated, overlay: QrScannerOverlayShape( borderColor: Colors.cyanAccent, borderRadius: 10, borderLength: 30, borderWidth: 10, cutOutSize: scanArea), onPermissionSet: (ctrl, p) => _onPermissionSet(context, ctrl, p), ); } void _onQRViewCreated(QRViewController controller) { setState(() { this.controller = controller; }); controller.scannedDataStream.listen((scanData) { controller.pauseCamera(); setState(() { result = scanData; }); // 调用回调传递扫描结果,不再打开新页面 widget.onScanCompleted(result!.code!); // 2秒后恢复相机,支持再次扫描 Future.delayed(Duration(seconds: 2), () { controller.resumeCamera(); }); }); } void _onPermissionSet(BuildContext context, QRViewController ctrl, bool p) { log('${DateTime.now().toIso8601String()}_onPermissionSet $p'); if (!p) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('无相机权限')), ); } } @override void dispose() { controller?.dispose(); super.dispose(); } }
关键修改说明
- 状态集中管理:将扫描结果
_scannedResult放在HomeView的State中,作为Tab页面的共享数据源 - 回调传递结果:
QRViewExample通过onScanCompleted回调将扫描结果传递给父组件,避免全局变量和不必要的页面跳转 - 动态构建Tab:每次build时生成最新的
SecondView实例,确保它能获取到最新的扫描结果 - 自动切换Tab:扫描完成后自动切换到第二个Tab,让用户直接查看结果
内容的提问来源于stack exchange,提问作者Nathan Bernard
相关产品推荐
相关产品推荐

