-
Notifications
You must be signed in to change notification settings - Fork 27.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Transforms break UI hit testing #18408
Comments
Here's a question on StackOverflow with the same problem -- https://stackoverflow.com/questions/50541250/transform-gesture-detector-in-flutterwith-stack |
Does "transformHitTests" property actually do anything? It seems to make no difference in my app. |
I was suspicious that this could be a Path.getBounds issue, but that doesn't seem to be the culprit here. It looks like the hit testing transformation just isn't quite working right. By default, I'm still working through a couple things - looks like there was some attempt to fix this in #16558, but looks like it still isn't quite right. |
I did set transformHitTests to false, and it didn't seem to make any
difference at all. I could find the hit position in the same place it was
when transformHitTests was true and not where the FAB was before being
transformed.
[this is not true... see below]
…On Thu, Jun 14, 2018 at 5:17 PM, Dan Field ***@***.***> wrote:
I was suspicious that this could be a Path.getBounds issue, but that
doesn't seem to be the culprit here. It looks like the hit testing
transformation just isn't quite working right.
By default, transformHitTests is set to true. If you set it to false,
you'll be able to tap your button where it originally was living (e.g. if
no transform had been applied). When it's set to true, the hit position is
getting translated, but it's not quite getting translated correctly - and
it doesn't look like the hittable area is actually getting translated
either.
I'm still working through a couple things - looks like there was some
attempt to fix this in #16558
<#16558>, but looks like it still
isn't quite right.
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#18408 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AADEtSQjytKf2uHWCHcCXN7A8U-XxNeCks5t8v0ogaJpZM4Uk1kd>
.
|
With it set to false, I'm able to make the button go by tapping where it would be if no transforms were applied... i.e. in the bottom right corner of the screen (where the red dot is): I've added some code to paint a red dot where the transform is making the hit test go to. The best I can make of it is that the logic (which looks like it should make sense) is actually mapping the physical device tap to the perspective transformed image correctly, but the hit testing logic still thinks the button is where it originally lived. It seems like this could be fixed by either transforming the point or transforming the hit test area. It's not very obvious to me as to how to do either though. |
You're right. I was fooled because it turns out that when I change transformHitTests to false, it is not good enough to do either a hot reload or a full reload. I have to stop the app from running and restart it from scratch in order to get that change to take effect. Once I did that, setting transformHitTests to false does make the hit target stay where it was before the transform took effect. If transformHitTests is true (or not specified of course) then as you rotate the scaffold slightly so that the bottom is further away then the top, the hit target does move, but it moves downward. So it is not at the same place as the FAB. If you rotate further, then the hit target is no longer on the screen. |
I have 2 Buttons inside a import 'dart:math';
import 'package:flutter/material.dart';
class TestPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: FancyFab(),
bottomNavigationBar: BottomAppBar(
shape: CircularNotchedRectangle(),
notchMargin: 4.0,
child: Row(),
elevation: 7,
));
}
}
class FancyFab extends StatefulWidget {
@override
_FancyFabState createState() => _FancyFabState();
}
class _FancyFabState extends State<FancyFab>
with SingleTickerProviderStateMixin {
bool isOpened = false;
AnimationController _animationController;
Animation<Color> _buttonColor;
Animation<double> _animateIcon;
Animation<double> _translateButton;
Curve _curve = Curves.easeInOut;
double actionRadius = 80;
@override
initState() {
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 500))
..addListener(() {
setState(() {});
});
_animateIcon =
Tween<double>(begin: 0.0, end: 1.0).animate(_animationController);
_buttonColor = ColorTween(
begin: Colors.blue,
end: Colors.red,
).animate(CurvedAnimation(
parent: _animationController,
curve: Interval(
0.00,
1.00,
curve: Curves.linear,
),
));
_translateButton = Tween<double>(
begin: 0,
end: 1,
).animate(CurvedAnimation(
parent: _animationController,
curve: Interval(
0.0,
0.5,
curve: Curves.linear,
),
));
super.initState();
}
@override
dispose() {
_animationController.dispose();
super.dispose();
}
animate() {
if (!isOpened) {
_animationController.forward();
} else {
_animationController.reverse();
}
isOpened = !isOpened;
}
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Transform(
transform: Matrix4.translationValues(
_translateButton.value * actionRadius * cos(4.5 * pi / 4),
_translateButton.value * actionRadius * sin(4.5 * pi / 4),
0.0,
),
child: FloatingActionButton(
backgroundColor: Colors.green,
onPressed: () {
//This code never fire
print('test print');
},
child: Icon(Icons.cached))),
FloatingActionButton(
backgroundColor: _buttonColor.value,
onPressed: animate,
child: AnimatedIcon(
icon: AnimatedIcons.menu_close,
progress: _animateIcon,
),
),
],
);
}
} |
Use container above Stack and give it height and width
|
I found the problem when the transformation contains out of plane rotations (i.e. around the x or y axis). The problem is that
static Offset transformPoint(Matrix4 transform, Offset point) {
final Vector3 position3 = Vector3(point.dx, point.dy, 0);
final Vector3 transformed3 = transform.perspectiveTransform(position3);
print("z_transformed = ${transformed3.z}");
return Offset(transformed3.x, transformed3.y);
} That is wrong in this case. The original child of The correct inverse transformation has to recover the "lost" transformed static Offset inverseTransformPoint(Matrix4 inverseTransform, Offset point) {
final A = inverseTransform.storage;
final Z = -(A[2]*point.dx + A[6]*point.dy + A[14])/A[10];
final Vector3 position3 = Vector3(point.dx, point.dy, Z);
final Vector3 transformed3 = inverseTransform.perspectiveTransform(position3);
print("z = ${transformed3.z} (should be zero)");
return Offset(transformed3.x, transformed3.y);
} I ignored the cases where (There is also an |
@spkersten You may be interested in reviewing PR #32192. Your feedback there would be most welcome. |
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of |
If you use a Transform widget to perform a 3D transformation, it breaks the UI (you cannot click on a button anymore). Scaling and rotateZ seem to be fine, but rotateX or rotateY seem to break things if you rotate more than a few degrees.
To reproduce, run the file https://gist.github.com/wmleler/cfc18137ce7db77d7e599e179dee4619 (change its name to main.dart, of course)
To rotate things, just touch the screen and pan around, like this (the little moving circle is the position of the user's finger):
As long as the screen is not rotated very much, you can still tap the FAB and increment the counter. Rotate a little more and the tap target is slightly away from the FAB itself and you can still find it by tapping around. But rotate more than that, and you can't even find the tap target.
The text was updated successfully, but these errors were encountered: