10

I have a ListView, at the top of which I have a map, I want the map to scroll out of view when the ListView is scrolled, but I also want the user to be able to interact with the map. So scrolling should only happen when the user scrolls on the other ListView widgets and not when they scroll on the map, then I want the gesture to be applied directly to the map. Currently though, when the user scrolls on the map, it scrolls the whole ListView.

I have tried the other suggestion that I have come across on here In Flutter, how can a child widget prevent the scrolling of its scrollable parent? I added a GestureDetector as suggested in the answer to the post above wrapping the map container in the sample below, however this just blocked scrolling of both the ListView and the Map when scrolling on the map. Video link https://i.stack.imgur.com/3Ngqw.jpg

Here is the widget that is returned by my build method. This code depends on the google_maps_flutter plugin.

Container(
  height: MediaQuery.of(context).size.height,
  child:
  ListView.builder(
    itemCount: 12 + 1,
    itemBuilder: (context, index) {
      if (index == 0) return GestureDetector(
        onVerticalDragUpdate: (_){},
        child: Container(
          height: MediaQuery.of(context).size.height / 2,
          child: GoogleMap(initialCameraPosition: initalPosition),
        ),
      );
      else return ListTile(title: Text("$index"),);
    }
  )
),

I had hoped the map would capture gestures but it doesn't, the listview which contains it captures all. Could anyone suggest how I could force all gestures for this item in the list to be passed directly to the map, and still have the list scroll when other items in the list are scrolled?

diarmuid
  • 324
  • 4
  • 14

2 Answers2

31

The accepted answer (Eset Mehmet's answer was marked as excepted as of time of writing) is way to complicated and in my case it did not even work! It provided left-to-right scrolling, panning, and scaling, however top-to-bottom scrolling still scrolled the ListView.

A real solution is very simple, since GoogleMap by default has those gesture detectors. You must just specify that gesture detection must be prioritized by GoogleMap and not ListView. This is achieved by giving GoogleMap object an EagerGestureRecognizer in the following way for example.

ListView(
  children: <Widget>[
    Text('a'),
    Text('b'),
    GoogleMap(
      ...,
      gestureRecognizers: {
        Factory<OneSequenceGestureRecognizer>(
          () => EagerGestureRecognizer(),
        ),
      },
    ),
  ],
)

In this way, all gestures that happen on or over the GoogleMap object will be prioritized by GoogleMap instead of any other widget.

campovski
  • 2,979
  • 19
  • 38
  • 1
    Thanks @campovski, this 5-line solution works great for me. I can scroll around the ListView and then I can scroll inside the map when I touch the map. – xilex Jul 05 '20 at 02:55
  • @eluong Glad it helped, this was a nightmare for me, so I decided to share my solution. – campovski Jul 05 '20 at 17:36
  • 1
    @campovski Thanks! This is what I was looking for – Yunus Kulyyev Aug 19 '20 at 22:21
  • 1
    No need for the EagerGestureRecognizer, try this solutoin https://stackoverflow.com/questions/54280541/google-map-in-flutter-not-responding-to-touch-events With this solution you can still use the Pageview. Eager recognizer will not allow Pageview scroll – giorgio79 Jan 28 '21 at 16:08
9

Edit: campovski 's answer down below is the updated answer.

Depreciated Answer:

  • Wrap everything with ListView if you want to move out GoogleMap widget from the screen when scrolling.

  • Override ListView scrolling phyics with GoogleMap gestureRecognizers.

  • Disable ListView.builder scrolling physics due to conflict between ListView physics.

First import the dependencies:

import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';

build method:

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView(
        children: <Widget>[
          SizedBox(
            height: MediaQuery.of(context).size.height / 2,
            child: GoogleMap(
              initialCameraPosition:
                  CameraPosition(target: LatLng(41, 29), zoom: 10),
              gestureRecognizers: Set()
                ..add(
                    Factory<PanGestureRecognizer>(() => PanGestureRecognizer()))
                ..add(
                  Factory<VerticalDragGestureRecognizer>(
                      () => VerticalDragGestureRecognizer()),
                )
                ..add(
                  Factory<HorizontalDragGestureRecognizer>(
                      () => HorizontalDragGestureRecognizer()),
                )
                ..add(
                  Factory<ScaleGestureRecognizer>(
                      () => ScaleGestureRecognizer()),
                ),
            ),
          ),
          ListView.builder(
            physics: const NeverScrollableScrollPhysics(),
            shrinkWrap: true,
            itemCount: 12,
            itemBuilder: (context, index) {
              return ListTile(
                title: Text("$index"),
              );
            },
          )
        ],
      ),
    );
  }
Mehmet Esen
  • 6,156
  • 3
  • 25
  • 44
  • Thank you for the answer! Am I right in saying that the ListView will now not scroll at all? Sorry, on rereading my initial post I realize that I didn't make it clear that I I do still want the ListView to scroll if the user scrolls on any other part of the list (other than the map) – diarmuid May 18 '19 at 11:53
  • 1
    @diarmuid it's exactly what you want, try it :) – Mehmet Esen May 18 '19 at 11:55