I am using a package fab_circular_menu, That package is for an animated floating action button which opens up a circular menu. within it I want a widget as a menu item such that there is an icon and a text below. the icon is centered with text.
e.g.
How can I achieve this, as of now I am using the code below but it does not work well with different screen size.
FlatButton(
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100),
),
padding: EdgeInsets.only(bottom: 15),
onPressed: () async {
await _auth.signOut().then((value) {
Navigator.of(context).popUntil((route) => route.isFirst);
});
},
child: Stack(
overflow: Overflow.visible,
children: <Widget>[
Icon(Icons.power_settings_new),
Positioned(
bottom: -12,
right: -10,
child: Text('Logout'),
),
],
),
),
Another Solution which could use in many ways
Wrap(
direction: Axis.vertical,
crossAxisAlignment: WrapCrossAlignment.center,
children: <Widget>[
Icon(Icons.power_settings_new),
Text('Logout'),
],
),
You can achieve this result with Column widget instead of Stack.
Here is the example:
.....
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(Icons.power_settings_new),
Text('Logout'),
],
),
Short answer:
Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.call),
SizedBox(height: 4), // spacing between two
Text('Call Icon'),
],
)
Long answer:
Copy this class:
class TextWithIcon extends StatelessWidget {
final IconData icon;
final String text;
final double spacing;
const TextWithIcon({Key key, this.icon, this.text, this.spacing = 2}) : super(key: key);
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon),
SizedBox(height: spacing),
Text(text),
],
),
);
}
}
Usage:
TextWithIcon(
icon: Icons.call,
spacing: 6, // Space between icon and text
text: 'Call Icon',
)
You can use columns for this like:
FlatButton(
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100),
),
padding: EdgeInsets.only(bottom: 15),
onPressed: () async {
await _auth.signOut().then((value) {
Navigator.of(context).popUntil((route) => route.isFirst);
});
},
child: Column(
children: <Widget>[
Icon(Icons.power_settings_new),
SizedBox(height:10), // set height according to your needs
Text('Logout'),
],
),
),
Related
I want to make this type of UI -
Like this dialog opens when we click on "Other Details" and appears just below it. Is there any package for this? Else I will try to use stack and positioned, and will position it as accurate as I can.
You could use an ExpansionTile.
It does exactly what you are looking for.
Code sample:
Center(
child: ExpansionTile(
iconColor: Colors.red,
title: const Text('More details'),
children: [
ListTile(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text('Name'),
Text('Ankit'),
],
)),
ListTile(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text('Grade'),
Text('9th'),
],
)),
ListTile(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text('Date of birth'),
Text('21-04-2008'),
],
)),
],
),
)
Keep in mind that in the children you could put any widget, even a container, as:
EDIT
After reading your clarifying comment this is what I made:
Obviously it's a basic floating box, but you can customize it.
I used a Stack to overlap widgets and a boolean to check if you need to show or not the infobox.
bool visibile = false;
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
onPressed: () => setState(() {
visibile = !visibile;
}),
icon: const Icon(Icons.remove_red_eye)),
Stack(
alignment: Alignment.center,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: const [
ElevatedButton(
onPressed: null,
child: Text("Check In"),
),
TextField()
],
),
),
visibile
? Container(
width: MediaQuery.of(context).size.width * .8,
height: MediaQuery.of(context).size.height * .5,
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
offset: Offset.fromDirection(1, 2),
spreadRadius: 1,
blurRadius: 3,
color: Colors.grey)
],
borderRadius: const BorderRadius.all(Radius.circular(20)),
border: Border.all(
color: Colors.orange,
width: 2,
style: BorderStyle.solid)),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: SingleChildScrollView(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text(
'Academic year',
style: TextStyle(color: Colors.grey),
),
Text('2023'),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text(
'Student name',
style: TextStyle(color: Colors.grey),
),
Text('Ankit'),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text(
'Grade',
style: TextStyle(color: Colors.grey),
),
Text('9th'),
],
),
],
),
),
),
)
: Container(),
],
),
],
))
Having the button outside the Stack prevents the InfoBox to hide it and makes it so you can click it at any time.
I have no idea why, but someone with enough privileges keeps deleting my comments on this answer.
You could use the native Flutter Widget https://api.flutter.dev/flutter/material/ExpansionPanel-class.html or try a package like https://pub.dev/packages/expandable
If you Want the widget float on the other widgets, you can use OverlayEntry. There is a medium article/guide about this: Click to read.
Abstract:
first you need a global key, an overlay widget and the other variables below:
GlobalKey overlayKey = LabeledGlobalKey("button_icon");
late OverlayEntry overlayEntry;
late Size buttonSize;
late Offset buttonPosition;
bool isMenuOpen = false;
then add the global key to your button's key proprty:
key: context.read<MainViewModel>().overlayKey,
then, find the clicked button using this function:
findButton() {
RenderBox renderBox =
overlayKey.currentContext!.findRenderObject() as RenderBox;
buttonSize = renderBox.size;
buttonPosition = renderBox.localToGlobal(Offset.zero);
}
then, a builder:
OverlayEntry overlayEntryBuilder() {
return OverlayEntry(
builder: (context) {
return Positioned(
top: buttonPosition.dy + buttonSize.height * 4 / 5,
left: buttonPosition.dx - (buttonSize.width / 2) - 32,
// width: buttonSize.width,
child: YourWidgetThatWillOpenWhenYouClickTheButton(),
);
},
);
}
you can play around with the values in the Positioned widget to get the overlay where you want it.
now you can call these functions to open/close the overlay:
void openMenu(BuildContext context) {
findButton();
overlayEntry = overlayEntryBuilder();
Overlay.of(context)!.insert(overlayEntry);
isMenuOpen = !isMenuOpen;
}
void closeMenu() {
overlayEntry.remove();
isMenuOpen = !isMenuOpen;
}
like this:
IconButton(
key: context.read<MainViewModel>().overlayKey,
onPressed: () {
if (!context.read<MainViewModel>().isMenuOpen) {
context.read<MainViewModel>().openMenu(context);
} else {
context.read<MainViewModel>().closeMenu();
}
},
check the medium article for the detailed explanation.
I have modified my code. I thought I could achieve what I am willing to do but I am still having an issue. The first image is fine, but when I am adding more images, they don't display to the screen. The idea is to allow the user to click on a button to select one or several images. Then, he can tap on a second button and add one pfd file, it is like adding attachment in email.Then, if the user wants he can tap on the first button and add an other image. The list of all the documents should be displayed on the screen. I though that maybe a set State is missing somewhere. Here is the code. I do not understand where is my mistake. Thank you in advance.
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
List<PlatformFile>? _paths;
List<String> filesGB =[];
bool _loadingPath = false;
String fileExtension='';
String _fileName='';
// To access the pictures
void _openPictureFileExplorer() async {
setState(() => _loadingPath = true);
try {
_paths = (await FilePicker.platform.pickFiles(
type: FileType.media,
allowMultiple: true,
))?.files;
if (_paths != null) {
_paths!.forEach((element) {
filesGB.add(element.path.toString());
print(filesGB);
print(filesGB.length);
});
setState(() {
});
}
} on PlatformException catch (e) {
print("Unsupported operation" + e.toString());
} catch (ex) {
print('$ex');
}
if (!mounted) return;
setState(() {
_loadingPath = false;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: const Text('File Picker app'),
),
body: Center(
child: Padding(
padding: const EdgeInsets.only(left: 10.0, right: 10.0),
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 20.0),
//#############
//Display card with button to select type of document
child: Card(
child:
Container(
// color: Colors.red,
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
//Attachement
FlatButton(
onPressed: () {},
child:
InkWell(
child: Container(
// color: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment
.center,
children: [
Icon(Icons.attach_file),
Text('Attachment'),
],
)
),
onTap: () async {
fileExtension = 'pdf';
_openDocumentFileExplorer();
},
),
),
//Photo
FlatButton(
onPressed: () {},
child:
InkWell(
child: Container(
// color: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment
.center,
children: [
Icon(Icons.add_a_photo_rounded),
Text('Photo'),
],
)
),
onTap: () {
fileExtension = 'jpeg';
_openPictureFileExplorer();
},
),
),
],
),
)),
),
Builder(
builder: (BuildContext context) => _loadingPath ?
Padding(
padding: const EdgeInsets.only(bottom: 10.0),
child:const CircularProgressIndicator(),
)
: filesGB.isNotEmpty ?
Column(
children: listOfCards(filesGB),
)
:Text('Nothing to display'),
),
]),)))));
}
}
List<Widget> listOfCards(List<String> item){
List<Widget> list = <Widget>[];
ListView.builder(
itemCount: filesGB.length,
itemBuilder: (BuildContext ctxt, int index) {
return new Container(
height: 114,
child: GestureDetector(
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
elevation: 10,
child: ClipPath(
clipper: ShapeBorderClipper(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15))),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
height: 113, width: 113,
child: Image.file(File(item[i].toString()),
fit: BoxFit.fill,
width: double.infinity,),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Text(item[i]
.split('/')
.last),
),
),
],
),
),),
),
);
});
return list;
}
first of all, you don't need to use for loop for building your pictures list
just use ListView.builder
but about your problem, I think it happens because you set selected pictures in a row
then return that row as a child of your column
so your pictures align horizontally and column just show widgets in vertical aligns
in other words, your column just have one child, and its a Row
so column just show pictures as possible then you just see the first picture.
for solving this problem you should return a list of widgets in the listOfCards function
just do these simple changes and I hope your problem solved
change your function return parameter to List<Widget>
Widget listOfCards(List<String> item) {
to
List<Widget> listOfCards(List<String> item) {
then just return your list
return list;
and your column should look like this
Column(
children: listOfCards(filesGB),
)
I have find a working solution. It does what I was expecting with image. I still have a problem when I delete a record, the card is not removed. I do not find where I should use the setState. I will continue to investigate.
body: Center(
child: Padding(
padding: const EdgeInsets.only(left: 10.0, right: 10.0),
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 20.0),
//#############
//Display card with button to select type of document
child: Card(
child:
Container(
// color: Colors.red,
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
//Attachement
FlatButton(
onPressed: () {},
child:
InkWell(
child: Container(
// color: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment
.center,
children: [
Icon(Icons.attach_file),
Text('Attachment'),
],
)
),
onTap: () async {
fileExtension = 'pdf';
_openDocumentFileExplorer();
},
),
),
//Photo
FlatButton(
onPressed: () {},
child:
InkWell(
child: Container(
// color: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment
.center,
children: [
Icon(Icons.add_a_photo_rounded),
Text('Photo'),
],
)
),
onTap: () {
fileExtension = 'jpeg';
_openPictureFileExplorer();
},
),
),
],
),
)),
),
Builder(
builder: (BuildContext context) => _loadingPath ?
Padding(
padding: const EdgeInsets.only(bottom: 10.0),
child:const CircularProgressIndicator(),
)
: filesGB.isNotEmpty ?
Column(
children: getList(),//[listOfCards(filesGB)],
)
:Text('Nothing to display'),
),
]),)))));
}
}
List<Widget> getList() {
List<Widget> childs = [];
for (var i = 0; i < filesGB.length; i++) {
childs.add(
GestureDetector(
onTap: (){
print ("Pressed");
},
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
elevation: 10,
child: ClipPath(
clipper: ShapeBorderClipper(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15))),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
height: 113,width: 113,
child: fileExtension == 'pdf'?
Image.asset('assets/logo_pdf.png',
// fit: BoxFit.fill,
// width: double.infinity,
):
Image.file(File(filesGB[i].toString()),
fit: BoxFit.fill,
width: double.infinity,),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Text(filesGB[i].toString().split('/').last,//_nameOfFile,//name,
style: TextStyle(fontWeight: FontWeight.bold),),
),
),
Padding(
padding: const EdgeInsets.only(right:25.0),
child: IconButton(onPressed: (){
//delete a record and the card displaying this record
// Delete the selected image
// This function is called when a trash icon is pressed
if (filesGB.length > 1) {
filesGB.removeAt(i);
print(filesGB);
setState(() {});
}
},
icon:Icon (Icons.delete, color: Colors.red,),),
)
],
),
),
//subtitle: Text(path),
),
));}
return childs;
}
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,
),
],
),
)
],
),
)
]),
);
}
}
So I'm trying to remove the bottom margin of raisedButton so it will look like it is merged with the card below it.
here's what it looks like
here is my code
Expanded(
child: Card(
elevation: 5,
// shape:
// RoundedRectangleBorder(borderRadius: BorderRadius.circular(22)),
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Text("Apakah anak sudah bisa ... ?",
style: TextStyle(fontWeight: FontWeight.bold)),
),
],
),
Row(
children: <Widget>[
Expanded(
child: RaisedButton(
child: Text(
"Iya",
style: TextStyle(color: Colors.white),
),
color: Colors.green[300],
onPressed: () {}),
),
Expanded(
child: RaisedButton(
child: Text(
"Tidak",
style: TextStyle(color: Colors.white),
),
color: Colors.red[200],
onPressed: () {}))
],
)
],
),
),
)
or do you guys have any other alternative solution to achieve that?
You can explicitly set your Row height to match the height of your RaisedButton. Simply wrap it inside a Container and add the height attribute.
If you want to, you can access the default height by using Theme.of(context).buttonTheme.height. Theme.of returns the ThemeData used in the current context.
Here a minimal example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Container(
child: Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Test'),
Container(
height: Theme.of(context).buttonTheme.height,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
RaisedButton(
onPressed: () {},
child: Text('Yes'),
color: Colors.green,
),
RaisedButton(
onPressed: (){},
child: Text('No'),
color: Colors.red,
)
],
),
),
],
)
),
),
)),
);
}
}
I had the same problem. NikasPor's answer didnt work for me, unfortunately. The way I fixed it is by seeting the crossAxisAlignment on the row to .stretch
Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children:[ /* Buttons Here */]
)
I am trying to match this design
on tap the card should expand
I cannot use a card wrapping an expansion tile because the expansion tile is basically only one row, I was trying to follow this example
flutter_catalog
I googled a lot and could not find an example of what I am trying to achieve, the closest thing I could find on stackoverflow was this other question is there not in flutter a collapsible / expansible card?
You can absolutely use a Card wrapping an ExpansionTile. The ExpansionTile has a title property which takes a Widget so you can pass any Widget you want into it.
Messy example code, but hopefully you get the idea:
class ExpandedTile extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(15.0))),
elevation: 2,
margin: EdgeInsets.all(12.0),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ExpansionTile(
backgroundColor: Colors.white,
title: _buildTitle(),
trailing: SizedBox(),
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: <Widget>[
Text("Herzlich Willkommen"),
Spacer(),
Icon(Icons.check),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: <Widget>[
Text("Das Kursmenu"),
Spacer(),
Icon(Icons.check),
],
),
)
],
),
),
);
}
Widget _buildTitle() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
Text("MODUL 1"),
Spacer(),
Text("Mehr"),
],
),
Text("Kursubersicht"),
Row(
children: <Widget>[
Text("6 Aufgaben"),
Spacer(),
FlatButton.icon(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(15.0))),
padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0),
color: Colors.blue,
textColor: Colors.white,
onPressed: () {},
icon: Icon(Icons.play_arrow),
label: Text("Fortsetzen"),
),
],
),
],
);
}
}
produces
and when tapped