Is there a way to have a floatingActionButton and a SpeedDial together? - flutter

I'm experimenting with SpeedDial and I wanted to know if it's possible to have a SpeedDial and a floatingActionButton in the same screen.
The next code has worked for me for having two FloatingActionButton, but when adding the speeddial, the limits of the screen go to hell (it says bottom overflowed by infinity pixels).
Widget build(BuildContext context) {
return Scaffold(
appBar: searchBar.build(context),
body: Container(),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
buildSpeedDial(),
FloatingActionButton(
onPressed: (){},
child: Icon(Icons.add),
backgroundColor: Colors.green,
),
FloatingActionButton(
onPressed: (){},
child: Icon(Icons.add),
backgroundColor: Colors.green,
),
],
)
);
}
I'm using the same buildSpeedDial() as in the example of flutter_speed_dial 1.2.5 page.
The console shows this:
Error: Cannot hit test a render box that has never been laid out.
The hitTest() method was called on this RenderBox: RenderFlex#aa804 NEEDS-LAYOUT NEEDS-PAINT:
needs compositing
creator: Column ← Container ← Positioned ← Stack ← SpeedDial ← Column ← Transform ← RotationTransition ← Transform ← ScaleTransition ← Stack ← _FloatingActionButtonTransition ← ⋯
parentData: right=0.0; bottom=0.0; offset=Offset(0.0, 0.0)
constraints: MISSING
size: MISSING
direction: vertical
mainAxisAlignment: end
mainAxisSize: max
crossAxisAlignment: end
textDirection: ltr
verticalDirection: down
Unfortunately, this object's geometry is not known at this time, probably because it has never been laid out. This means it cannot be accurately hit-tested.
If you are trying to perform a hit test during the layout phase itself, make sure you only hit test nodes that have completed layout (e.g. the node's children, after their layout() method has been called).

One solution would be to constrain the speedDial as follows:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Flutter Speed Dial')),
body: buildBody(),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
SizedBox(
height: 60,
width: 60,
child: buildSpeedDial(),
),
SizedBox(height: 10),
FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
backgroundColor: Colors.green,
),
SizedBox(height: 10),
FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
backgroundColor: Colors.green,
),
],
),
);
}
Which would give this result:

SpeedDial is using the Stack widget while building itself. So, I have a dirty solution using Stack, too:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
void main() {
runApp(MaterialApp(home: MyApp(), title: 'Flutter Speed Dial Examples'));
}
class MyApp extends StatefulWidget {
#override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> with TickerProviderStateMixin {
ScrollController scrollController;
bool dialVisible = true;
#override
void initState() {
super.initState();
scrollController = ScrollController()
..addListener(() {
setDialVisible(scrollController.position.userScrollDirection ==
ScrollDirection.forward);
});
}
void setDialVisible(bool value) {
setState(() {
dialVisible = value;
});
}
Widget buildBody() {
return ListView.builder(
controller: scrollController,
itemCount: 30,
itemBuilder: (ctx, i) => ListTile(title: Text('Item $i')),
);
}
SpeedDial buildSpeedDial() {
return SpeedDial(
animatedIcon: AnimatedIcons.menu_close,
animatedIconTheme: IconThemeData(size: 22.0),
// child: Icon(Icons.add),
onOpen: () => print('OPENING DIAL'),
onClose: () => print('DIAL CLOSED'),
visible: dialVisible,
curve: Curves.bounceIn,
children: [
SpeedDialChild(
child: Icon(Icons.accessibility, color: Colors.white),
backgroundColor: Colors.deepOrange,
onTap: () => print('FIRST CHILD'),
label: 'First Child',
labelStyle: TextStyle(fontWeight: FontWeight.w500),
labelBackgroundColor: Colors.deepOrangeAccent,
),
SpeedDialChild(
child: Icon(Icons.brush, color: Colors.white),
backgroundColor: Colors.green,
onTap: () => print('SECOND CHILD'),
label: 'Second Child',
labelStyle: TextStyle(fontWeight: FontWeight.w500),
labelBackgroundColor: Colors.green,
),
SpeedDialChild(
child: Icon(Icons.keyboard_voice, color: Colors.white),
backgroundColor: Colors.blue,
onTap: () => print('THIRD CHILD'),
labelWidget: Container(
color: Colors.blue,
margin: EdgeInsets.only(right: 10),
padding: EdgeInsets.all(6),
child: Text('Custom Label Widget'),
),
),
],
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Flutter Speed Dial')),
body: buildBody(),
floatingActionButton: Stack(
alignment: Alignment.bottomRight,
fit: StackFit.expand,
overflow: Overflow.visible,
children: [
Stack(
alignment: Alignment.bottomRight,
fit: StackFit.expand,
overflow: Overflow.visible,
children: [
buildSpeedDial(),
],
),
// Here is a FAB
Stack(
alignment: Alignment.bottomCenter,
children: [
FloatingActionButton(
onPressed: () {
print("object");
},
child: Icon(Icons.add),
backgroundColor: Colors.green,
),
],
),
// Here, one more FAB!
Stack(
alignment: Alignment.bottomLeft,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(40, 0, 0, 0),
child: FloatingActionButton(
onPressed: () {
print("object");
},
child: Icon(Icons.remove),
backgroundColor: Colors.red,
),
),
],
),
],
),
);
}
}
It will look like this:

