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

Flutter GridPaper组件单个容器着色异常问题求助——路径可视化工具中起点/终点/障碍物颜色区分实现

Hey Robsen, let's fix this issue step by step. The core problem here is that you're using a single list to track all activated cells without differentiating their types (start, end, obstacle), and your block variable isn't managed as reactive state—plus, when you switch to start mode, you're re-rendering every existing activated cell as green instead of just marking new clicks as the start point.

1. Create a Node Type System

First, we need a way to distinguish between different cell types. Let's define an enum to represent these clearly:

enum NodeType { obstacle, start, end }

Instead of a List<Offset>, use a Map<Offset, NodeType> to track which offset corresponds to which node type. This gives us explicit separation between obstacles, start points, and end points.

2. Make block Reactive State

Your current block variable is a plain class member—changing it won't trigger a UI rebuild. Convert it to a useState hook so Flutter knows to update the UI when its value changes:

final block = useState(NodeType.obstacle); // Default to obstacle mode

3. Update the Toggle Logic

Modify the _toggle function to handle different node types, and add logic to ensure only one start/end point exists (standard for pathfinding apps):

void _toggle(Offset offset) {
  final currentType = block.value;
  // Clear existing start/end if we're setting a new one
  if (currentType == NodeType.start) {
    _nodes.value.removeWhere((key, value) => value == NodeType.start);
  } else if (currentType == NodeType.end) {
    _nodes.value.removeWhere((key, value) => value == NodeType.end);
  }

  // Toggle the cell: if it's already the current type, remove it; else set it
  if (_nodes.value.containsKey(offset) && _nodes.value[offset] == currentType) {
    _nodes.value.remove(offset);
  } else {
    _nodes.value[offset] = currentType;
  }
  // Trigger state update to refresh UI
  _nodes.value = {..._nodes.value};
}

4. Rewrite the Rendering Logic

Instead of switching on block to render all cells the same, iterate through the Map and render each cell based on its stored NodeType:

List<Widget> gridContainerMap(ValueNotifier<Map<Offset, NodeType>> _nodes) {
  final widgets = [Container(color: Colors.white)];
  _nodes.value.forEach((offset, type) {
    Color color;
    switch (type) {
      case NodeType.obstacle:
        color = Colors.black;
        break;
      case NodeType.start:
        color = Colors.green;
        break;
      case NodeType.end:
        color = Colors.red;
        break;
    }
    widgets.add(Positioned(
      left: offset.dx * cellSize,
      top: offset.dy * cellSize,
      width: cellSize,
      height: cellSize,
      child: ColoredBox(color: color),
    ));
  });
  return widgets;
}

Full Modified Code

Here's the complete working code with all these changes:

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

enum NodeType { obstacle, start, end }

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  final appTitle = 'Path Finder';
  final Color gridColor = Colors.lightBlue[100]!;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: appTitle,
      home: MyHomePage(title: appTitle),
    );
  }
}

class MyHomePage extends HookWidget {
  final double cellSize = 20.0;
  final String title;
  GlobalKey _key = GlobalKey();

  MyHomePage({
    Key? key,
    required this.title,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final _nodes = useState<Map<Offset, NodeType>>({});
    final block = useState(NodeType.obstacle); // Reactive state for current mode

    void _toggle(Offset offset) {
      final currentType = block.value;
      // Ensure only one start/end exists
      if (currentType == NodeType.start) {
        _nodes.value.removeWhere((key, value) => value == NodeType.start);
      } else if (currentType == NodeType.end) {
        _nodes.value.removeWhere((key, value) => value == NodeType.end);
      }

      // Toggle the cell
      if (_nodes.value.containsKey(offset) && _nodes.value[offset] == currentType) {
        _nodes.value.remove(offset);
      } else {
        _nodes.value[offset] = currentType;
      }
      _nodes.value = {..._nodes.value};
    }

    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: GestureDetector(
        onTapDown: (details) => _toggle(details.localPosition ~/ cellSize),
        child: GridPaper(
          child: Stack(
            children: gridContainerMap(_nodes),
          ),
          color: Colors.lightBlue[100]!,
          interval: cellSize,
          divisions: 1,
          subdivisions: 1,
        ),
      ),
      drawer: Drawer(
        child: ListView(
          padding: EdgeInsets.zero,
          children: <Widget>[
            const DrawerHeader(
              child: Text('Einstellungen'),
              decoration: BoxDecoration(
                color: Colors.blue,
              ),
            ),
            ListTile(
              title: const Text('Startpunkt'),
              onTap: () {
                block.value = NodeType.start;
                Navigator.pop(context);
              },
            ),
            ListTile(
              title: const Text('Ziel'),
              onTap: () {
                block.value = NodeType.end;
                Navigator.pop(context);
              },
            ),
            ListTile(
              title: const Text('Pathfinding-Algorithm'),
              onTap: () {
                Navigator.pop(context);
              },
            ),
          ],
        ),
      ),
    );
  }

  List<Widget> gridContainerMap(ValueNotifier<Map<Offset, NodeType>> _nodes) {
    final widgets = [Container(color: Colors.white)];
    _nodes.value.forEach((offset, type) {
      Color color;
      switch (type) {
        case NodeType.obstacle:
          color = Colors.black;
          break;
        case NodeType.start:
          color = Colors.green;
          break;
        case NodeType.end:
          color = Colors.red;
          break;
      }
      widgets.add(Positioned(
        left: offset.dx * cellSize,
        top: offset.dy * cellSize,
        width: cellSize,
        height: cellSize,
        child: ColoredBox(color: color),
      ));
    });
    return widgets;
  }
}

Key Improvements Explained

  • Node Type Enum: Clearly categorizes each cell's purpose, making it easy to extend later (like adding visited/path nodes for pathfinding algorithms).
  • Reactive State: Using useState for both _nodes and block ensures the UI updates correctly when either value changes.
  • Single Start/End: The toggle logic prevents multiple start or end points, which is a standard requirement for pathfinding visualizers.
  • Targeted Rendering: Each cell is rendered based on its stored type, so switching modes only affects new clicks—existing cells keep their original type.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 09:52:29