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

Flutter中setState更新对话框不及时问题求助

问题分析与解决方案

你的核心问题是:短时间内频繁调用setState时,Flutter会合并多次重绘请求以优化性能,导致UI无法实时响应每一次状态更新,这就是日志里多次出现setState called但UI只在下载完成后才更新的原因。

下面给出两种针对性的解决方法:


方案1:限制UI更新频率(节流)

通过固定时间间隔控制更新时机,避免过度触发重绘。比如设置每100ms更新一次UI,既能保证进度流畅,又不会给性能造成负担。

class _VideoDownloadPageState extends State<VideoDownloadPage> {
  int _progress = 0;
  DateTime _lastUpdateTime = DateTime.now();
  // 最小更新间隔,可根据需求调整
  final Duration _minUpdateInterval = const Duration(milliseconds: 100);

  @override
  void initState() {
    _startDownload();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    log('Widget rebuilt');
    return Scaffold(
      appBar: AppBar(
        title: const Text('Baixando vídeo'),
      ),
      body: Center(
        child: Text('$_progress'),
      ),
    );
  }

  void _startDownload() async {
    var file = await widget.sftpServer.open(
      widget.remoteFilePath,
      mode: SftpFileOpenMode.read,
    );

    List<List<int>> byteList = [];
    var stream = file.read();
    stream.listen((Uint8List event) {
      byteList.add(event);
      final now = DateTime.now();
      // 仅当距离上次更新超过设定间隔时,才更新UI
      if (now.difference(_lastUpdateTime) >= _minUpdateInterval) {
        setState(() {
          log('setState called');
          _progress++;
        });
        _lastUpdateTime = now;
      }
    }, onDone: () {
      // 下载完成后强制更新一次,确保进度显示完整
      setState(() {
        _progress = byteList.length;
      });
      List<int> flattenedList = [];
      byteList.forEach((list) => flattenedList.addAll(list));
      var fullListUint8 = Uint8List.fromList(flattenedList);
      Navigator.pop(context, fullListUint8);
    }, onError: (error) {
      FirebaseCrashlytics.instance
          .log('Error downloading video: ${error.toString()}');
      Navigator.pop(context, null);
    });
  }
}

方案2:使用ValueNotifier + ValueListenableBuilder(更高效)

这种方式避免了整个页面重绘,仅更新需要变化的UI组件,同时也能间接控制更新频率。

class _VideoDownloadPageState extends State<VideoDownloadPage> {
  // 用ValueNotifier管理进度状态
  final ValueNotifier<int> _progressNotifier = ValueNotifier(0);
  DateTime _lastUpdateTime = DateTime.now();
  final Duration _minUpdateInterval = const Duration(milliseconds: 100);

  @override
  void initState() {
    _startDownload();
    super.initState();
  }

  @override
  void dispose() {
    // 记得释放资源,避免内存泄漏
    _progressNotifier.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    log('Widget rebuilt');
    return Scaffold(
      appBar: AppBar(
        title: const Text('Baixando vídeo'),
      ),
      body: Center(
        // 仅更新Text组件,而非整个页面
        child: ValueListenableBuilder<int>(
          valueListenable: _progressNotifier,
          builder: (context, progress, child) {
            return Text('$progress');
          },
        ),
      ),
    );
  }

  void _startDownload() async {
    var file = await widget.sftpServer.open(
      widget.remoteFilePath,
      mode: SftpFileOpenMode.read,
    );

    List<List<int>> byteList = [];
    var stream = file.read();
    stream.listen((Uint8List event) {
      byteList.add(event);
      final now = DateTime.now();
      if (now.difference(_lastUpdateTime) >= _minUpdateInterval) {
        // 更新ValueNotifier的值,触发UI更新
        _progressNotifier.value++;
        _lastUpdateTime = now;
      }
    }, onDone: () {
      _progressNotifier.value = byteList.length;
      List<int> flattenedList = [];
      byteList.forEach((list) => flattenedList.addAll(list));
      var fullListUint8 = Uint8List.fromList(flattenedList);
      Navigator.pop(context, fullListUint8);
    }, onError: (error) {
      FirebaseCrashlytics.instance
          .log('Error downloading video: ${error.toString()}');
      Navigator.pop(context, null);
    });
  }
}

额外注意事项

如果你的SFTP库回调是在后台线程执行,需要将状态更新切换到主线程,否则会导致异常:

WidgetsBinding.instance.addPostFrameCallback((_) {
  setState(() {
    _progress++;
  });
});

内容的提问来源于stack exchange,提问作者Raphael Sauer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 11:35:40