I modified the approach of Akif to make two buttons one on top of the other. Here's the result:
floatingActionButton: Stack(
alignment: Alignment.bottomRight,
children: [
Positioned(
bottom: 70,
child: Container(
child: FloatingActionButton(
onPressed: (){
Navigator.push(context, MaterialPageRoute(builder:(context)=> FormNewActivity()));
},
child: Icon(Icons.add,color:Colors.white,size: 30),
backgroundColor: Colors.green,
)
),
),
Stack(
alignment: Alignment.bottomRight,
children: [
buildSpeedDial(),
],
),
],
),
The result looks like this:

Related

How to stack two bottom sheet in flutter?

I want to stack two bottom sheet each other in flutter as show in photo. The upper one is shown when in error state. In photo, it build with alert dialog. I want is with bottom sheet. How can I get it?
Edit:
Here is my code that I want to do. Lower bottom sheet is with pin field, autoComplete. autoComplete trigger StreamController, and then streamBuilder watch Error state and show dialog.
confirmPasswordModalBottomSheet(
BiometricAuthRegisterBloc biometricAuthRegBloc) {
showMaterialModalBottomSheet(
context: context,
builder: (BuildContext context) {
return StreamBuilder(
stream: biometricAuthRegBloc.biometricAuthRegisterStream,
builder: (context,AsyncSnapshot<ResponseObject>biometricAuthRegSnapShot) {
if (biometricAuthRegSnapShot.hasData) {
if (biometricAuthRegSnapShot.data!.messageState ==
MessageState.requestError) {
showModalBottomSheet(context: context, builder:
(BuildContext context){
return Container(
width: 200,height: 200,
child: Center(child: Text('Helllllllllo'),),);
});
}
}
return SizedBox(
width: 100,
height: 300,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
height: margin30,
),
Text(CURRENT_PIN_TITLE),
SizedBox(
height: margin30,
),
Padding(
padding: const EdgeInsets.only(
left: margin60, right: margin60),
child: PinCodeField(
pinLength: 6,
onChange: () {},
onComplete: (value) {
biometricAuthRegBloc.biometricAuthRegister(
biometricType:_biometricAuthTypeForApi,
password: value);
},
),
),
SizedBox(
height: margin30,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal:
margin80),
child: AppButton(
onClick: () {},
label: CANCEL_BTN_LABEL,
),
),
Container(
padding: const EdgeInsets.all(8.0),
margin:
EdgeInsets.symmetric(vertical: 8.0,
horizontal: 30),
decoration: BoxDecoration(
color: Colors.grey,
border: Border.all(color: Colors.black),
),
child: const Text(
FINGER_PRINT_DIALOG,
textAlign: TextAlign.center,
),
)
],
),
);
});
},
);
}
When I do like that above, I get setState() or markNeedsBuild() called during build. Error and why? Sorry for my previous incomplete question.
I am bit confused with your question but stacking two bottomsheet is just easy. You just need to call the showModalBottomSheet whenever you want it shown to user. You can check out the following implementation:
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: ElevatedButton(
onPressed: () {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return Container(
height: 500,
color: Colors.amber,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('Modal BottomSheet 1'),
ElevatedButton(
child: const Text('Show second modal 2'),
onPressed: () {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return Container(
height: 200,
color: Colors.redAccent,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('Modal BottomSheet 2'),
ElevatedButton(
child: const Text('Close BottomSheet'),
onPressed: () => Navigator.pop(context),
),
],
),
),
);
},
);
},
),
],
),
),
);
},
);
},
child: Text('Show bottom sheet 1'),
),
);
}
}
I have solution. All I need to do is, need to add WidgetBinding.insatance.addPostFrameCallback((timeStamp){showModalBottomSheet()}); in the StreamBuilder return.

Flexible making widgets overflow in Flutter

