Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GestureDetector within TabBarView - nested scrolling?

Tags:

flutter

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?

like image 798
david.mihola Avatar asked May 09 '17 11:05

david.mihola


1 Answers

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.

like image 146
Collin Jackson Avatar answered Oct 10 '22 06:10

Collin Jackson