I'm trying to make a screen that the blue container is on top of the content of the grey container, but the way I'm doing the blue container is pushing the content of the grey container. How could I make the blue container when expanded to be on top of the grey one? Do I have to use stack? I couldn't understand how I can do it.
class _TestScreenState extends State<TestScreen> {
bool isExpanded = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Title'),
),
body: LayoutBuilder(
builder: (p0, p1) {
final widthScreen = p1.maxWidth;
final heightScreen = p1.maxHeight;
return Column(
children: [
Container(
color: Colors.amber,
width: widthScreen,
height: heightScreen * 0.32,
child: Column(
children: [
Row(
children: [
IconButton(
iconSize: 30,
icon: const Icon(Icons.arrow_back),
onPressed: () {},
),
],
),
],
),
),
Expanded(
child: Container(
height: heightScreen * .5,
alignment: Alignment.bottomCenter,
color: Colors.grey,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text("line 1"),
Text("line 2"),
Text("line 3"),
AnimatedContainer(
duration: Duration(milliseconds: 300),
height: isExpanded
? heightScreen - heightScreen * 0.32
: heightScreen * .2,
color: Colors.blue,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
iconSize: 30,
color: Colors.black,
icon: const Icon(Icons.ac_unit),
onPressed: () {
setState(() {
isExpanded = !isExpanded;
});
},
)
]),
],
),
),
],
),
),
),
],
);
},
),
);
}
}
Yes you have to use a Stack:
Columns and Rows only put UI elements next to each other.
Stack(
children: [
BottomWidget(),
MiddleWidget(),
TopWidget(),
]
),
If you want your TopWidget to also be at the top of the screen wrap it with an Align widget.
In your case replace TopWidget() with whatever you want to be on top (probably the blue container).
Also watch this 1 min video about the Stack widget: Stack - Flutter.
In column you should wrap all the widgets with expanded widget and provide flex accourding to the size which you need. Try the code as followed:
Column(
children: [
Expanded(flex: 2, child: Container(color:
Colors.red));
Expanded(flex: 3, child: Container(color:
Colors.green));
Expanded(flex: 1, child: Container(color:
Colors.blue));
]
);
This might solve your issue.
For overlapping Stack is the widget.
Related
I'm a beginner at app dev and I'm trying out flutter. I'm currently having a problem with positioning my background on the project that I am currently testing out.I'm following a UI kit that I am trying to copy for the purpose of practicing, but I am having problem with the UI.
I tried using stack but the whole screen is wrapped with its children and not taking up space. it looks like this:
and this is what I wanted to do:
This is the background that I wanted to put in my app, it is not literally a background or wallpaper because of its size. I just needed this to be placed at the bottom of the screen or background:
this is the code that I currently have:
import 'package:audit_finance_app/constant/theme.dart';
import 'package:audit_finance_app/widgets/widgets.dart';
import 'package:audit_finance_app/screens/homescreen.dart';
import 'package:flutter/material.dart';
import 'dart:math' as math;
class SignInPage extends StatefulWidget {
const SignInPage({super.key});
#override
State<SignInPage> createState() => _SignInPageState();
}
class _SignInPageState extends State<SignInPage> {
late List<String> inputPass;
String defaultPass = '1234';
#override
void initState() {
inputPass = [];
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: <Widget>[
const SizedBox(
width: double.maxFinite,
height: double.maxFinite,
child: Image(
image: AssetImage('assets/background.png'),
),
),
CustomScrollView(
slivers: <Widget>[
SliverAppBar(
pinned: true,
flexibleSpace: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [
AuditTheme.primaryColor,
AuditTheme.secondaryColor,
],
),
),
),
leadingWidth: 100,
leading: Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 0, 0),
child: Row(
children: const [
Expanded(
child: ImageIcon(
AssetImage('assets/logo/white_logo.png'),
),
),
Text(
'Audit',
style: TextStyle(fontSize: 20),
),
],
),
),
title: const Text('Sign In'),
centerTitle: true,
actions: [
Transform(
alignment: Alignment.center,
transform: Matrix4.rotationY(math.pi),
child: IconButton(
onPressed: () {},
icon: const Icon(Icons.sort),
),
),
],
),
SliverList(
delegate: SliverChildListDelegate(
[
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Widgets().sixedBoxHeight(50),
Column(
children: [
const CircleAvatar(
radius: 35,
backgroundImage:
AssetImage('assets/logo/audit_logo.png'),
),
Widgets().sixedBoxHeight(10),
const Text(
'Ledjoric Vermont',
style: TextStyle(fontSize: 20),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
pinIconTest(inputPass.isNotEmpty
? Colors.black
: Colors.grey),
pinIconTest(inputPass.length >= 2
? Colors.black
: Colors.grey),
pinIconTest(inputPass.length >= 3
? Colors.black
: Colors.grey),
pinIconTest(inputPass.length == 4
? Colors.black
: Colors.grey),
],
),
Card(
child: Column(
children: [
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
numPad(const Text('1'), () => inputPin('1')),
numPad(const Text('2'), () => inputPin('2')),
numPad(const Text('3'), () => inputPin('3')),
],
),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
numPad(const Text('4'), () => inputPin('4')),
numPad(const Text('5'), () => inputPin('5')),
numPad(const Text('6'), () => inputPin('6')),
],
),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
numPad(const Text('7'), () => inputPin('7')),
numPad(const Text('8'), () => inputPin('8')),
numPad(const Text('9'), () => inputPin('9')),
],
),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
const SizedBox(
width: 100,
height: 100,
),
numPad(const Text('0'), () => inputPin('0')),
numPad(
const Icon(Icons.backspace_sharp),
() => deletePin(),
),
],
),
],
),
),
],
),
],
),
),
],
),
],
),
);
}
Widget pinIconTest(Color color) {
return Padding(
padding: const EdgeInsets.all(5.0),
child: Icon(
Icons.circle,
size: 35,
color: color,
),
);
}
Widget numPad(Widget widget, void Function() function) {
return SizedBox(
width: 100,
height: 100,
child: TextButton(
style: TextButton.styleFrom(
foregroundColor: Colors.grey,
textStyle: const TextStyle(
fontSize: 30,
),
),
onPressed: function,
child: widget,
),
);
}
void inputPin(String value) {
setState(() {
inputPass.length != 4 ? inputPass.add(value) : null;
inputPass.length == 4 ? checkPass() : null;
});
print(inputPass);
}
void checkPass() {
var stringList = inputPass.join('');
if (stringList == defaultPass) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const HomeScreen(),
),
);
}
print(stringList);
}
void deletePin() {
setState(() {
inputPass.isNotEmpty ? inputPass.removeLast() : null;
});
print(inputPass);
}
}
I was missing the fact you want to place at the bottom that background, however to achieve that you can do it as the code below shows:
class SignInPage extends StatefulWidget {
const SignInPage({super.key});
#override
State<SignInPage> createState() => _SignInPageState();
}
class _SignInPageState extends State<SignInPage> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: <Widget>[
const Align(
alignment: Alignment.bottomCenter,
child: Image(
image: AssetImage('assets/background.png'),
),
),
//Other child here
],
),
);
}
}
And this is the result:
You can use the example below for the status bar. I don't know about the real problem.
You can try using this way for gradient color
SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.light,
statusBarBrightness: Brightness.dark,
statusBarGradient: LinearGradient(
colors: [Colors.red, Colors.blue],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
);
You need to wrap your image in a Positioned Widget.
Positioned(bottom: 0.0,
child: const SizedBox(
width: double.maxFinite,
height: double.maxFinite,
child: Image(
image: AssetImage('assets/background.png'),
),
),
Or you can use the alignment property of the Stack to stick everything onto the bottom. I'm not sure this is exactly what you want though.
body: Stack(
alignment: Alignment.bottomCenter,
fit: StackFit.expand,
children: <Widget>[
const SizedBox(
width: double.maxFinite,
height: double.maxFinite,
child: Image(
image: AssetImage('assets/background.png'),
),
),
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.
How can fix it?
Widget build(BuildContext context) {
final Size size = MediaQuery.of(context).size;
return Scaffold(
resizeToAvoidBottomInset: false,
body: Stack(
fit: StackFit.loose,
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
flex: 2,
child: buildSearch(),
),
Expanded(
flex: 2,
child: Container(color: Colors.green),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Frequitly Visited'),
TextButton(
onPressed: () {},
child: Text('Show More'),
),
],
),
Expanded(
flex: 6,
child: buildSites(size),
)
],
)
],
),
);
}
buildSearch() {
return Container(
child: FloatingSearchBar(
transitionCurve: Curves.easeInOutCubic,
transition: CircularFloatingSearchBarTransition(),
physics: const BouncingScrollPhysics(),
scrollPadding: EdgeInsets.zero,
builder: (context, _) => _history(),
),
);
}
Container buildSites(size) {
return Container(
color: Colors.black,
width: size.width,
);
}
Widget _history() {
List<String> listHistory = [
'Flutter',
'Python',
'Nodejs',
'Rails',
];
return ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Material(
child: Column(
children: listHistory
.map((e) => ListTile(
title: Text(e),
onTap: () {},
))
.toList(),
),
),
);
}
}
try to fixed the height of search & container, expanded only flexible part:
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
height: 100,
child: buildSearch(),
),
Container(
height: 100,
child: Container(color: Colors.green),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Frequitly Visited'),
TextButton(
onPressed: () {},
child: Text('Show More'),
),
],
),
Expanded(
child: buildSites(size),
)
],
)
You can just remove the Expanded widget wrapped around your buildSearch() widget and give a desired padding or you can also your sizedbox with a desiredHeight below and above the buildSearch()
so basically change
...
Expanded(
flex: 2,
child: buildSearch(),
),
...
to
...
Padding(
padding:EdgeInsets.only(top:20,bottom:50), // adding a suitable padding should fix it
child:buildSearch(),
)
...
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,
),
],
),
)
],
),
)
]),
);
}
}
I am trying to have a grouped button with two individuals and place them in the bottom. It is a login screen so there are other things.
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent,
body: SafeArea(
child: Center(
child: Column(
children: <Widget>[
SizedBox(
height: 75.0,
),
Text('Login'),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
color: Colors.orange,
onPressed: () {
print('login');
},
),
RaisedButton(
onPressed: () {
print('signup');
},
),
],
),
],
),
),
),
);
}
}
I've been trying with like align bottom and stuffs. But with Row(), it does not work with Center + Column widget. How can I send these two buttons to the bottom of the Center widget?
There are multiple ways to do this. One of them is to use the Spacer widget:
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent,
body: SafeArea(
child: Center(
child: Column(
children: <Widget>[
SizedBox(
height: 75.0,
),
Spacer(),
Text('Login'),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
color: Colors.orange,
onPressed: () {
print('login');
},
),
RaisedButton(
onPressed: () {
print('signup');
},
),
],
),
],
),
),
),
);
Another one would be to reverse the column and start from the bottom, but most probably the side effects are not wanted.
Also, one option would be to divide the whole screen into fixed height partitions with the help of MediaQuery.of(context).size.height value so that your buttons are always at the bottom.
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent,
body: SafeArea(
child: Center(
child: Column(
children: <Widget>[
SizedBox(
height: 75.0,
),
Text('Login'),
Expanded(
child:Container
(
alignment: Alignment.bottomCenter,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
color: Colors.orange,
onPressed: () {
print('login');
},
),
RaisedButton(
onPressed: () {
print('signup');
},
),
],
),
)
)
],
),
),
),
);
}