1

I try to add a PageView which doesn't fill the whole screen.

To do that I putted the PageView inside a Column:

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(),
      body: new Column(
        children: <Widget>[
          new SizedBox(height: 100.0, child: new Center(child: new Text("sticky header"))),
          new Expanded(
            child: new PageView(
              children: <Widget>[
                new Container(
                  color: Colors.red,
                  child: new Padding(
                    padding: const EdgeInsets.all(50.0),
                    child: new _Painter(),
                  ),
                ),
                new Container(
                  color: Colors.green,
                  child: new Padding(
                    padding: const EdgeInsets.all(50.0),
                    child: new _Painter(),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

This works so far.

Each PageView has a _Painter which has a RenderBox to paint something.

And here comes my problem: I use the handleEvent method to detect drag events but the y position is wrong. You can see that the drawn line is not where I touched the screen (the transparent bubble).

enter image description here

How can I fix this? Do I have to calculate the correct y position myself?

You can find the full source here.

Update

globalToLocal fixed the problem halfway but I still have to include the padding in the calculation. Is there a way to get the padding of a widget?

void _handleDragUpdate(DragUpdateDetails details) {
  final pos = globalToLocal(details.globalPosition);
  _currentPath?.lineTo(pos.dx + 50.0, pos.dy + 50.0);
  markNeedsPaint();
}

Bonus Points

When I drag the PageView left and right my _PainterRenderBox forgets the drawn lines. Where is the best place to remember this lines? Store them in the _Painter or in the _MyHomePageState?

Ralph Bergmann
  • 3,015
  • 4
  • 30
  • 61

1 Answers1

2

Something that you are missing is to convert the globalPosition into localPosition with respect to the RenderBox. You can achieve it like

// onDragUpdate with the Painting Context
RenderBox referenceBox = context.findRenderObject();
Offset localPosition = referenceBox.globalToLocal(details.globalPosition);
// then use the localPosition to draw

Example usage for your use case as in here:

class _PainterRenderBox extends RenderBox {
  final _lines = new List<Path>();
  PanGestureRecognizer _drag;
  Path _currentPath;

  // variable to store padding
  Offset padding;

  _PainterRenderBox() {
    final GestureArenaTeam team = new GestureArenaTeam();
    _drag = new PanGestureRecognizer()
      ..team = team
      ..onStart = _handleDragStart
      ..onUpdate = _handleDragUpdate
      ..onEnd = _handleDragEnd;
  }

  @override
  bool get sizedByParent => true;

  @override
  bool hitTestSelf(Offset position) => true;

  @override
  handleEvent(PointerEvent event, BoxHitTestEntry entry) {
    assert(debugHandleEvent(event, entry));
    if (event is PointerDownEvent) {
      _drag.addPointer(event);
    }
  }

  @override
  paint(PaintingContext context, Offset offset) {
    final Canvas canvas = context.canvas;

    // update padding
    padding = offset;

    final Paint paintBorder = new Paint()
      ..strokeWidth = 1.0
      ..style = PaintingStyle.stroke
      ..color = Colors.white.withAlpha(128);
    canvas.drawRect(offset & size, paintBorder);

    final Paint paintPath = new Paint()
      ..strokeWidth = 5.0
      ..style = PaintingStyle.stroke
      ..color = Colors.white;
    _lines.forEach((path) {
      canvas.drawPath(path, paintPath);
    });
  }

  // check if the point lies inside drawable area
  bool _canDraw(Offset offset){
    return (padding & size).contains(offset);
  }

  void _handleDragStart(DragStartDetails details) {
    _currentPath = new Path();
    Offset point = globalToLocal(details.globalPosition); // convert globalPosition to localPosition
    point = padding + point; // add the padding to localPosition if any
    // check if point lies inside drawable area and then markNeedsPaint
    if(_canDraw(point)){
      _currentPath?.moveTo(point.dx, point.dy);
      _lines.add(_currentPath);
      markNeedsPaint();
    }
  }

  void _handleDragUpdate(DragUpdateDetails details) {
    Offset point = globalToLocal(details.globalPosition); // convert globalPosition to localPosition
    point = padding + point; // add the padding to localPosition if any
    // check if point lies inside drawable area and then markNeedsPaint
    if(_canDraw(point)){
      _currentPath?.lineTo(point.dx, point.dy);
      markNeedsPaint();
    }
  }

  void _handleDragEnd(DragEndDetails details) {
    _currentPath = null;
    markNeedsPaint();
  }
}

There is a question with a similar use case which allows the user to sign on the screen. Hope that might help you to know how to keep track of path. You can take a look at it here.

Hope that helps!

Hemanth Raj
  • 32,555
  • 10
  • 92
  • 82
  • That solves the problem halfway. I have a padding around the RenderBox, which has to be included in the calculation. How do I get to the padding without handing it over? – Ralph Bergmann Mar 16 '18 at 13:18
  • You receive an `Offset` along with the `PaintingContext` which tells about an padding data. Either add that offset to the points in path during drawing, or add it to the points when you add the points to path. – Hemanth Raj Mar 16 '18 at 13:56
  • I've edited my answer to include your code. Hope that helps! Accept the answer if it answered your question. – Hemanth Raj Mar 16 '18 at 14:03