Git Product home page Git Product logo

Comments (14)

ibrierley avatar ibrierley commented on July 17, 2024 1

One thing you could do, is take a look at my other plugin flutter_map_line_editor, as it seems to sort of what you want as a by product, and not have the issue you have (when it goes offscreen). It is using an older version of dragmarker tho, so that may have some effect. I don't have a lot of time to debug further into that atm tho, but it may help spot where the difference is.

from flutter_map_dragmarker.

pablojimpas avatar pablojimpas commented on July 17, 2024 1

And I'll take a look at the flutter_map_line_editor. Thanks a ton : )

Be aware that ibrierley/flutter_map_line_editor#36 might get merged, so that line editor reuses this package instead of its drag marker implementation and things can break for your use case.

from flutter_map_dragmarker.

ibrierley avatar ibrierley commented on July 17, 2024

Thanks for this! Good spot and surprised I missed this. I haven't got a lot of time for the next week, but I will try and sort this when I get chance.

from flutter_map_dragmarker.

ibrierley avatar ibrierley commented on July 17, 2024

This may fix it for version 4 (readying for flutter_map v3), so I'll try and get this type of solution added there where I get chance. If you need a quick hack, look at the stuff with marker.point and markerPoint which is now saved by initState and amend anything with those in it.

It may be a few days before I get time to test this a bit more and push to Git and publish to pub.dev.

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:latlong2/latlong.dart';
import 'package:flutter_map/plugin_api.dart';

class DragMarkers extends StatefulWidget {
  final List<DragMarker> markers;

  DragMarkers({Key? key, this.markers = const []});

  @override
  State<DragMarkers> createState() => _DragMarkersState();
}

class _DragMarkersState extends State<DragMarkers> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    var dragMarkers = <Widget>[];

    FlutterMapState? mapState = FlutterMapState.maybeOf(context);

    for (var marker in widget.markers) {
      if (!_boundsContainsMarker(mapState, marker)) continue;

      dragMarkers.add(DragMarkerWidget(
          mapState: mapState,
          marker: marker));
    }
    return Stack(children: dragMarkers);
  }

  static bool _boundsContainsMarker(FlutterMapState? map, DragMarker marker) {
    var pixelPoint = map!.project(marker.point);

    final width = marker.width - marker.anchor.left;
    final height = marker.height - marker.anchor.top;

    var sw = CustomPoint(pixelPoint.x + width, pixelPoint.y - height);
    var ne = CustomPoint(pixelPoint.x - width, pixelPoint.y + height);

    return map.pixelBounds.containsPartialBounds(Bounds(sw, ne));
  }
}

class DragMarkerWidget extends StatefulWidget {
  const DragMarkerWidget(
      {Key? key,
      this.mapState,
      required this.marker,
      AnchorPos? anchorPos})
      //: anchor = Anchor.forPos(anchorPos, marker.width, marker.height);
      : super(key: key);

  final FlutterMapState? mapState;
  //final Anchor anchor;
  final DragMarker marker;

  @override
  State<DragMarkerWidget> createState() => _DragMarkerWidgetState();
}

class _DragMarkerWidgetState extends State<DragMarkerWidget> {
  CustomPoint pixelPosition = const CustomPoint(0.0, 0.0);
  late LatLng dragPosStart;
  late LatLng markerPointStart;
  late LatLng oldDragPosition;
  bool isDragging = false;
  late LatLng markerPoint;

  static Timer? autoDragTimer;

