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
?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With