I am building a Flutter web-app, where I have a menu interface with 4 Iconbuttons and 1 MaterialButton that is the users profile, like this:
I am using Flexible to make sure there is no overflow, but if I minimise the window, the Iconbuttons overflow, like this:
Is there any way to prevent this from happening? This is my code:
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Flexible(
flex: 2,
child: IconButton(
icon: Icon(
Icons.chat_bubble_outline_rounded,
color: Colors.white,
),
onPressed: null),
),
Flexible(
flex: 2,
child: IconButton(
icon: Icon(
Icons.settings_outlined,
color: Colors.white,
),
onPressed: null),
),
Flexible(
flex: 2,
child: IconButton(
icon: Icon(
FontAwesomeIcons.wallet,
color: Colors.white,
),
onPressed: null),
),
Flexible(
flex: 2,
child: IconButton(
icon: Icon(
Icons.notifications_outlined,
color: Colors.white,
),
onPressed: null),
),
MaterialButton(
shape: CircleBorder(),
onPressed: () {
print('click');
},
child: CircleAvatar(
backgroundImage: NetworkImage(user.photoURL.toString()),
),
)
],
),
Thank you for your help!
Is this what you want to achieve?
You could achieve this with a SingleChildScrollView.
I combined the SingleChildScrollView with a ConstrainedBox to allow the spread of the icons if the screen is larger than needed.
LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: ConstrainedBox(
constraints: BoxConstraints(minWidth: constraints.maxWidth),
child: Row(...),
),
);
},
),
Full source code for easy copy-paste
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: HomePage(),
),
);
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(width: 800, child: MyBar()),
const SizedBox(height: 16.0),
Container(width: 400, child: MyBar()),
const SizedBox(height: 16.0),
Container(width: 200, child: MyBar()),
const SizedBox(height: 16.0),
Container(
width: 100,
child: SingleChildScrollView(child: MyBar()),
),
],
),
),
);
}
}
class MyBar extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(vertical: 8.0),
color: Colors.amber.shade300,
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: ConstrainedBox(
constraints: BoxConstraints(
minWidth: constraints.maxWidth,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(
icon: Icon(
Icons.chat_bubble_outline_rounded,
color: Colors.white,
),
onPressed: null),
IconButton(
icon: Icon(
Icons.settings_outlined,
color: Colors.white,
),
onPressed: null),
IconButton(
icon: Icon(
FontAwesomeIcons.wallet,
color: Colors.white,
),
onPressed: null),
IconButton(
icon: Icon(
Icons.notifications_outlined,
color: Colors.white,
),
onPressed: null),
MaterialButton(
shape: CircleBorder(),
onPressed: () {
print('click');
},
child: CircleAvatar(
child: Text('X'),
),
)
],
),
),
);
},
),
);
}
}
I don't think Flexible will solve the problem here. Instead of this I have two options for you.
1) Replace Row with ListView (also provide scrollDirection as horizontal).
This will prevent the overflow and the list will be scrollable when the screen will be resized to a smaller width. I will prefer doing this.
2) Scale all the buttons with respect to the screen width.
This will provide you a responsive layout as the buttons will resize as the screen width changes. But you need to make sure that they don't get so small that user will find it difficult to press them.

Flutter: Expanded IconButton still shows under the Container

AnimatedContainer(
...
child: Container(
child: Column(
children: [
Row(
children: [
Container(
width:60,
height:50,
color: Colors.black,
),
],
),
Expanded(
child: GestureDetector(
onTap: () {
print('what');
},
child: Container(
height: 50,
child: Column(
children: [
Expanded(
child: Text('asdf'),
),
Expanded(
child: IconButton(
icon: Icon(Icons.ac_unit),
onPressed: () {},
),
I have this AnimatedContainer widget here. My Text() widget and RaisedButton widget are hidden inside the box. They appear as the AnimatedContainer expand. However, my IconButton doesn't.
Also, Icon widget also stays like IconButton widget. How can I make it appear when the AnimatedContainer is stretched like the Text widget?
I've modified your code, hope this is what u want.
class _RowStructureState extends State<RowStructure> {
bool pressed = false;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
pressed = !pressed;
});
print(pressed);
},
child: Column(children: <Widget>[
AnimatedContainer(
height: pressed ? 120 : 50,
color: Colors.green,
duration: Duration(seconds: 1),
child: Stack(
children: [
Container(
height: 50,
width: 50,
color: Colors.black,
),
Positioned(
bottom:0,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text('asd'),
IconButton(
padding: EdgeInsets.all(0),
icon: Icon(Icons.ac_unit,),
onPressed: pressed?(){}:null,
),
],
),
)
],
),
)
]),
);
}
}

Align text in centre of screen

I am trying to use Expand widget in Column so user can tap in any position of screen to increase counter, but there is an issue in alignment of text in crossAxisAlignment and mainAxisAlignment of the column it didn't apply on Expand widget as the following
return MaterialApp(
home: Scaffold(
backgroundColor: Colors.black12,
body: SafeArea(
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: GestureDetector(
onTap: () {
setState(() {
i++;
print(i);
});
},
child: Text(
i.toString(),
style: TextStyle(fontSize: 50.0, color: Colors.blueAccent),
textAlign: TextAlign.center,
),
),
),
],
),
),
),
),
);
You went with the wrong approach because you can't Center an Expanded because Expanded takes the entire available space inside Row or Column in your case Column with single Widget inside children the GestureDetector. Here is one of a solution for what you want to achieve
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black12,
body: SafeArea(
child: Center(
child: Stack(
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: GestureDetector(
onTap: () {
setState(() => i++;);
},
),
),
],
),
Align(
alignment: Alignment.center,
child: Text(
'$i',
style: TextStyle(fontSize: 50.0, color: Colors.blueAccent),
),
),
],
),
),
),
);
}
another solution using FlatButton inside SizedBox.expand()
so you don't need to fight with a single child column.
class _MyAppState extends State<MyApp> {
int i = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Colors.black12,
body: SafeArea(
child: SizedBox.expand(
child: FlatButton(
onPressed: () {
setState(() {
i++;
print(i);
});
},
child: Text(
i.toString(),
style: TextStyle(fontSize: 50.0, color: Colors.blueAccent),
),
),
),
),
),
);
}
}