  @override
  void initState() {
    markerPoint = widget.marker.point;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    DragMarker marker = widget.marker;
    updatePixelPos(markerPoint);

    bool feedBackEnabled = isDragging && marker.feedbackBuilder != null;
    Widget displayMarker = feedBackEnabled
        ? marker.feedbackBuilder!(context)
        : marker.builder!(context);

    return GestureDetector(
      onPanStart: marker.useLongPress ? null : onPanStart,
      onPanUpdate: marker.useLongPress ? null : onPanUpdate,
      onPanEnd: marker.useLongPress ? null : onPanEnd,
      onLongPressStart: marker.useLongPress ? onLongPanStart : null,
      onLongPressMoveUpdate: marker.useLongPress ? onLongPanUpdate : null,
      onLongPressEnd: marker.useLongPress ? onLongPanEnd : null,
      onTap: () {
        if (marker.onTap != null) {
          marker.onTap!(markerPoint);
        }
      },
      onLongPress: () {
        if (marker.onLongPress != null) {
          marker.onLongPress!(markerPoint);
        }
      },
      child: Stack(children: [
        Positioned(
            width: marker.width,
            height: marker.height,
            left: pixelPosition.x +
                ((isDragging) ? marker.feedbackOffset.dx : marker.offset.dx),
            top: pixelPosition.y +
                ((isDragging) ? marker.feedbackOffset.dy : marker.offset.dy),
            child: widget.marker.rotateMarker
                ? Transform.rotate(
                    angle: -widget.mapState!.rotationRad, child: displayMarker)
                : displayMarker)
      ]),
    );
  }

  void updatePixelPos(point) {
    DragMarker marker = widget.marker;
    FlutterMapState? mapState = widget.mapState;

    CustomPoint pos;
    if (mapState != null) {
      pos = mapState.project(point);
      pos =
          pos.multiplyBy(mapState.getZoomScale(mapState.zoom, mapState.zoom)) -
              mapState.pixelOrigin;

      pixelPosition = CustomPoint(
          (pos.x - (marker.width - widget.marker.anchor.left)).toDouble(),
          (pos.y - (marker.height - widget.marker.anchor.top)).toDouble());
    }
  }

  void _start(Offset localPosition) {
    isDragging = true;
    dragPosStart = _offsetToCrs(localPosition);
    markerPointStart =
        LatLng(markerPoint.latitude, markerPoint.longitude);
  }

  void onPanStart(DragStartDetails details) {
    _start(details.localPosition);
    DragMarker marker = widget.marker;
    if (marker.onDragStart != null) marker.onDragStart!(details, markerPoint);
  }

  void onLongPanStart(LongPressStartDetails details) {
    _start(details.localPosition);
    DragMarker marker = widget.marker;
    if (marker.onLongDragStart != null) {
      marker.onLongDragStart!(details, markerPoint);
    }
  }

  void _pan(Offset localPosition) {
    bool isDragging = true;
    DragMarker marker = widget.marker;
    FlutterMapState? mapState = widget.mapState;

    var dragPos = _offsetToCrs(localPosition);

    var deltaLat = dragPos.latitude - dragPosStart.latitude;
    var deltaLon = dragPos.longitude - dragPosStart.longitude;

    var pixelB = mapState?.getPixelBounds(mapState.zoom);    //getLastPixelBounds();
    var pixelPoint = mapState?.project(markerPoint);

    /// If we're near an edge, move the map to compensate.

    if (marker.updateMapNearEdge) {
      /// How much we'll move the map by to compensate

      var autoOffsetX = 0.0;
      var autoOffsetY = 0.0;
      if (pixelB != null && pixelPoint != null) {
        if (pixelPoint.x + marker.width * marker.nearEdgeRatio >=
            pixelB.topRight.x) autoOffsetX = marker.nearEdgeSpeed;
        if (pixelPoint.x - marker.width * marker.nearEdgeRatio <=
            pixelB.bottomLeft.x) autoOffsetX = -marker.nearEdgeSpeed;
        if (pixelPoint.y - marker.height * marker.nearEdgeRatio <=
            pixelB.topRight.y) autoOffsetY = -marker.nearEdgeSpeed;
        if (pixelPoint.y + marker.height * marker.nearEdgeRatio >=
            pixelB.bottomLeft.y) autoOffsetY = marker.nearEdgeSpeed;
      }

      /// Sometimes when dragging the onDragEnd doesn't fire, so just stops dead.
      /// Here we allow a bit of time to keep dragging whilst user may move
      /// around a bit to keep it going.

      var lastTick = 0;
      if (autoDragTimer != null) lastTick = autoDragTimer!.tick;

      if ((autoOffsetY != 0.0) || (autoOffsetX != 0.0)) {
        adjustMapToMarker(widget, autoOffsetX, autoOffsetY);

        if ((autoDragTimer == null || autoDragTimer?.isActive == false) &&
            (isDragging == true)) {
          autoDragTimer =
              Timer.periodic(const Duration(milliseconds: 10), (Timer t) {
            var tick = autoDragTimer?.tick;
            bool tickCheck = false;
            if (tick != null) {
              if (tick > lastTick + 15) {
                tickCheck = true;
              }
            }
            if (isDragging == false || tickCheck) {
              autoDragTimer?.cancel();
            } else {
              /// Note, we may have adjusted a few lines up in same drag,
              /// so could test for whether we've just done that
              /// this, but in reality it seems to work ok as is.

              adjustMapToMarker(widget, autoOffsetX, autoOffsetY);
            }
          });
        }
      }
    }

    setState(() {
      markerPoint = LatLng(markerPointStart.latitude + deltaLat,
          markerPointStart.longitude + deltaLon);
      updatePixelPos(markerPoint);
    });
  }

  void onPanUpdate(DragUpdateDetails details) {
    _pan(details.localPosition);
    DragMarker marker = widget.marker;
    if (marker.onDragUpdate != null) {
      marker.onDragUpdate!(details, markerPoint);
    }
  }

  void onLongPanUpdate(LongPressMoveUpdateDetails details) {
    _pan(details.localPosition);
    DragMarker marker = widget.marker;
    if (marker.onLongDragUpdate != null) {
      marker.onLongDragUpdate!(details, markerPoint);
    }
  }

  /// If dragging near edge of the screen, adjust the map so we keep dragging
  void adjustMapToMarker(DragMarkerWidget widget, autoOffsetX, autoOffsetY) {
    DragMarker marker = widget.marker;
    FlutterMapState? mapState = widget.mapState;

    var oldMapPos = mapState?.project(mapState.center);
    LatLng? newMapLatLng;
    CustomPoint<num>? oldMarkerPoint;
    if (oldMapPos != null) {
      newMapLatLng = mapState?.unproject(
          CustomPoint(oldMapPos.x + autoOffsetX, oldMapPos.y + autoOffsetY));
      oldMarkerPoint = mapState?.project(markerPoint);
    }
    if (mapState != null && newMapLatLng != null && oldMarkerPoint != null) {
      markerPoint = mapState.unproject(CustomPoint(
          oldMarkerPoint.x + autoOffsetX, oldMarkerPoint.y + autoOffsetY));

      mapState.move(newMapLatLng, mapState.zoom, source: MapEventSource.onDrag);
    }
  }

  void _end() {
    isDragging = false;
    if (autoDragTimer != null) autoDragTimer?.cancel();
  }

  void onPanEnd(details) {
    _end();
    if (widget.marker.onDragEnd != null) {
      widget.marker.onDragEnd!(details, markerPoint);
    }
    setState(() {}); // Needed if using a feedback widget
  }

  void onLongPanEnd(details) {
    _end();
    if (widget.marker.onLongDragEnd != null) {
      widget.marker.onLongDragEnd!(details, markerPoint);
    }
    setState(() {}); // Needed if using a feedback widget
  }

  static CustomPoint _offsetToPoint(Offset offset) {
    return CustomPoint(offset.dx, offset.dy);
  }

  LatLng _offsetToCrs(Offset offset) {
    // Get the widget's offset
    var renderObject = context.findRenderObject() as RenderBox;
    var width = renderObject.size.width;
    var height = renderObject.size.height;
    var mapState = widget.mapState;

    // convert the point to global coordinates
    var localPoint = _offsetToPoint(offset);
    var localPointCenterDistance =
        CustomPoint((width / 2) - localPoint.x, (height / 2) - localPoint.y);
    if (mapState != null) {
      var mapCenter = mapState.project(mapState.center);
      var point = mapCenter - localPointCenterDistance;
      return mapState.unproject(point);
    }
    return LatLng(0, 0);
  }
}

