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

Flutter绘图应用添加AppBar后绘制线条偏移问题求助

问题:添加AppBar后绘图位置与触摸位置偏移

我正在使用Flutter开发一款绘图应用。当Scaffold仅设置home时,可以正常绘制线条,但添加AppBar后,绘制的线条会从顶部向下偏移,偏移距离与AppBar的高度一致,实际绘制位置与屏幕触摸位置不符。尝试过调整padding、将Container放入SizedBox等方法,但问题仍未解决。

相关代码

class HiraganaWriting extends StatefulWidget {
  const HiraganaWriting({Key? key}) : super(key: key);

  @override
  State<HiraganaWriting> createState() => _HiraganaWritingState();
}

class _HiraganaWritingState extends State<HiraganaWriting> {
  final GlobalKey _globalKey = GlobalKey();
  List<DrawnLine> lines = <DrawnLine>[];
  DrawnLine line = DrawnLine([Offset(50, 50)], Colors.black, 5.0);
  Color selectedColor = Colors.black;
  double selectedWidth = 5.0;

  StreamController<List<DrawnLine>> linesStreamController = StreamController<List<DrawnLine>>.broadcast();
  StreamController<DrawnLine> currentLineStreamController = StreamController<DrawnLine>.broadcast();

  String picName = "Hi";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("AppBar"),
      ),
      body: Container(
        child: Stack(
          children: [
            buildAllPaths(context),
            buildCurrentPath(context),
          ],
        ),
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.arrow_back), label: ""),
          BottomNavigationBarItem(icon: Icon(Icons.arrow_back), label: ""),
          BottomNavigationBarItem(icon: Icon(Icons.arrow_back), label: ""),
          BottomNavigationBarItem(icon: Icon(Icons.arrow_back), label: ""),
        ],
      ),
    );
  }

  Widget buildAllPaths(BuildContext context) {
    return RepaintBoundary(
      key: _globalKey,
      child: Container(
        width: MediaQuery.of(context).size.width,
        height: MediaQuery.of(context).size.height,
        color: Colors.transparent,
        padding: EdgeInsets.all(4),
        alignment: Alignment.topLeft,
        child: StreamBuilder<List<DrawnLine>>(
          stream: linesStreamController.stream,
          builder: (context, snapshot) {
            return CustomPaint(
              painter: Sketcher(
                lines: lines,
              ),
            );
          },
        ),
      ),
    );
  }

  Widget buildCurrentPath(BuildContext context) {
    return GestureDetector(
      onPanStart: onPanStart,
      onPanUpdate: onPanUpdate,
      onPanEnd: onPanEnd,
      child: RepaintBoundary(
        child: Container(
          width: MediaQuery.of(context).size.width,
          height: MediaQuery.of(context).size.height,
          padding: EdgeInsets.all(4),
          color: Colors.transparent,
          alignment: Alignment.topLeft,
          child: StreamBuilder<DrawnLine>(
            stream: currentLineStreamController.stream,
            builder: (context, snapshot) {
              return CustomPaint(
                painter: Sketcher(
                  lines: [line],
                ),
              );
            },
          ),
        ),
      ),
    );
  }

  void onPanStart(DragStartDetails details) {
    RenderBox? box = context.findRenderObject() as RenderBox?;
    Offset point = box!.globalToLocal(details.globalPosition);
    line = DrawnLine([point], selectedColor, selectedWidth);
  }

  void onPanUpdate(DragUpdateDetails details) {
    RenderBox? box = context.findRenderObject() as RenderBox?;
    Offset point = box!.globalToLocal(details.globalPosition);

    List<Offset> path = List.from(line.path)..add(point);
    line = DrawnLine(path, selectedColor, selectedWidth);
    currentLineStreamController.add(line);
  }

  void onPanEnd(DragEndDetails details) {
    lines = List.from(lines)..add(line);

    linesStreamController.add(lines);
  }
}

解决方案

问题根源

你在onPanStart和onPanUpdate中使用的context是页面根State的context,对应的RenderBox包含了AppBar的高度空间,导致globalToLocal转换后的坐标自带AppBar的偏移量,最终绘制位置和触摸位置不符。

修复步骤

  1. 在State类中添加一个GlobalKey,用于绑定GestureDetector下的容器:
final GlobalKey _gestureContainerKey = GlobalKey();
  1. 修改buildCurrentPath中的Container,绑定这个key:
child: RepaintBoundary(
  child: Container(
    key: _gestureContainerKey, // 新增绑定key
    width: MediaQuery.of(context).size.width,
    height: MediaQuery.of(context).size.height,
    padding: EdgeInsets.all(4),
    color: Colors.transparent,
    alignment: Alignment.topLeft,
    child: StreamBuilder<DrawnLine>(
      // 原有代码不变
    ),
  ),
),
  1. 更新onPanStart和onPanUpdate方法,使用新的key获取正确的RenderBox:
void onPanStart(DragStartDetails details) {
  RenderBox? box = _gestureContainerKey.currentContext?.findRenderObject() as RenderBox?;
  Offset point = box!.globalToLocal(details.globalPosition);
  line = DrawnLine([point], selectedColor, selectedWidth);
}

void onPanUpdate(DragUpdateDetails details) {
  RenderBox? box = _gestureContainerKey.currentContext?.findRenderObject() as RenderBox?;
  Offset point = box!.globalToLocal(details.globalPosition);

  List<Offset> path = List.from(line.path)..add(point);
  line = DrawnLine(path, selectedColor, selectedWidth);
  currentLineStreamController.add(line);
}

额外优化

当前代码中容器高度设置为MediaQuery.of(context).size.height,会导致绘制区域超出AppBar和BottomNavigationBar的范围,可以改成适配可用空间的高度:

height: MediaQuery.of(context).size.height - 
        AppBar().preferredSize.height - 
        MediaQuery.of(context).padding.top -
        kBottomNavigationBarHeight,

这个修改需要同时应用到buildAllPaths和buildCurrentPath中的Container上,确保绘制区域完全在可视范围内。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 16:45:21