GestureDetector within TabBarView - nested scrolling?

flutter

Solution

I think Flutter's pan gesture recognizer should probably yield to the scale gesture recognizer if it detects that a second pointer is down and a scale gesture recognizer is active. Feel free to file an issue for this if you agree.

In the meantime, you can "disable" the `PageView`'s pan gesture recognizer by putting an opaque `GestureRecognizer` on top of it and forwarding the scale events to your State.

import 'package:flutter/material.dart';
import 'package:vector_math/vector_math_64.dart';

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  MyHomePage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return new DefaultTabController(
      length: 2,
      child: new Scaffold(
        appBar: new AppBar(
          bottom: new TabBar(
            tabs: [
              new Tab(text: 'foo'),
              new Tab(text: 'bar'),
            ],
          ),
        ),
        body: new ScalableTabBarView()
      ),
    );
  }
}

class ScalableTabBarView extends StatelessWidget {

  final List<GlobalKey<ScaledState>> keys = <GlobalKey<ScaledState>>[
    new GlobalKey<ScaledState>(),
    new GlobalKey<ScaledState>(),
  ];

  Widget build(BuildContext context) {
    return new Stack(children: [
      new TabBarView(
        children: [
          new Center(
            child: new Scaled(
              child: new FlutterLogo(),
              key: keys[0],
            ),
          ),
          new Center(
            child: new Scaled(
              child: new FlutterLogo(),
              key: keys[1],
            ),
          ),
        ],
      ),
      new GestureDetector(
        behavior: HitTestBehavior.opaque,
        onScaleStart: (ScaleStartDetails details) {
          keys[DefaultTabController.of(context).index].currentState.onScaleStart(details);
        },
        onScaleUpdate: (ScaleUpdateDetails details) {
          keys[DefaultTabController.of(context).index].currentState.onScaleUpdate(details);
        },
        onScaleEnd: (ScaleEndDetails details)
        {
          keys[DefaultTabController.of(context).index].currentState.onScaleEnd(details);
        }
      ),
    ],
    );
  }
}

class Scaled extends StatefulWidget {
  Scaled({ Key key, this.child }) : super(key: key);

  final Widget child;
  State createState() => new ScaledState();
}

class ScaledState extends State<Scaled> {

  double _previousScale;
  double _scale = 1.0;

  void onScaleStart(ScaleStartDetails details) {
    print(details);
    setState(() {
      _previousScale = _scale;
    });
  }
  void onScaleUpdate(ScaleUpdateDetails details) {
    print(details);
    setState(() {
      _scale = _previousScale * details.scale;
    });
  }
  void onScaleEnd(ScaleEndDetails details) {
    print(details);
    setState(() {
      _previousScale = null;
    });
  }
  @override
  Widget build(BuildContext context) {
    return new GestureDetector(
        child: new Transform(
          transform: new Matrix4.diagonal3(new Vector3(_scale, _scale, _scale)),
          alignment: FractionalOffset.center,
          child: widget.child,
        ),
    );
  }
}

Handling the scale gestures outside the `TabBarView` can also be used to remember the scaled state of the tabs instead of having them reset to 1.0 when when they're offscreen.

Problem

I have a `TabBarView` where each tab contains an `Image` wrapped in a `GestureDetector` for zooming and panning. If it matters, this is how I am currently implementing the zooming: How do I pan and zoom an image?. Now I have the problem that zooming only works when the pinching movement is very vertical. If I am more than a little bit off, the touch events are immediately used for swiping to the next tab, even though I am using both fingers. And I suppose that horizontal panning wouldn't work at all. Is there any mechanism in Flutter that would allow the inner `GestureDetector` to intercept certain touch events (but not others, and not always) before feeding them to the `TabBarView`?

Original source

Related problems