class DragMarker {
  LatLng point;
  final WidgetBuilder? builder;
  final WidgetBuilder? feedbackBuilder;
  final double width;
  final double height;
  final Offset offset;
  final Offset feedbackOffset;
  final bool useLongPress;
  final Function(DragStartDetails, LatLng)? onDragStart;
  final Function(DragUpdateDetails, LatLng)? onDragUpdate;
  final Function(DragEndDetails, LatLng)? onDragEnd;
  final Function(LongPressStartDetails, LatLng)? onLongDragStart;
  final Function(LongPressMoveUpdateDetails, LatLng)? onLongDragUpdate;
  final Function(LongPressEndDetails, LatLng)? onLongDragEnd;
  final Function(LatLng)? onTap;
  final Function(LatLng)? onLongPress;
  final bool updateMapNearEdge;
  final double nearEdgeRatio;
  final double nearEdgeSpeed;
  final bool rotateMarker;
  late Anchor anchor;

  DragMarker({
    required this.point,
    this.builder,
    this.feedbackBuilder,
    this.width = 30.0,
    this.height = 30.0,
    this.offset = const Offset(0.0, 0.0),
    this.feedbackOffset = const Offset(0.0, 0.0),
    this.useLongPress = false,
    this.onDragStart,
    this.onDragUpdate,
    this.onDragEnd,
    this.onLongDragStart,
    this.onLongDragUpdate,
    this.onLongDragEnd,
    this.onTap,
    this.onLongPress,
    this.updateMapNearEdge = false, // experimental
    this.nearEdgeRatio = 1.5,
    this.nearEdgeSpeed = 1.0,
    this.rotateMarker = true,
    AnchorPos? anchorPos,
  }) {
    anchor = Anchor.forPos(anchorPos, width, height);
  }
}

from flutter_map_dragmarker.

SheenaJacob avatar SheenaJacob commented on July 17, 2024

Thanks for the quick response @ibrierley

from flutter_map_dragmarker.

pablojimpas avatar pablojimpas commented on July 17, 2024

@SheenaJacob can you confirm if the problem is solved to close this issue?

from flutter_map_dragmarker.

SheenaJacob avatar SheenaJacob commented on July 17, 2024

So the first part of the issue is now fixed, which means that a rebuild no longer triggers a change in the position of a marker while dragging.
I'm still facing an issue when dragging a marker whose position is constantly updated on onDragUpdate() outside the border though. At some point, the marker gets stuck when moving it outside the border even though the drag action is still taking place.

For example, in the video below there are two markers. In the first part of the video the black marker is dragged outside the border and I can bring it back without facing any problems. On the other hand, when dragging the red marker outside, the drag event can no longer be recognized and at 0:05 seconds you can see that the position displayed at the bottom is no longer updated even though I'm still moving the mouse. The difference between the two markers is that the black marker's position is not updated, while the red marker's position is updated every time the onDragUpdate is called.

Screen.Recording.2023-05-19.at.08.19.26.mov

The code for the example above is :

import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map/plugin_api.dart';
import 'package:flutter_map_dragmarker/dragmarker.dart';
import 'package:latlong2/latlong.dart';

class DragMarkerBoundaryTest extends StatefulWidget {
  const DragMarkerBoundaryTest({super.key});

  @override
  State<DragMarkerBoundaryTest> createState() => _DragMarkerBoundaryTestState();
}

class _DragMarkerBoundaryTestState extends State<DragMarkerBoundaryTest> {
  final LatLng _marker1Position = LatLng(44.1461, 9.9000);
  LatLng _marker2Position = LatLng(44.1461, 10.1122);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        floatingActionButton: Text(
            ' Marker1 Position : $_marker1Position \n Marker2 Position : $_marker2Position'),
        body: Center(
          child: FlutterMap(
            options: MapOptions(
                absorbPanEventsOnScrollables: false,
                center: _marker1Position,
                zoom: 10.4,
                maxZoom: 18.0),
            children: [
              TileLayer(
                  urlTemplate:
                      'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
                  subdomains: const ['a', 'b', 'c']),
              DragMarkers(
                markers: [
                  DragMarker(
                    point: _marker1Position,
                    width: 80.0,
                    height: 80.0,
                    offset: const Offset(0.0, -8.0),
                    builder: (ctx) => const Icon(Icons.location_on,
                        size: 50, color: Colors.black),
                    feedbackBuilder: (ctx) => const Icon(Icons.edit_location,
                        size: 50, color: Colors.black),
                    feedbackOffset: const Offset(0.0, -8.0),
                  ),
                  DragMarker(
                    point: _marker2Position,
                    width: 80.0,
                    height: 80.0,
                    offset: const Offset(0.0, -8.0),
                    builder: (ctx) => const Icon(Icons.location_on,
                        size: 50, color: Colors.red),
                    feedbackBuilder: (ctx) => const Icon(Icons.edit_location,
                        size: 50, color: Colors.red),
                    feedbackOffset: const Offset(0.0, -8.0),
                    onDragUpdate: (details, point) {
                      setState(() {
                        _marker2Position = point;
                      });
                    },
                  )
                ],
              )
            ],
          ),
        ),
      ),
    );
  }
}

Platform Tested on : macOS
flutter_map: ^3.1.0
flutter_map_dragmarker: ^4.1.2

I'm not sure if this is the intended behavior or if it is not recommended to update the position onDragUpdate, but in my use case, I need the position of the marker when it's being dragged.

from flutter_map_dragmarker.

ibrierley avatar ibrierley commented on July 17, 2024

Hmm I don't think that's a good idea in general (currently), it's not currently intended for that to be updated iirc. However, what's the use case, because it provides the location of the marker, so not quite sure why you need to update it whilst it's mid drag ?

from flutter_map_dragmarker.

SheenaJacob avatar SheenaJacob commented on July 17, 2024

Ok. That makes sense. My current use case is to find the distance between two markers, and visually it would look something like this. So I need to update the position of the markers so that I can draw a line between the two points.

Screenshot 2023-05-19 at 10 04 26

from flutter_map_dragmarker.

ibrierley avatar ibrierley commented on July 17, 2024

Ok, you could try updating the point, but not calling setState on the map out of interest, see what happens. I think the problem is that when setState is called, the gesture handlers lose their dragging (That would need a bit longer to test all of that to check if I'm going mad or not :))

from flutter_map_dragmarker.

ibrierley avatar ibrierley commented on July 17, 2024

Actually, scrap that I think, as you would probably need setState to update the lines...

from flutter_map_dragmarker.

SheenaJacob avatar SheenaJacob commented on July 17, 2024

Hehe. Yea. So I can use a workaround where I just have two variables that define the same point like this:

LatLng _markerPosition = LatLng(44.1461, 9.9000);
final LatLng _initialMarkerPosition = LatLng(44.1461, 9.9000);
DragMarker(
                    point: _initialMarkerPosition,
                    width: 80.0,
                    height: 80.0,
                    offset: const Offset(0.0, -8.0),
                    builder: (ctx) => const Icon(Icons.location_on,
                        size: 50, color: Colors.black),
                    feedbackBuilder: (ctx) => const Icon(Icons.edit_location,
                        size: 50, color: Colors.black),
                    feedbackOffset: const Offset(0.0, -8.0),
                    onDragUpdate: (details, point) {
                      setState(() {
                        _markerPosition = point;
                      });
                    },
                  ),

but I don't understand why updating the value causes the gesture callbacks to stop working and also it only happens when it's dragged outside.

from flutter_map_dragmarker.

SheenaJacob avatar SheenaJacob commented on July 17, 2024

And I'll take a look at the flutter_map_line_editor. Thanks a ton : )

from flutter_map_dragmarker.

ibrierley avatar ibrierley commented on July 17, 2024

Yep, I wasn't really meaning to use that, just to try and spot why that doesn't break the dragging off screen when calling setState in comparison.

from flutter_map_dragmarker.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.