首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Flutter for OpenHarmony 可视化教学:A* 寻路算法的交互式演示

Flutter for OpenHarmony 可视化教学:A* 寻路算法的交互式演示

作者头像
晚霞的不甘
发布2026-02-09 16:15:21
发布2026-02-09 16:15:21
360
举报

Flutter for OpenHarmony 可视化教学:A* 寻路算法的交互式演示

在人工智能、游戏开发和机器人导航等领域,路径规划(Pathfinding) 是一项基础而关键的技术。其中,A*(A-Star)算法因其高效性与最优性,成为最广为人知的启发式搜索算法之一。然而,其背后的“开启列表”、“关闭列表”、“f = g + h”等概念对初学者而言仍显抽象。

本文将深入解析一段完整的 Flutter 应用代码,该应用通过直观的网格界面、动态的颜色反馈和流畅的动画效果,将 A* 算法的执行过程转化为一场沉浸式的可视化学习体验。你将看到:如何用 Dart 实现经典的 A* 逻辑,如何利用 GridView 构建交互式地图,以及如何设计一个既能教学又能娱乐的寻路模拟器。

🌐 加入社区 欢迎加入 开源鸿蒙跨平台开发者社区,获取最新资源与技术支持: 👉 开源鸿蒙跨平台开发者社区


完整效果

在这里插入图片描述
在这里插入图片描述

一、应用概览:交互式寻路沙盒

该应用提供了一个 15×20 的网格世界,用户可以:

  • 点击任意空白格子:添加或移除灰色障碍物(墙壁);
  • 点击“开始寻路”:启动 A* 算法,从绿色起点到红色终点寻找最短路径;
  • 实时观察:蓝色区域表示算法正在探索的节点,黄色路径即为最终结果;
  • 一键重置:恢复初始状态,或仅清除障碍物进行新实验。

💡 核心目标:让抽象的图搜索算法变得可见、可操作、可理解


二、A* 算法原理简述

A* 是一种最佳优先搜索算法,它通过评估函数 f(n) = g(n) + h(n) 来决定下一个要探索的节点:

  • g(n):从起点到当前节点 n 的实际代价(本例中为步数);
  • h(n):从节点 n 到终点的启发式估计代价(本例使用曼哈顿距离);
  • f(n):总预估代价,值越小,优先级越高。

算法维护两个关键集合:

  • 开启列表(Open List):待检查的候选节点;
  • 关闭列表(Closed List):已处理完毕的节点。

通过不断从开启列表中选择 f 值最小的节点进行扩展,并更新邻居的代价,最终找到一条从起点到终点的最短路径(若存在)。


三、代码实现详解

1. 数据结构设计
▶ 网格单元类型
代码语言:javascript
复制
enum CellType { empty, wall, start, end, path, visited }
在这里插入图片描述
在这里插入图片描述

  • 清晰定义六种状态,便于 UI 渲染与逻辑判断。
▶ 二维坐标点
代码语言:javascript
复制
class Point {
  final int x, y;
  // 重写 == 和 hashCode,使其可作为 Map 的 key
}
在这里插入图片描述
在这里插入图片描述

  • 支持集合操作(如 Set<Point>)和映射(如 Map<Point, Point>),是实现 cameFrom 路径回溯的关键。
2. A* 核心逻辑 (_runAStar)
▶ 初始化
代码语言:javascript
复制
List<Point> openList = [start];
Set<Point> closedList = {};
Map<Point, Point> cameFrom = {}; // 记录父节点
Map<Point, double> gScore = {start: 0};
Map<Point, double> fScore = {start: _heuristic(start, end)};
▶ 主循环

  1. 选点:从 openList 中选出 fScore 最小的节点 current
  2. 终止条件:若 current == end,调用 _reconstructPath 回溯路径;
  3. 标记访问:将 current 加入 closedList,并在 UI 上标记为蓝色(CellType.visited);
  4. 探索邻居:遍历上下左右四个方向;
    • 跳过越界、墙壁或已在 closedList 中的点;
    • 计算临时 gScore,若更优则更新 gScorefScorecameFrom,并将邻居加入 openList
