flutterdrawingpaintflutter-custompainterflutter-custompaint

Drawing line is offset when add an appBar - Flu


I'm making a drawing app by Flutter. I can draw a line normally when i put it in a Scaffold with only home. But the problem is when i add an AppBar, the line is offset down from the top, it's not the position where i touch in screen, it's moved down same as the height of App BAr.

Here's my code.

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);
  }
}

enter image description here

I tried to change padding, or put Container in of sizebox,.... but it's working wrongly


Solution

  • Don't use the scaffold inside HiraganaWriting Widget, this widget is the painting part of application, just seperate this widget from scaffold.

    HiraganaWriting:

    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 Stack(
          children: [
            buildAllPaths(context),
            buildCurrentPath(context),
          ],
        );
      }
    
      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);
      }
    }
    

    Then use it in your page Widget like this:

    class TestPage extends StatelessWidget {
      const TestPage({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
              title: const Text("AppBar"),
            ),
            body: const HiraganaWriting());
      }
    }