I need to increase the tap area for gesture detector. I tried wrapping it in a stack and added gesture detector to the top widget which is basically an empty container
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: [
_button(),
GestureDetector(
behavior: HitTestBehavior.translucent,
child: Container(
height: 80,
width: 80,
color: Colors.redAccent.withOpacity(0.5), // remove this
),
onTap: () {
print('tapped');
},
onPanUpdate: (details) => ....,
onPanEnd: (details) => ....,
)
],
);
}
Screenshot
As you can see in the screenshot I have a text which can be scaled in height. In order to do so I have added a button:
Widget _button() {
return Material(
color: Colors.grey[500],
child: Container(
alignment: Alignment.center,
child: SizedBox(
height: widget.size / 4,
width: widget.size,
),
),
);
}
But I need to increase the tap area and so the stack solution. But it's not working unless I tap in the center(right on top of the _button()). Why is that happening even when I wrapped the empty container with gesture detector?
The top button in the screenshot attached is positioned on the dash lines:
return Stack(
clipBehavior: Clip.none,
fit: StackFit.passthrough,
children: <Widget>[
child,
Positioned(
top: -25,
right: widget.item!.width / 2,
child: TopScaleButton(
widget.item!,
size: buttonSize,
canvasKey: canvasKey,
),
),
],
);
}
AFAIK, in a stack, the widget are stacked on to of each other, so you might need to switch the order of your two widgets in the stack.
Related
Minimal reproducible code
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
GestureDetector(
onTap: () => print('Black'),
child: Container(
width: 200,
height: 200,
color: Colors.black,
),
),
GestureDetector(
onTap: () => print('White'),
behavior: HitTestBehavior.translucent,
child: Container(
color: Colors.white,
width: 100,
height: 100,
),
),
],
),
);
}
Docs says:
HitTestBehavior.translucent: Translucent targets both receive events within their bounds and permit targets visually behind them to also receive events.
As I'm using HitTestBehavior.translucent for my white container (second GestureDetector) but then why clicking on it doesn't print both White and Black?
PS: I am not looking for a way to pass the touch events to the first child, it can easily be done using IgnorePointer.
onTap didn't work for me, I use onTapDown instead.
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
GestureDetector(
onTap: () => print('Black'),
child: Container(
width: 200,
height: 200,
color: Colors.black,
),
),
GestureDetector(
onTap: () => print('White'),
onTapDown: (_) { // this one
debugPrint("on White pan down");
},
behavior: HitTestBehavior.translucent,
child: Container(
color: Colors.white,
width: 100,
height: 100,
),
),
],
),
);
}
Check this out: https://github.com/flutter/flutter/issues/18450#issuecomment-397372078
translucent means that the hit testing will traverse below. However, if both widgets are listening for the same gesture, in this case Tap, only one will "win". You can tell that both are getting the pointers though by listening to onTapDown instead of onTap.
I'm using a Stack to Positioned one widget on top of an Image.asset widget, but the problem I have is that my whole Stack widget is not scrollable to see the whole content. I wrapped the Stack widget with a SingleChildScrollView but the problem still persists. If I wrap the Stack widget with a Containter and give the height: MediaQuery.of(context).size.height, it's scrollable but I can't still see the whole content of the page. Where is my mistake, that's what I'm wondering? With what widget should I wrap Stack or is there a better way for my problem? In short, I'm stacking a Column on top of an Image and I need the whole Stack widget to be scrollable so I can see the whole content of the Stack widget. Here is the code:
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey,
body: SafeArea(
child: SingleChildScrollView(
child: Stack(
overflow: Overflow.visible,
children: [
Image.asset(
'lib/modules/common/images/logo.png',
width: double.infinity,
height: 196.0,
),
Positioned(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
top: 136.0,
child: Column(
children: [
Container(), // some content inside the container
Container(), // // some content inside the container
],
),
),
],
),
),
),
);
}
Thanks in advance for the help!
if you mean to have a image on back and scrollable content at fron check this answer, if not let me know and share a prototype/image that you want.
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey,
body: SafeArea(
child: SingleChildScrollView(
child: SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height * 3,
child: Stack(
fit: StackFit.loose,
children: [
Align(
alignment: Alignment.topCenter,
child: Image.asset(
'assets/image.jpg',
width: double.infinity,
height: 196.0,
),
),
Positioned(
top: 136.0,
child: SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Column(
mainAxisSize: MainAxisSize.min,
children: List.generate(
2,
(index) => Container(
height: 100,
width: double.infinity,
color: index % 2 == 0
? Colors.amberAccent
: Colors.cyanAccent,
), // some content inside the container
// some content inside the container
),
),
),
),
],
),
),
),
),
);
}
I have encountered some problems with event handling when using ScrollView and Transform. The layout structure is like this, ScrollView and Transform exist inside Stack.
I want the ScrollView to scroll when scrolling outside the FlatButton in Container(Colors.cyan), event can penetrate to ScrollView.
Click FlatButton onPress to work. In fact, after clicking FlatButton twice, it will no longer move whether you click the initial position or the current position. The FlatButton control moves away from the initial position within the size range, the click event is no longer detected, but I did not understand. the code is as follows:
class EventListener extends StatefulWidget {
#override
_EventListenerState createState() => _EventListenerState();
}
class _EventListenerState extends State<EventListener> {
Offset offset = Offset(0, 0);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("EventListener"),
),
body: Stack(
children: <Widget>[
SingleChildScrollView(
child: Column(
children: <Widget>[
Container(
color: Colors.red,
height: 200,
),
Container(
color: Colors.teal,
height: 300,
),
Container(
color: Colors.orange,
height: 400,
)
],
),
),
Container(
color: Colors.cyan,
width: double.infinity,
height: 400,
alignment: Alignment.center,
child: SizedBox(
width: 100,
height: 100,
child: Transform.translate(
offset: offset,
child: FlatButton(
color: Colors.orange,
onPressed: () {
setState(() {
offset += Offset(50, 50);
});
print('click !');
},
child: Text("translate"),
),
),
),
)
],
),
);
}
}
This is a known difficulty with Buttons and Stacks and I would advise anyone with this kind of problem to look at this discussion on Github.
TL;DR:
When translating a widget, the area which you can tap is made of two things:
The area of the parent widget
The area of the child (the Flatbutton here)
See the picture below:
The usual solution:
Expanding the size of the parent.
Which gives us something like this:
Container(
width: double.infinity,
height: 400,
child: Transform.translate(
offset: offset,
child: SizedBox(
width: 100,
height: 100,
child: FlatButton(
onPressed: () => print('tap button'),
child: Text("translate"),
),
),
),
),
Here you can tap anywhere in the parent Container.
Solution for you
You actually wanted something a bit different: That anything BUT the button is clickable. For that you need:
a GestureDetector being the parent of the clickable area
a FlatButton with a onPressed method which does nothing
So here is the final code if we only want the blue container to be clickable:
import 'package:flutter/material.dart';
main() => runApp(MaterialApp(
home: EventListener(),
));
class EventListener extends StatefulWidget {
#override
_EventListenerState createState() => _EventListenerState();
}
class _EventListenerState extends State<EventListener> {
Offset offset = Offset(0, 0);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("EventListener"),
),
body: Stack(
children: <Widget>[
SingleChildScrollView(
child: Column(
children: <Widget>[
Container(
color: Colors.red,
height: 200,
),
Container(
color: Colors.teal,
height: 300,
),
Container(
color: Colors.orange,
height: 400,
)
],
),
),
GestureDetector(
onTap: () {
setState(() {
offset += Offset(50, 50);
});
},
child: Container(
width: double.infinity,
height: 400,
color: Colors.cyan,
alignment: Alignment.center,
child: Transform.translate(
offset: offset,
child: SizedBox(
width: 100,
height: 100,
child: FlatButton(
color: Colors.orange,
onPressed: () {},
child: Text("translate"),
),
),
),
),
)
],
),
);
}
}
Why this works
As explained previously, the parent being the cyan Container, any area in this container will make the button clickable.
Furthermore adding a GestureDetector on top of this Container allow us to capture any tap within this Container.
So finally here is what happens when you click, if you click:
Outside the cyan Container, nothing happens.
Inside the cyan Container
Outside the button, the GestureController catch the tap and makes the button move
Inside the button, the Button catches the tap, does nothing with it (empty method), and mark this tap as treated which causes it not to bubble up in the tree and therefore the GestureController gets nothing and nothing happens.
Hope this helps you and other understand the tricky way all of this works. Once you embrace it it's kinda beautiful though ;)
Eventough I made the things stated above, it didnt work for me. So I debuged for hours and I found a solution. I hope it helps to somebody.
If your parent widget is Column/Row/Wrap and if you translate your widget to top of another widget, for some reason gesture detector doesn't work. So, you need to remove your Column/Row/Wrap widget to make it work.
this blue button isn't clickable on red area, bottom side clickable
Example Code:
SingleChildScrollView(
controller: _scrollViewController,
child: Column(children: [
Container(
height: 500,
color: Colors.red,
),
Wrap(
children: [
FractionalTranslation(
translation: Offset(0.0, -0.5),
child: FlatButton(
child: Container(
color: Colors.blue,
height: 100,
),
onPressed: () {
print("INGA");
},
)),
],
),])
After the Wrap widget is removed, button becomes fully clickable again.
Even if the ModalBarrier is wrapped with a GestureDetector, it doesn't trigger taps outside the Container. But replace ModalBarrier with Container and it works.
The Container becomes a workaround but why doesn't it work with ModalBarrier?
Here's the code:
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: _onDropdownTap(),
child: Stack(
fit: StackFit.expand,
children: [
ModalBarrier(
color: Colors.black26,
),
Positioned(
left: width/2 - cardWidth/2,
top: height/2 - cardHeight/2,
width: dropdownWidth,
height: dropdownHeight,
child: Container(
color: Colors.red,
),
),
],
),
);
How can i hide specific child inside Stack widget. I tried Visibility widget to hide child but this effects whole stack widget to hidden.
My code
Visibility(
visible: visibleControl == ScreenControls.RECORDING ? true:false,
child: Stack(
alignment: AlignmentDirectional.center,
children: <Widget>[
Visibility(
visible: false,
child: SpinKitRipple(
color: kConvertLoadingDotsColor,
size: 100,
),
),
Positioned(
bottom:6,
child:Text("Hello Mongolia"),),
],
)
UPDATE:
There is a param in Visibility called maintainSize that will keep the size of the widget and therefore the Stack size too
OLD:
Visibility doesn't hide the widget, it replaces it with a zero-sized box. So if the `Stack size is inherited from their child and the only child that give it size has visibility false the stack will have 0 size.
There are several solutions to this
Use fit: StackFit.expand to expand Stack size to parent
1.1 Add a SizedBox above Stack with a fixed height if you want a fixed height
Replace Visibility widget with Opacity widget, this will hide the widget but won't be remove it. This to consider here is
opacity performance
For values of opacity other than 0.0 and 1.0, this class is relatively expensive because it requires painting the child into an intermediate buffer.
Touch inputs will still work. You can add an IgnorePointer with the param ignore that changes along the opacity one
This is just an example ! Modify as your requirement... !
List<bool> visibilityValues = [];
#override
void initState() {
super.initState();
visibilityValues = List.generate(5, (ind) => true).toList();
//change 5 with your num of widgets
}
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(15),
child: Stack(children: [
Positioned(
top: 1,
left: 1,
child: Visibility(
visible: visibilityValues[0],
child:
InkWell(
//similarly wrap all widgets with inkwell or any othe listener and change widget visibility value
onTap:(){
setState((){
visibilityValues[0] = false;
});
},
child: Container(width: 100, height: 100, color: Colors.greenAccent)),
),
),
Positioned(
top: 1,
right: 1,
child: Visibility(
visible: visibilityValues[1],
child:
Container(width: 100, height: 100, color: Colors.blueAccent),
),
),
Positioned(
bottom: 1,
left: 1,
child: Visibility(
visible: visibilityValues[2],
child:
Container(width: 100, height: 100, color: Colors.redAccent),
),
),
Positioned(
bottom: 1,
right: 1,
child: Visibility(
visible: visibilityValues[3],
child:
Container(width: 100, height: 100, color: Colors.yellowAccent),
),
),
Align(
alignment: Alignment.center,
child: Visibility(
visible: visibilityValues[4],
child:
Container(width: 100, height: 100, color: Colors.tealAccent),
),
)
]));
}