▶ 启发式函数(曼哈顿距离)
代码语言:javascript
复制
double _heuristic(Point a, Point b) {
  return (a.x - b.x).abs() + (a.y - b.y).abs();
}
在这里插入图片描述
在这里插入图片描述

  • 适用于只能上下左右移动的网格地图,保证可采纳性(Admissible),即不会高估实际代价,从而确保找到最优解。
▶ 路径重构
代码语言:javascript
复制
Future<void> _reconstructPath(Map<Point, Point> cameFrom, Point current) async {
  List<Point> totalPath = [];
  while (cameFrom.containsKey(current)) {
    current = cameFrom[current]!;
    totalPath.add(current);
  }
  // 从起点到终点反向遍历,再反转
  totalPath = totalPath.reversed.toList();
  // 逐个高亮为黄色
}
3. 异步与动画
  • await Future.delayed(...):在每次更新网格状态后暂停片刻,形成“探索”和“绘制路径”的动画效果;
  • setState(() {}):触发 UI 重绘,实时反映算法进展;
  • isRunning 标志:防止用户在运行时误操作,保证状态一致性。

四、UI/UX 设计亮点

1. 直观的色彩编码

颜色

含义

🟢 绿色

起点 (start)

🔴 红色

终点 (end)

⚪ 透明/白色

空地 (empty)

⚫ 灰色

障碍物 (wall)

🔵 蓝色(半透)

已探索节点 (visited)

🟡 黄色

最终路径 (path)

这种设计让用户一眼就能分辨算法的当前状态。

2. 交互式网格 (GridView.builder)
  • 每个格子都是一个 GestureDetector,点击即可切换空地/墙壁;
  • 使用 SliverGridDelegateWithFixedCrossAxisCount 精确控制列数,确保网格比例协调;
  • mainAxisSpacingcrossAxisSpacing 添加 1px 间隙,提升视觉清晰度。
3. 控制面板

三个功能按钮布局合理:

  • 重置:恢复初始布局(含默认起点终点);
  • 清空障碍:保留起点终点,仅移除墙壁,方便快速测试不同障碍配置;
  • 开始寻路:核心操作,运行时显示“运行中…”并禁用其他按钮。

五、教育价值与应用场景

教学场景
  • 算法课演示:动态展示 A* 如何“聪明地”避开障碍,优先向终点方向搜索;
  • 启发式函数对比:可轻松扩展为支持欧几里得距离、对角线移动等,观察不同 h(n) 对搜索效率的影响;
  • 失败案例分析:当终点被完全包围时,应用会弹出提示“未找到可达路径!”,帮助学生理解算法的局限性。
开发参考
  • 游戏 AI:可作为 NPC 自动寻路的基础模块;
  • 地图应用:室内导航、仓库机器人路径规划的简化模型;
  • 性能基准:通过调整网格大小,测试算法在不同规模问题下的表现。

六、总结

这段 Flutter 代码不仅是一个功能完整的 A* 寻路演示器,更是一个优秀的计算思维教学工具。它将复杂的图搜索过程分解为可视化的步骤,通过颜色、动画和即时反馈,极大地降低了学习门槛。

完整代码

代码语言:javascript
复制
import 'dart:ui' as ui;
import 'package:flutter/material.dart';

void main() {
  runApp(const PathFindingApp());
}

class PathFindingApp extends StatelessWidget {
  const PathFindingApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'A* 寻路算法',
      theme: ThemeData.dark(),
      home: const AStarVisualizer(),
      debugShowCheckedModeBanner: false,
    );
  }
}

// 网格状态枚举
enum CellType { empty, wall, start, end, path, visited }

// 二维坐标点
class Point {
  final int x, y;
  Point(this.x, this.y);

  @override
  bool operator ==(Object other) {
    return other is Point && x == other.x && y == other.y;
  }

  @override
  int get hashCode => Object.hash(x, y);
}

class AStarVisualizer extends StatefulWidget {
  const AStarVisualizer({super.key});

  @override
  State<AStarVisualizer> createState() => _AStarVisualizerState();
}

class _AStarVisualizerState extends State<AStarVisualizer> {
  static const int rows = 15;
  static const int cols = 20;
  late List<List<CellType>> grid;
  Point? start;
  Point? end;
  bool isRunning = false;

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

  void _resetGrid() {
    grid = List.generate(rows, (i) {
      return List.generate(cols, (j) {
        return CellType.empty;
      });
    });
    start = Point(1, 1);
    end = Point(rows - 2, cols - 2);
    grid[start!.x][start!.y] = CellType.start;
    grid[end!.x][end!.y] = CellType.end;
  }

  void _clearWalls() {
    for (int i = 0; i < rows; i++) {
      for (int j = 0; j < cols; j++) {
        if (grid[i][j] == CellType.wall) {
          grid[i][j] = CellType.empty;
        }
      }
    }
  }

  // A* 算法核心
  Future<void> _runAStar() async {
    if (start == null || end == null) return;

    setState(() {
      isRunning = true;
    });

    // 开启列表:待检查的节点
    List<Point> openList = [Point(start!.x, start!.y)];
    // 关闭列表:已检查的节点
    Set<Point> closedList = {};

    // 记录路径 (父节点映射)
    Map<Point, Point> cameFrom = {};

    // gScore: 从起点到该节点的实际代价
    Map<Point, double> gScore = {};
    gScore[start!] = 0;

    // fScore: 从起点经由该节点到终点的预估总代价
    Map<Point, double> fScore = {};
    fScore[start!] = _heuristic(start!, end!);

    while (openList.isNotEmpty) {
      // 1. 找到fScore最小的节点
      Point current = openList.reduce((a, b) {
        return (fScore[a] ?? double.infinity) < (fScore[b] ?? double.infinity)
            ? a
            : b;
      });

      // 2. 如果到达终点,重构路径
      if (current == end) {
        await _reconstructPath(cameFrom, current);
        setState(() {
          isRunning = false;
        });
        return;
      }

      // 3. 将当前节点移出开启列表,加入关闭列表
      openList.remove(current);
      closedList.add(current);

      // 可视化:标记为已访问
      if (grid[current.x][current.y] != CellType.start &&
          grid[current.x][current.y] != CellType.end) {
        grid[current.x][current.y] = CellType.visited;
        setState(() {});
        await Future.delayed(const Duration(milliseconds: 50));
      }

      // 4. 检查邻居 (上下左右)
      List<Point> neighbors = [
        Point(current.x - 1, current.y),
        Point(current.x + 1, current.y),
        Point(current.x, current.y - 1),
        Point(current.x, current.y + 1),
      ];

      for (Point neighbor in neighbors) {
        int x = neighbor.x;
        int y = neighbor.y;

        // 检查是否越界或为墙壁
        if (x < 0 ||
            x >= rows ||
            y < 0 ||
            y >= cols ||
            grid[x][y] == CellType.wall) {
          continue;
        }

        // 如果已经在关闭列表,跳过
        if (closedList.contains(neighbor)) {
          continue;
        }

        // 计算临时的gScore (从起点到邻居的代价)
        double tentativeGScore = (gScore[current] ?? double.infinity) + 1;

        // 如果该邻居不在开启列表中,或者找到了更短的路径
        if (!openList.contains(neighbor) ||
            tentativeGScore < (gScore[neighbor] ?? double.infinity)) {
          // 记录路径
          cameFrom[neighbor] = current;
          gScore[neighbor] = tentativeGScore;
          fScore[neighbor] = tentativeGScore + _heuristic(neighbor, end!);

          if (!openList.contains(neighbor)) {
            openList.add(neighbor);
          }
        }
      }
    }

    // 没有找到路径
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('未找到可达路径!')),
    );
    setState(() {
      isRunning = false;
    });
  }

  // 启发式函数 (曼哈顿距离)
  double _heuristic(Point a, Point b) {
    return ((a.x - b.x).abs() + (a.y - b.y).abs()).toDouble();
  }

  // 重构路径
  Future<void> _reconstructPath(
      Map<Point, Point> cameFrom, Point current) async {
    List<Point> totalPath = [current];
    while (cameFrom.containsKey(current)) {
      current = cameFrom[current]!;
      totalPath.add(current);
    }
    totalPath = totalPath.reversed.toList();

    // 高亮显示路径
    for (Point p in totalPath) {
      if (p != start && p != end) {
        grid[p.x][p.y] = CellType.path;
        setState(() {});
        await Future.delayed(const Duration(milliseconds: 100));
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('A* 寻路算法可视化'),
      ),
      body: Column(
        children: [
          const Text('绿色=起点, Red=终点, Gray=障碍物, Blue=探索中, Yellow=路径'),
          Expanded(
            child: GridView.builder(
              padding: const EdgeInsets.all(8),
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: cols,
                mainAxisSpacing: 1,
                crossAxisSpacing: 1,
              ),
              itemCount: rows * cols,
              itemBuilder: (context, index) {
                int row = index ~/ cols;
                int col = index % cols;
                bool isStart = Point(row, col) == start;
                bool isEnd = Point(row, col) == end;

                Color color = Colors.transparent;
                if (grid[row][col] == CellType.wall) {
                  color = Colors.grey;
                } else if (grid[row][col] == CellType.visited) {
                  color = Colors.blue.withOpacity(0.5);
                } else if (grid[row][col] == CellType.path) {
                  color = Colors.yellow;
                } else if (isStart) {
                  color = Colors.green;
                } else if (isEnd) {
                  color = Colors.red;
                }

                return GestureDetector(
                  onTap: () {
                    if (isRunning) return;
                    setState(() {
                      if (grid[row][col] == CellType.empty) {
                        grid[row][col] = CellType.wall;
                      } else if (grid[row][col] == CellType.wall) {
                        grid[row][col] = CellType.empty;
                      }
                    });
                  },
                  child: Container(
                    decoration: BoxDecoration(color: color),
                    child: const SizedBox(),
                  ),
                );
              },
            ),
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              ElevatedButton.icon(
                onPressed: () {
                  if (!isRunning) {
                    _resetGrid();
                    setState(() {});
                  }
                },
                icon: const Icon(Icons.refresh),
                label: const Text('重置'),
              ),
              ElevatedButton.icon(
                onPressed: () {
                  if (!isRunning) {
                    _clearWalls();
                    setState(() {});
                  }
                },
                icon: const Icon(Icons.delete),
                label: const Text('清空障碍'),
              ),
              ElevatedButton.icon(
                onPressed: _runAStar,
                icon: const Icon(Icons.route),
                label: Text(isRunning ? '运行中...' : '开始寻路'),
              ),
            ],
          ),
        ],
      ),
    );
  }
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2026-02-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Flutter for OpenHarmony 可视化教学:A* 寻路算法的交互式演示
    • 一、应用概览:交互式寻路沙盒
    • 二、A* 算法原理简述
    • 三、代码实现详解
      • 1. 数据结构设计
      • 2. A* 核心逻辑 (_runAStar)
      • 3. 异步与动画
    • 四、UI/UX 设计亮点
      • 1. 直观的色彩编码
      • 2. 交互式网格 (GridView.builder)
      • 3. 控制面板
    • 五、教育价值与应用场景
      • 教学场景
      • 开发参考
    • 六、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档