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

Flutter中FutureBuilder处理耗时Voronoi图生成时UI卡顿、加载指示器不及时显示的问题

Flutter中FutureBuilder处理耗时Voronoi图生成时UI卡顿、加载指示器不及时显示的问题

嗨,我看了你的代码,问题的核心是你把耗时的Voronoi图生成逻辑放在了主线程里,导致UI完全被阻塞,FutureBuilder根本没机会显示加载指示器。咱们一步步来解决这个问题:

问题根源分析

你写的createDiagram()是一个同步的耗时函数,虽然你把它包在了drawDiagram()的Future.value()里,但Future.value()的特性是先同步执行传入的代码,再返回Future。也就是说,点击按钮后,主线程会立刻开始执行createDiagram()的所有计算,直到完成才会继续处理UI更新——这就导致你的APP卡顿,而FutureBuilder的ConnectionState.waiting状态根本没机会触发,因为等计算完,Future已经直接进入完成状态了。

解决方案:把耗时计算移到后台Isolate

Flutter提供了compute函数,可以轻松把耗时任务放到后台隔离线程(Isolate)执行,这样主线程就能保持响应,加载指示器也能正常显示。

步骤1:重构createDiagram为静态/顶层函数,并打包参数

因为Isolate不能访问类的实例成员,所以我们需要把createDiagram改成静态函数,同时把需要的参数(width、height、station数量)打包成一个简单的数据类:

// 定义参数类,传递计算所需的配置
class DiagramParams {
  final int width;
  final int height;
  final int stationCount;

  DiagramParams(this.width, this.height, this.stationCount);
}

// 把createDiagram改成静态函数,接收参数类
static Uint8List createDiagram(DiagramParams params) {
  int width = params.width;
  int height = params.height;
  int basedStation = params.stationCount;
  
  // 以下是你原来的生成逻辑,完全不变
  img.Image diagram = img.Image(width: width, height: height);
  print('image created');
  List<DrawPixels> baseStationList = generatePixels(basedStation, width, height);
  for (var pixel in baseStationList) {
    pixel.color = generateRandomColor();
  }
  for (int i = 0; i < width; i++) {
    for (int j = 0; j < height; j++) {
      double minDistance = double.infinity;
      int minIndex = -1;
      for (int k = 0; k < basedStation; k++) {
        double distance = calculateDistance(i, j, baseStationList[k].x, baseStationList[k].y);
        if (distance < minDistance) {
          minDistance = distance;
          minIndex = k;
        }
      }
      img.drawPixel(diagram, i, j, baseStationList[minIndex].color);
    }
  }

  for (var pixel in baseStationList) {
    img.fillCircle(diagram, x: pixel.x, y: pixel.y, radius: 3, color: img.ColorFloat16.rgb(255, 255, 255));
  }
  return img.encodePng(diagram); 
}

步骤2:修改drawDiagram方法,用compute执行后台计算

把原来的Future.value()替换成compute,让任务在后台线程执行:

Future<Uint8List> drawDiagram() async {
  try {
    int width = int.tryParse(textEditingControllerWidth.text) ?? 0;
    int height = int.tryParse(textEditingControllerHeight.text) ?? 0;
    int basedStation = int.tryParse(textEditingControllerStations.text) ?? 0;
    
    // 使用compute将耗时任务丢到后台线程
    final data = await compute(createDiagram, DiagramParams(width, height, basedStation));
    return data;
  } catch (e) {
    throw Exception('生成失败:$e');
  }
}

注意:这里把参数解析移到了drawDiagram里,原来在createDiagram中调用setState是错误的——后台线程不能更新UI,而且参数解析不需要触发UI更新,直接在这里处理就好。

步骤3:调整FutureBuilder的逻辑(更严谨)

原来的判断顺序有点问题,应该先判断连接状态,再处理数据和错误:

FutureBuilder(
  future: diagramImage,
  builder: (context, AsyncSnapshot<Uint8List> snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      print('正在生成...');
      return const CircularProgressIndicator();
    }
    if (snapshot.hasError) {
      return Text('生成出错:${snapshot.error}');
    }
    if (snapshot.hasData) {
      return Image.memory(snapshot.data!);
    }
    // 初始未点击按钮的状态
    return const Icon(Icons.image);
  },
)

步骤4:修正按钮点击逻辑

点击按钮时要调用setState,让FutureBuilder感知到diagramImage的变化:

ElevatedButton(
  onPressed: () {
    setState(() {
      diagramImage = drawDiagram();
    });
  },
  child: const Text('Create diagram'),
)

原来的代码没有调用setState,这会导致FutureBuilder不会重新构建,也是一个隐藏的小问题!

效果说明

修改后,点击按钮时主线程会立刻触发UI更新,FutureBuilder显示加载指示器;耗时的Voronoi计算在后台线程执行,完全不会阻塞UI;计算完成后,FutureBuilder会自动切换到显示生成的图片。

备注:内容来源于stack exchange,提问作者Vladislav Lebedev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.17 11:58:01