2

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

Mahdi Dahouei
  • 1,588
  • 2
  • 12
  • 32
MinhDat
  • 57
  • 3

1 Answers1

2

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());
  }
}
Mahdi Dahouei
  • 1,588
  • 2
  • 12
  • 32
  • Thank you, I fixed it following your suggest. It's work! – MinhDat Jul 02 '23 at 12:31
  • However, can you explain me why it can not used in Scaffold ? – MinhDat Jul 02 '23 at 12:31
  • because in `onPanStart` method, you are getting the offset of where user touched according to `HiraganaWriting` widget size, and when you add `AppBar` to the same widget, `context.findRenderObject` gets offset from top of `AppBar` but draws it as low as the height of the AppBar. when you separate the `HiraganaWriting` from `Scaffold`, `context.findRenderObject` gets the size of `HiraganaWriting` and because it doesn't contain the `AppBar` it gets the Offset from top of itself which starts from bottom of the `AppBar`. – Mahdi Dahouei Jul 02 '23 at 12:47
  • another trick could be using `Scaffold` inside `HiraganaWriting` widget and setting the `extendBodyBehindAppBar: true,` in Scaffold. this will expand the painting area to the top of the `AppBar` and makes it work, but the better solution is the solution that is in the answer. – Mahdi Dahouei Jul 02 '23 at 12:52
  • if this helps you, please consider upvoting the answer too :) – Mahdi Dahouei Jul 02 '23 at 12:52
  • 1
    Such a great explanation. I got it. Thank you so much ! – MinhDat Jul 03 '23 at 01:49