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

Flutter中BottomNavigationBar视图间如何传递值?

Flutter Tab间传递扫码值的解决方案

你当前的核心问题是用Navigator.push打开了新的SecondView实例,而非更新Tab栏中已存在的那个。另外,Flutter不推荐全局变量(类似PHP的global $var)是因为全局状态不可控,大型应用中维护成本极高,组件化的状态管理能让数据流更清晰,保证UI与状态同步可靠。

以下是针对你的需求的具体解决方案:

核心修改思路

  1. 状态提升:将扫码结果的状态放到Tab的父组件HomeView中,让所有Tab共享该状态
  2. 回调传值:给QRViewExample添加回调函数,扫描完成后将结果传递给父组件
  3. 动态构建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();
  }
}

关键修改说明

  1. 状态集中管理:将扫描结果_scannedResult放在HomeView的State中,作为Tab页面的共享数据源
  2. 回调传递结果:QRViewExample通过onScanCompleted回调将扫描结果传递给父组件,避免全局变量和不必要的页面跳转
  3. 动态构建Tab:每次build时生成最新的SecondView实例,确保它能获取到最新的扫描结果
  4. 自动切换Tab:扫描完成后自动切换到第二个Tab,让用户直接查看结果

内容的提问来源于stack exchange,提问作者Nathan Bernard

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 13:45:46