If two containers completely overlap each other in stack then how do we detect onTap() event on bottom when widget at top is clicked?
Use Case:
My top container is transparent. I am using this transparent container tap to animate top widgets (this top widget sticks at the bottom of the sceen). And on tap I want to animate same bottom element at position.
Additianly I implemented custom Rawgesture but i don't see gesture arena conflicts. This arena conflicts are only detected if elements are positioned such that one is parent and second is child.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Stack(children: <Widget>[
GestureDetector(
onTap: () {
print('green clicked.');
},
child: Container(
color: Colors.green,
width: 400,
height: 400,
),
),
GestureDetector(
onTap: () {
print('red clicked.');
},
child: Container(
color: Colors.red,
width: 200,
height: 200,
),
),
]),
);
}
}
In this above example When I click on red container it will print red clicked but there is some part of green container is also present. I want to know if both events can be called?
Thanks.
AFAIK, there is no direct way to know if there is an underlying widget where user has tapped. However, I ran your code of red and green containers and achieved it with onTapDown event
Simplest solution (without onTapDown) ::
Added a following function :
void onGreenClick()
{
print("Green clicked");
}
Changed Green's onTap handler as :
onTap: onGreenClick,
And added onGreenClick() in Red's onTap as :
onTap: () {
print('red clicked.');
onGreenClick();
},
However, as you guess this will work only when the widgets are completely overlap on each other. In case, there's some area where red container is present but not green then above would fail. To, achieve that, you use onTapDown as below :
Add onTapDown handler to both red and green (under GestureDetector) as :
onTapDown: (TapDownDetails details) => onTapDown(context, details),
where the handler function is :
void onTapDown(BuildContext context, TapDownDetails details) {
print('${details.globalPosition}');
final RenderBox box = context.findRenderObject();
final Offset localOffset = box.globalToLocal(details.globalPosition);
setState(() {
posx = localOffset.dx;
posy = localOffset.dy;
});
if (posx < 100.0 && posy < 100.0) onGreenClick();
}
Now, if you see the last line, we call onGreenClick only on the specific x,y-coordinates.
Thus the end result is you get "Green click" in the debug output window even on clicking on red.
The entire code is available here
Related
I'm currently making a 3-slides long Intro Slider for my application, using the Flutter package intro_slider.
Everything works flawlessly and looks perfect, but since I decided to use a custom png image as my backgroundImage, it seems that the package shows the image with extremely darker colors.
In the image I've attached I'm showing the effective problem. The one on top (using centerWidget to show it) is the expected image that should show with the exact same colors.
On the back there is the backgroundImage, darkened automatically.
I couldn't find anyone else talking about this issue and hope someone found a way to "solve" it.
Is there a way to stop the darkening of the image?
Thanks in advance.
I have currently tried:
Changing image extensions and trying again.
Remove everything but the backgroundImage (I thought that the image was darkened because of the items on top of it)
Here is the code I wrote for the slides:
Main
void main() {
runApp(const MyApp());
}
// Main
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Goals App',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: IntroScreen(),
);
}
}
IntroScreen
class IntroScreen extends StatefulWidget {
const IntroScreen({Key? key}) : super(key: key);
#override
IntroScreenState createState() => IntroScreenState();
}
class IntroScreenState extends State<IntroScreen> {
List<Slide> slides = [];
#override
void initState() {
super.initState();
slides.add(Slide(
backgroundImage: 'assets/images/intro_first_slide.png',
centerWidget:
Image(image: AssetImage('assets/images/intro_first_slide.png')),
));
}
Widget renderNextBtn() {
return const Icon(
Icons.navigate_next,
color: Colors.white,
size: 35.0,
);
}
Widget renderDoneBtn() {
return const Icon(
Icons.done,
color: Colors.white,
size: 35.0,
);
}
Widget renderSkipBtn() {
return const Icon(
Icons.skip_next,
color: Colors.white,
size: 35.0,
);
}
#override
Widget build(BuildContext context) {
return IntroSlider(
// List Slides
slides: slides,
// Skip button
// renderSkipBtn: renderSkipBtn(),
// Next button
renderNextBtn: renderNextBtn(),
// Done button
renderDoneBtn: renderDoneBtn(),
// Dot indicator
colorDot: Colors.white,
colorActiveDot: Colors.grey[300],
sizeDot: 13.0,
// show/hide status bar
hideStatusBar: true,
backgroundColorAllSlides: Colors.grey,
// Scrollbar
verticalScrollbarBehavior: scrollbarBehavior.HIDE,
);
}
}
Maybe this is what you are looking for?
https://pub.dev/packages/intro_slider#slide-object-properties all the way down at the bottom of the properties list:
backgroundBlendMode BlendMode? BlendMode.darken Background tab image filter blend mode
probably like so
slides.add(
Slide(
backgroundImage: 'assets/images/intro_first_slide.png',
centerWidget: Image(
image: AssetImage('assets/images/intro_first_slide.png'),
),
backgroundBlendMode: BlendMode.srcOver // <- try this
),);
Let's say I have a base page in a Material App.
The basepage only has one widget, a scaffold.
The scaffold contains an appbar, that is to remain constant through the app, in every page.
The scaffold also contains a body, which should be overriden by the pages that extend the base to display their contents.
How can I go about to do this?
Thanks for the help!
You could create a globally accessible page like this:
base_page.dart
import 'package:flutter/material.dart';
class BasePage extends StatelessWidget {
/// Body of [BasePage]
final Widget body;
const BasePage({#required this.body, Key key})
: assert(body != null),
super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// Your appbar content here
),
body: body,
);
}
}
And when you want to use it, just provide the body to the new class like this:
main.dart
import 'package:flutter/material.dart';
import 'base_page.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BasePage(
// This is where you give you custom widget it's data.
body: Center(child: Text('Hello, World')),
);
}
}
One way to do this is to create a variable holding the current route in your StatefulWidget.
Widget currentBody = your initial body ;
and then change that variable whenever you want to switch the body using setState:
SetState(() { currentBody = your new body widget }) ;
and in your scaffold after the appbar you put !
body : currentBody ;
You have many ways to do this, one is to use the Bloc package, but another way is to use a Bottom Navigation Bar](https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html)
The bottom navigation bar consists of multiple items in the form of text labels, icons, or both, laid out on top of a piece of material. It provides quick navigation between the top-level views of an app. For larger screens, side navigation may be a better fit.
A bottom navigation bar is usually used in conjunction with a Scaffold, where it is provided as the Scaffold.bottomNavigationBar argument.
I provided an example in my answer for transparent appbar, of course you do not need your appbar to be transparent.
class HomePageState extends State<Homepage> {
List<Widget> widgets = [Text("haha"), Placeholder(), Text("hoho")]; // as many widgets as you have buttons.
Widget currentWidget = widgets[0];
void _onItemTapped(int index) {
setState(() {
NavigationBar._selectedIndex = index;
currentWidget = widgets[index];
});
}
#override
Widget build(BuildContext context) {
return return MaterialApp(
home: Scaffold(
extendBody: true, // very important as noted
bottomNavigationBar: BottomNavigationBar(
currentIndex: NavigationBar._selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
backgroundColor: Color(0x00ffffff), // transparent
type: BottomNavigationBarType.fixed,
unselectedItemColor: Colors.blue,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(Icons.grade),
title: Text('Level'),
),
[...] // remaining in the link
),
body: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: ExactAssetImage("assets/background.png"), // because if you want a transparent navigation bar I assume that you have either a background image or a background color.
fit: BoxFit.fill
),
),
child: currentWidget
),
),
);
}
}
...
The Bloc architecture is harder to understand, you will need to read documentation and try tutorials, but it is also very interesting to implement.
I don't get any errors upon running, I just get a blank page. I guess it has to do with the layout arrangement, but that's just a guess.
I switched the class to statful in the scaffold body, so I can use setState(){} under the flatButton. I don't know why nothing is showing on the screen though, because I would assume at least the AppBar would be on the screen. Can the body extend over the AppBar?
import 'package:flutter/material.dart';
import 'dart:math';
int colorNumber = 1;
void main()=> runApp(StructureBuild());
class StructureBuild extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text('Random Color Generator',
style: TextStyle(color: Colors.white)),
backgroundColor: Colors.blue,
),
body: BodyBuild()
));
}
}
class BodyBuild extends StatefulWidget {
#override
_BodyBuildState createState() => _BodyBuildState();
}
class _BodyBuildState extends State<BodyBuild> {
int changeColor(int colorNumber) {
return colorNumber = Random().nextInt(245) + 1;
}
#override
Widget build(BuildContext context) {
return Expanded(child:
FlatButton(color: Color(colorNumber),
onPressed: () {
setState(() {
changeColor(1);
});
}, child: Text('press anywhere'),));
}
}
First of all, I recommend checking the documentation of Flutter for using color const. Color const has color from the lower 32 bits of an int. 1-255 values are very rare values for Color. I suggest you find a hexadecimal value by examining the link I gave. https://api.flutter.dev/flutter/dart-ui/Color-class.html .
Also, you can give an integer value. But it should be a huge number to change the color of the Flatbutton. For Example, 4123423412341242342 would change to color kind of turquoise blue.
In my app the menu have items with "cut-off" times. Once the cut-off time is reached, the item turns red and becomes disabled with a note stating "Cut-off time missed".
This works fine except that for a user watching the menu when the cutoff time passes the display does not update dynamically. Which in not a major issue for my current app, if somebody wanted to sit and watch the menu and notice that it only updates when you close and re-open the menu, I don't care.
But as a learning exercise it would be good if I could make it so that the menu items gets rebuilt once the time is passed.
I however do not want to turn the large Menu tree into a stateful widget. Doing this with a stateful widget is easy enough and the minimal example below (Based off of the flutter default app) shows the desired effect. There are two fields which are updated periodically (on every second) using a timer which just calls setState every second.
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
DateTime _markedTime;
// Current Time plus a few seconds....
void _markTheTime() {
setState(() {
_markedTime = DateTime.now().add(Duration(seconds: 15));
});
}
// For the purpose of this test we won't use the intl package.
String hhmmssFromDateTime(DateTime tm) {
if (tm == null) {
return null;
}
return '${zeroPadded(tm.hour)}:${zeroPadded(tm.minute)}:${zeroPadded(
tm.second)}';
}
String zeroPadded(int number) => number.toString().padLeft(2, '0');
String get markedTime => hhmmssFromDateTime(_markedTime);
String get currentTime => hhmmssFromDateTime(DateTime.now());
Timer rebuildTimer;
#override
void initState() {
rebuildTimer = Timer.periodic(Duration(seconds: 1), (timer) {
setState(() {
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_markedTime == null
? Text('Mark the time with the FAB...')
: Text(
'Marked Time: $markedTime',
style: Theme
.of(context)
.textTheme
.bodyText1
.copyWith(
color: DateTime.now().isAfter(_markedTime)
? Colors.red
: Colors.blue),
),
Text(
'Current Time: $currentTime',
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _markTheTime,
tooltip: 'Set',
child: Icon(Icons.lock_clock),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
I believe that the solution lies in a StatefulBuilder wrapped around the Column, in stead of a stateful Widget. But I don't understand StatefulBuilders even though they look really handy.
Also I wonder whether it would be possible to wrap another one around the FloatingActionButton to say disable the button once it has been tapped until the marked time expires.
I think what I want to achieve ultimately is a way to cause various parts of the widget tree to rebuild independantly of one another. So for example using separate timers, the Fab should rebuild at the time when the timer expires, while another periodic timer should update the "Time display" every second.
Use the timer_builder package. An example:
#override
Widget build(BuildContext context) {
return TimerBuilder.scheduled(
[cutoffTime],
builder: (context) {
...
}
);
}
I have a stack in a Flutter app, that stacks multiple images on top of each other. They are all of the same width and height, and have transparent backgrounds.
Individually, they look like this:
When they overlap, they look like this:
I need to make the visible part of each picture clickable. I do not want any interaction with the transparent part of any image. I've tried using GestureDetector, but since all the images are of the same size, it isn't working too well. How do I achieve this?
Circle the borders of the picture in any vector graphics editor, I used figma.com, it's free.
Save it as svg file, open it and copy path from svg.
Convert svg paths to Flutter Paths, I've used the path_drawing package.
Use custom clipper to clip image by path.
Unfortunately, path_drawing package ignores the beginning of the path. So you need to add it, by adding offset.
Add GestureDetector.
import 'package:flutter/material.dart';
import 'package:path_drawing/path_drawing.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
body: SafeArea(
child: MyHomePage(),
),
),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String clicked = '';
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
_getClippedImage(
clipper: _Clipper(
svgPath: svgCarPath,
offset: Offset(66, 157),
),
image: 'assets/image.png',
onClick: _handleClick('car'),
),
_getClippedImage(
clipper: _Clipper(
svgPath: svgManPath,
offset: Offset(115, 53),
),
image: 'assets/image.png',
onClick: _handleClick('man'),
),
Positioned(
child: Text(
clicked,
style: TextStyle(fontSize: 30),
),
bottom: 0,
),
],
);
}
void Function() _handleClick(String clickedImage) {
return () => setState(() {
clicked = clickedImage;
});
}
Widget _getClippedImage({
_Clipper clipper,
String image,
void Function() onClick,
}) {
return ClipPath(
clipper: clipper,
child: GestureDetector(
onTap: onClick,
child: Image.asset('assets/image.png'),
),
);
}
}
class _Clipper extends CustomClipper<Path> {
_Clipper({this.svgPath, this.offset = Offset.zero});
String svgPath;
Offset offset;
#override
Path getClip(Size size) {
var path = parseSvgPathData(svgPath);
return path.shift(offset);
}
#override
bool shouldReclip(CustomClipper oldClipper) {
return false;
}
}
const svgCarPath =
'M35 13.7742L46.9628 1.52606L58.8398 5.97996V17.1147L111.544 13.7742L117.111 50.8899L109.688 55.715C108.575 61.2823 103.75 72.417 93.3574 72.417C82.965 72.417 80.4751 64.3753 80.4751 59.4266C68.1032 55.5913 53.5355 53.8592 39.5397 57.5708C35.0128 76.8252 14.4397 76.0591 12.0741 55.715H0.939362V26.7647L12.0741 17.1147L35.8281 13.7742Z';
const svgManPath =
'M50.2647 19.9617C50.6461 5.85163 47.5952 0.703364 38.2521 0.703369C32.0776 2.87051 31.0217 6.36354 30.625 14.0016C30.625 14.0016 27.9555 28.1424 30.625 32.8584C33.2945 37.5744 42.1784 35.788 39.3961 40.7456C36.6138 45.7032 27.9555 63.6268 27.9555 63.6268H22.6165C14.7864 70.572 19.1843 79.9011 12.1293 88.7962C3.01255 100.291 -0.77319 103.733 0.879345 106.911L8.12508 109.199L19.1844 96.8046L12.1293 120.258L15.9428 123.499L22.6165 121.402L32.7224 97.9487L39.3961 104.622C36.5995 110.597 32.2267 122.088 37.108 120.258C43.2097 117.97 54.2865 120.258 66.0909 113.394C75.3267 28.4915 49.8834 34.0719 50.2647 19.9617Z';