0

I have a Container A wrapped inside GestureDetector, which tap down to change background color to yellow, tap up to restore color to red. When the Container A is tapped, the color will be changed like this (red -> yellow -> red). But if this container A is wrapped inside a PageView with another empty Container B, tapping the Container A will only show the tap up color(red), the tap down color(yellow) will only be shown if I onLongPress the Container A, which is not expected. And I tried to remove Container B from the PageView, then the color changing of Container A will be shown correctly(red -> yellow -> red). Please help me to figure out, thanks. Here is the sample code.

class PageViewDemo extends StatefulWidget {
  @override
  _PageViewDemoState createState() => _PageViewDemoState();
}

class _PageViewDemoState extends State<PageViewDemo>
    with SingleTickerProviderStateMixin {
  PageController _pageController;
  TabController _tabController;

  static var pageOneColor;
  static var isTapped;

  @override
  void initState() {
    super.initState();
    pageOneColor = Colors.red;
    isTapped = false;
    _pageController = PageController();
    _tabController = TabController(vsync: this, length: 2);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.start,
        children: <Widget>[
          Padding(
            padding: const EdgeInsets.only(
              top: 40.0,
            ),
            child: TabPageSelector(
              controller: _tabController,
              color: Colors.transparent,
              selectedColor: Colors.blue,
            ),
          ),
          Container(
            width: MediaQuery.of(context).size.width,
            height: 400,
            child: PageView(
              controller: _pageController,
              scrollDirection: Axis.horizontal,
              children: <Widget>[
                Container(
                  color: Colors.greenAccent,
                  child: Container(
                    padding: EdgeInsets.all(50),
                    decoration: BoxDecoration(
                      border: Border.all(
                        width: 3,
                        color: Colors.black,
                      ),
                    ),
                    height: 100,
                    width: 100,
                    child: RawGestureDetector(
                      gestures: <Type, GestureRecognizerFactory>{
                        CustomButtonGestureRecognizer:
                            GestureRecognizerFactoryWithHandlers<
                                CustomButtonGestureRecognizer>(
                          () => CustomButtonGestureRecognizer(
                            onTapDown: _onTapDown,
                            onTapUp: _onTapUp,
                            onTapCancel: _onTapCancel,
                          ),
                          (CustomButtonGestureRecognizer instance) {},
                        ),
                      },
                      child: Container(
                        color: pageOneColor,
                        child: Center(
                          child: Text('Container A'),
                        ),
                      ),
                    ),
//                    child: GestureDetector(
//                      onTapDown: (detail) {
//                        /// TapDown Color isn't shown if 'Container B' is added to PageView
//                        print(detail);
//                        setState(() {
//                          isTapped = true;
//                          pageOneColor = Colors.yellow;
//                        });
//                      },
//                      onTapUp: (detail) {
//                        print(detail);
//                        setState(() {
//                          isTapped = false;
//                          pageOneColor = Colors.red;
//                        });
//                      },
//                      child: Container(
//                        color: pageOneColor,
//                        child: Center(
//                          child: Text('Container A'),
//                        ),
//                      ),
//                    ),
                  ),
                ),
                Container(
                  /// remove Container B will see the correct color changing of Container A
                  color: Colors.grey,
                  child: Center(
                    child: Text('Container B'),
                  ),
                ),
              ],
              onPageChanged: (int pageId) {
                setState(() {
                  debugPrint('pageId:$pageId');
                  _tabController.index = pageId;
                });
              },
            ),
          ),
        ],
      ),
    );
  }

  _onTapDown(TapDownDetails details) {
    print(details);
    setState(() {
      isTapped = true;
      pageOneColor = Colors.yellow;
    });
  }

  _onTapUp(TapUpDetails details) {
    print(details);
    setState(() {
      isTapped = false;
      pageOneColor = Colors.red;
    });
  }

  _onTapCancel() {
    print('tap cancelled');
    setState(() {
      isTapped = false;
      pageOneColor = Colors.red;
    });
  }
}

class CustomButtonGestureRecognizer extends OneSequenceGestureRecognizer {
  final Function onTapDown;
  final Function onTapUp;
  final Function onTapCancel;

  CustomButtonGestureRecognizer(
      {@required this.onTapDown,
      @required this.onTapUp,
      @required this.onTapCancel});

  @override
  void addPointer(PointerEvent event) {
    if (event is PointerDownEvent) {
      onTapDown(TapDownDetails());
      startTrackingPointer(event.pointer);
      resolve(GestureDisposition.accepted);
    } else {
      stopTrackingPointer(event.pointer);
    }
  }

  @override
  void handleEvent(PointerEvent event) {
    if (event is PointerDownEvent) {
      print('tap down detected');
      onTapDown(TapDownDetails());
    }
    if (event is PointerUpEvent) {
      print('tap up detected');
      onTapUp(TapUpDetails());
      stopTrackingPointer(event.pointer);
    }
    if (event is PointerCancelEvent) {
      print('tap cancel detected');
      onTapCancel();
      stopTrackingPointer(event.pointer);
    }
  }

  @override
  String get debugDescription => 'customButtonTap';

  @override
  void didStopTrackingLastPointer(int pointer) {}
}

  • It's possible that this is due to the scrolling of the `PageView` and the way you're testing it is similar to a swipe, leading to the `PageView` to recognizing a page change instead of your container recognizing a tap. Just a possibility. – Christopher Moore Jun 16 '20 at 16:43
  • I tested your example. The TabDown actually is recognized. The Problem is, that the PageView and the GestureDetector both recognize the TabDown event, and it's not clear yet if it is an horizontal swipe event or a TapEvent, so the processing gets delayed until the PageView is sure it isn't a swipe event. This happenes the moment you lift your finger. Then TapDown and TapUp is processed one after another and the color change is not noticeable. I don't really see a workaround to make this work. – ZeRj Jun 16 '20 at 16:58
  • have you tried declaring in the widget: var pageOneColor; and then assigning value in initState: pageOneColor = Colors.red ? Same for isTapped. If it is not working try to add 'static' in front of var. – Antonin GAVREL Jun 16 '20 at 17:00
  • @ Antonin GAVREL I've tried assigning value in initState and add 'static', both are not working. :( – lance kinji Jun 16 '20 at 17:53
  • Check this article with gestureArena and how to make your gesture take precedence https://medium.com/flutter-community/combining-multiple-gesturedetectors-in-flutter-26d899d008b2 – EdwynZN Jun 16 '20 at 18:25
  • @ EdwynZN It works, THX. – lance kinji Jun 17 '20 at 07:01
  • Sample code is updated to solve this issue, thanks. – lance kinji Jun 17 '20 at 07:06

1 Answers1

0

The issue here seems that you're trying to use multiple GestureDetector and it interferes with each other. What you can do here is implement a custom GestureRecognizer in a RawGestureDetector. You can follow this guide fore more details.

Omatt
  • 8,564
  • 2
  • 42
  • 144