Expanded() widget not working in listview

I'm new to flutter. I'm trying to render a page whose body contains Listview with multiple widgets.
_buildOrderDetails widget in the listview is widget that is build with listview.builder() , remaining are normal widgets.
The problem is page is not being scrolled .
When the body Listview is changed to column and _buildOrderDetails is given as child to the Expanded, the listview is limited to some extent of the page height and being scrolled. But when input is focused the page is overflowed
Widget build(BuildContext context){
return ScopedModelDescendant<MainModel>(
builder: (BuildContext context, Widget child, MainModel model) {
return Scaffold(
appBar: AppBar(
title: Text('Order Details'),
actions: <Widget>[
IconButton(
onPressed: () {
model.addNewOrder();
},
icon: Icon(Icons.add),
),
BadgeIconButton(
itemCount: model.ordersCount,
badgeColor: Color.fromRGBO(37, 134, 16, 1.0),
badgeTextColor: Colors.white,
icon: Icon(Icons.shopping_cart, size: 30.0,),
onPressed: () {}
),
]
),
body: ListView(
children: [
Column(
children: [
_buildItemsTitle(),
Expanded(child: _buildOrderDetails(context, model)),
]
),
Card(
child: Column(
children:[
TextField(
decoration: InputDecoration(
labelText: 'Offer Code'
),
),
RaisedButton(
onPressed: (){},
child: Text('Apply'),
)
]
),
),
Card(child: _orderAmount(context, model),),
RaisedButton(
color: Theme.of(context).accentColor,
onPressed: (){},
child: Text('Checkout',
style: TextStyle(
fontSize: 20.0,
color: Colors.white
)
),
)
]
),);});}}
Maybe it can help someone in the future, but the trick seems to be: use ListView + LimitedBox(maxHeight) + Column ...
ListView(
padding: EdgeInsets.zero,
shrinkWrap: true,
children: [
FocusTraversalGroup(
child: Form(
key: _formKey,
child: LimitedBox(
maxHeight: MediaQuery.of(context).size.height,
child: Column(
// mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Spacer(flex: 30), // Or Expanded
// More Widgets...
Spacer(flex: 70), // Or Expanded
// ....
Try not to use expanded on growing items. If you want to cover a percentage/fractional height wrap the height with a fixed height or the full height with a container that includes box contstains, then proceed to have expanded or fixed height children. also helpful is the FracionalBox
In the example you showed there is no need for expanded, the children inside will give a content height and the SingleChildScrollView will automaticly handle scrolling based on children.
Widget build(BuildContext context) {
return ScopedModelDescendant<MainModel>(
builder: (BuildContext context, Widget child, MainModel model) {
return Scaffold(
appBar: AppBar(title: Text('Order Details'), actions: <Widget>[
IconButton(
onPressed: () {
model.addNewOrder();
},
icon: Icon(Icons.add),
),
BadgeIconButton(
itemCount: model.ordersCount,
badgeColor: Color.fromRGBO(37, 134, 16, 1.0),
badgeTextColor: Colors.white,
icon: Icon(
Icons.shopping_cart,
size: 30.0,
),
onPressed: () {}),
]),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
Column(children: [
_buildItemsTitle(),
Container(child: _buildOrderDetails(context, model)),
]),
Card(
child: Column(children: [
TextField(
decoration: InputDecoration(labelText: 'Offer Code'),
),
RaisedButton(
onPressed: () {},
child: Text('Apply'),
)
]),
),
Card(
child: _orderAmount(context, model),
),
RaisedButton(
color: Theme.of(context).accentColor,
onPressed: () {},
child: Text('Checkout',
style: TextStyle(fontSize: 20.0, color: Colors.white)),
),
],
),
),
);
});
}