Flutter: Content hidden by phones bottom Navigation bar - flutter

In my register screen, part of the content(at the bottom) is hidden by the phone's bottom navbar. The content is only visible when I close the bottom navbar.
What I want to achieve is, whenever the bottom navbar is displayed on the phone, I want content that is hidden by it to be pushed upwards for visibility and whenever the navbar is removed from sight, I want the content to remain at it's position.
Here is my code.
class Body extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SizedBox(
width: double.infinity,
child: Padding(
padding:
EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(20)),
child: SingleChildScrollView(
child: Column(
children: [
Text(
"Register Account",
style: headingStyle,
),
Text(
"Complete your details or continue \nwith social media.",
textAlign: TextAlign.center,
),
SizedBox(height: SizeConfig.screenHeight * 0.05), // 5%
SignUpForm(),
SizedBox(height: SizeConfig.screenHeight * 0.03), // 3%
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
SocialCard(
icon: "assets/icons/google-icon.svg",
press: () {},
),
SocialCard(
icon: "assets/icons/facebook-2.svg",
press: () {},
),
SocialCard(
icon: "assets/icons/twitter.svg",
press: () {},
)
]),
SizedBox(height: getProportionateScreenHeight(15)),
Text( <--- HIDDEN FROM VIEW
"By continuing you confirm that you agree with our Term and Condition",
textAlign: TextAlign.center,
)
],
),
),
),
);
}
}
// Get Screen Size
class SizeConfig {
static MediaQueryData _mediaQueryData;
static double screenWidth;
static double screenHeight;
static double defaultSize;
static Orientation orientation;
void init(BuildContext context) {
_mediaQueryData = MediaQuery.of(context);
screenWidth = _mediaQueryData.size.width;
screenHeight = _mediaQueryData.size.height;
orientation = _mediaQueryData.orientation;
}
}
// Get the proportionate height as per screen size
double getProportionateScreenHeight(double inputHeight) {
double screenHeight = SizeConfig.screenHeight;
// 812 is the layout height that designer use
return (inputHeight / 812.0) * screenHeight;
}
Visual of the problem

You can try implementing SafeArea, which takes care of this. This widget acts as a padding for the phones that have this kind of issue with unreachable content. It was originally thought for the iPhones without touchId at first, but if I'm not wrong fixes this problem on Androids as well.
This is the link to the documentation: https://api.flutter.dev/flutter/widgets/SafeArea-class.html
You can wrap your containers (And your scaffold as well), or your widgets individually, as follow:
SafeArea(
minimum: const EdgeInsets.all(15.0),
child: Text('Your Widget'),
)
In your case I would wrap your entire Scaffold into a SafeArea this way:
SafeArea(
child: Scaffold(
...
)
)

Wrap your entire widget inside a SafeArea
This would ensure additional padding is put, to account for System-Based intrusions like the Status Bar, Nav Bar, Notch, etc.

Related

How to fix a button at bottom of a single child scrollview with a list

I have a SingleChildScrollView and inside it I have a list with some cards, that you can remove ou add more. I need to fix an add button at the bottom of the screen, when the card list is not scrollable yet, but when the card list increase size and the scrollview is able to scroll now (to see all the content), the button must follow the list and not keep fixed at the bottom anymore.
For now, what I did to solve this, was check the scroll view every time that a card is added ou removed, if I checked that the screen is now scrollable or not scrollable I change some properties of my build widget:
SingleChildScrollView(
controller: _scrollController,
physics: AlwaysScrollableScrollPhysics(),
child: Container(
height: isNotScrollable
? _pageSize - (_appBarSize + _notifySize)
: null,
padding: const EdgeInsets.symmetric(
horizontal: Constraints.paddingNormal),
child: Column(
.....
and after the list render I create the button like this
isNotScrollable
? Expanded(
child: Container(),
)
: Container(),
CVButton(
color: Palette.white,
Basically, my idea is: if the screen is not scrollable yet (the list content fits in the screen size) I will set a height to the container inside scrollview and add a Expanded() widget before the add button (so the button will stay in the bottom of the container), but if the screen is scrollable (the list content not fits inside the screen size) so I remove the container height and the Expanded widget, then the button will follow the list now as normally.
I don't know if this is the better way to deal with that, I want to know if there is some way to do this without this 'dinamic' way that I am doing, only with fixed widgets and not changing the widget according to the state of the scrollview.
An example when the list becomes scrollable and the button will keep at list bottom
Here the list is not scrollable yet but the button must be at the screen bottom and not list bottom
(I dont wanna use bottomNavBar)
Anyone has any idea how I can solve this?
I have a solution for this. check the code bellow. I added some buttons to add or remove cards. The main trick is to use constraints like minHeight.
import 'package:flutter/material.dart';
class BottomButton extends StatefulWidget {
#override
_BottomButtonState createState() => _BottomButtonState();
}
class _BottomButtonState extends State<BottomButton> {
List<Widget> cards = [];
#override
Widget build(BuildContext context) {
var appBar2 = AppBar(
actions: [
IconButton(
icon: Icon(Icons.add),
onPressed: () {
_addCard();
}),
IconButton(
icon: Icon(Icons.remove),
onPressed: () {
_removeCard();
}),
],
);
return Scaffold(
appBar: appBar2,
body: Container(
height: MediaQuery.of(context).size.height -
(MediaQuery.of(context).padding.top + appBar2.preferredSize.height),
alignment: Alignment.topCenter,
child: ListView(
primary: true,
children: [
Container(
constraints: BoxConstraints(
minHeight: MediaQuery.of(context).size.height -
(MediaQuery.of(context).padding.top +
appBar2.preferredSize.height),
),
alignment: Alignment.topCenter,
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
constraints: BoxConstraints(
minHeight: MediaQuery.of(context).size.height -
(MediaQuery.of(context).padding.top +
appBar2.preferredSize.height +
50),
),
alignment: Alignment.topCenter,
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
children: cards,
),
),
ElevatedButton(
child: Text('this is a button'),
onPressed: () {},
),
],
),
)
],
),
),
);
}
void _addCard() {
Widget card = Card(
child: Container(
height: 100,
color: Colors.red,
padding: EdgeInsets.all(2),
),
);
setState(() {
cards.add(card);
});
}
void _removeCard() {
setState(() {
cards.removeLast();
});
}
}

How to draw widget on available height in flutter

I have a widget that needs to draw on the remaining height where that widget needs to be drawn in between the top and bottom widget(Bottom widget drawn with bottom center attribute).
Ex:
Container() -- The First container almost take half screen
Container() -- The Second container draw on the bottom of the screen eg: Consider widget as Button
Container() -- The Third Container needs to be drawn in between two containers.
The Expanded widget would work for this scenario where we need to wrap middle widget with it.
Eg:
Expanded(
child: Container(
...
),
),
I know a better method using MediaQuery.of(), where you just put the percentage of the space available that a widget will cover:
final mediaQuery = MediaQuery.of(context);
const SizedBox(height: mediaQuery.size.heigh * [percentage of the screen]
But this way includes AppBar space too. So if you want to exclude it, you should do this:
Create appBar variable:
final PreferredSizeWidget appBar = AppBar(
title: const Text('Personal expensense'),
);
Subtract mediaQuery.size with appBar variable size:
final availableHeight = mediaQuery.size.height -
appBar.preferredSize.height -
mediaQuery.padding.top;
A full example:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final PreferredSizeWidget appBar = AppBar(
title: const Text('Personal expensense'),
);
final mediaQuery = MediaQuery.of(context);
final availableHeight = mediaQuery.size.height -
appBar.preferredSize.height -
mediaQuery.padding.top;
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(
height: availableHeight * 0.4,
child: Container(),
),
SizedBox(
height: availableHeight * 0.3,
child: Container(),
),
SizedBox(
height: availableHeight * 0.3,
child: Container(),
),
],
);
}
}
It happens to be really useful!
If you have something like a Column, you probably want to use the Expanded widget.
Try this code
Column(
children: [
Container(color: Colors.red, height: 100),
Expanded(
child: Container(color: Colors.yellow, height: 100),
),
Container(color: Colors.red, height: 100),
],
)
From the Expanded-class documentation:
A widget that expands a child of a Row, Column, or Flex so that the
child fills the available space.

Color of a widget inside a Stack is always slightly transparent

I display a custom-made bottom app bar in a Stack because of keyboard padding reasons. The custom widget is fully opaque as it should be until it's a child of a Stack in which case, the content behind it starts to be visible since the color's opacity somehow changes.
As you can see, it's only the "main" color that's transparent. Icons remain opaque.
This is the build method of my custom BottomBar widget which is then just regularly put into a Stack. I have tried using a Material and even a simple Container in place of the BottomAppBar widget but the results are the same.
#override
Widget build(BuildContext context) {
return BottomAppBar(
color: Colors.blue.withOpacity(1),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(
icon: Icon(MdiIcons.plusBoxOutline),
onPressed: () {},
),
Text('Edited 11:57'),
IconButton(
icon: Icon(MdiIcons.dotsVertical),
onPressed: () {},
),
],
),
);
}
Can you interact with the BottomAppBar ? It looks like an order problem. Try to put the BottomAppBar as last in the Stack children.
Note that BottomAppBar doesn't have a constant size, if you did not add it to Scaffold bottomNavigationBar named parameter has a size if this is not null. Below is peace of code in Scaffold dart file:
double bottomNavigationBarTop;
if (hasChild(_ScaffoldSlot.bottomNavigationBar)) {
final double bottomNavigationBarHeight = layoutChild(_ScaffoldSlot.bottomNavigationBar, fullWidthConstraints).height;
bottomWidgetsHeight += bottomNavigationBarHeight;
bottomNavigationBarTop = math.max(0.0, bottom - bottomWidgetsHeight);
positionChild(_ScaffoldSlot.bottomNavigationBar, Offset(0.0, bottomNavigationBarTop));
}
You can even develop your own Widget without BottomAppBar but if you want things like centerDocked and things like circular notched, you will have to do more stuff (anyway you have flexibility to custom design the way you want).
Here is a simple example to do that(one way to do that):
import 'package:flutter/material.dart';
class CustomBottomBar extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
Container(
margin: EdgeInsets.only(bottom: 50),
color: Colors.greenAccent, // if you want this color under bottom bar add the margin to list view
child: ListView.builder(
itemCount: 100,
itemBuilder: (_, int index) => Text("Text $index"),
),
),
Positioned(
bottom: 0,
child: Container(
color: Colors.amber.withOpacity(.5),
width: MediaQuery.of(context).size.width,
height: 50,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(4, (int index) => Text("Text $index")), // you can make these clickable by wrapping with InkWell or any gesture widget
),
),
),
],
),
);
}
}

Resizing button to fit different screen sizes

I am trying to make the button below resize depending on different screen sizes but retain the same ratios (i.e. text and button size should change proportionately so they look the same on all screens). I am using the AutoSizeText package to resize the text based on screensize but the text doesn't seem to getting smaller on the smaller screen which might be causing the button to resize oddly.
[![button 1][1]][1] - lager screen
[![button 2][2]][2] - smaller screen
I have tried using a mediaquery to size the button with height and width but that does not seem to be working.
Is there a recommended way of doing this?
class PurchaseButton extends StatelessWidget {
final Product product;
PurchaseButton({Key key, #required this.product}) : super(key: key);
#override
Widget build(BuildContext context) {
double deviceWidth = MediaQuery.of(context).size.width;
Container(
margin: EdgeInsets.symmetric(horizontal: 0.05 * deviceWidth),
child: Row(
children: <Widget>[
Expanded(
child: MaterialButton(
// height: SizeConfig.blockSizeVertical * 6,
onPressed: () async {
await Provider.of<SubscriptionModel>(context).makePurchase(product);
},
child: Text('Join now! Only ${product.priceString}',
style: Theme.of(context).textTheme.body1.copyWith(
fontWeight: FontWeight.w600, fontSize: 0.03 * deviceWidth)),
color: Theme.of(context).primaryColor,
),
),
],
),
);
return Container();
}
}
Many ways to do so, here's a suggestion:
Wrap your widget with this tree of widgets:
Container: to manipulate the width according to the screen size,
-- Row: we need it to force Expanded to work,
---- Expanded: will Expand its content to the whole space it has,
------[the widget you want to expand]
#override
Widget build(BuildContext context) {
double deviceWidth = MediaQuery.of(context).size.width;
double deviceHight = MediaQuery.of(context).size.height;
Container(
// If the button size(Row) is 90% then we give margin 5% + 5% like this
margin: EdgeInsets.symmetric(horizontal: 0.05 * deviceWidth),
// We need a Row in order to "Expanded" to work
child: Row(
children: <Widget>[
// Use "Expanded" if you want the button to fill the Row's size
// Use "Flexible" if you want the button to fit the text inside size.
Expanded(
child: MaterialButton(
onPressed: () {
print('Hi');
},
child: Text(
'Join now! Only...',
style: Theme.of(context)
.textTheme
.body1
.copyWith(fontWeight: FontWeight.w600),
),
color: Theme.of(context).primaryColor,
),
),
],
),
);
Regarding AutoSizeText it takes the size of text's container into consideration, not the screen size, my suggestion is to use regular Text(..) widget with font size taken from MediaQuery.of(context).size.width
e.g
child: Text(
'Join now! Only...',
style: Theme.of(context)
.textTheme
.body1
.copyWith(
fontWeight: FontWeight.w600,
fontSize: 0.03 * deviceWidth,
),
),

How to make text as big as the width allows in flutter

I have a Container where I need to show a barcode and I'd love to have the barcode to be as wide as possible on the screen.
For now I set the font size at a reasonable size that suits all devices, but it's only temporary of course.
How can I solve this? This is the code I am using for building the Widget.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_title),
),
body: Container(
padding: const EdgeInsets.all(12.0),
child: Column(
children: <Widget>[
SizedBox(
width: double.infinity,
child: Text(_barcode, style: TextStyle(fontFamily: 'Code128', fontSize: 90.0))
),
Text(_barcode, style: TextStyle(fontSize: 40.0))
]
),
)
);
}
I believe what you're looking for is FittedBox.
BoxFit applies whichever 'fit' you want to stretch/scale the child to fit in the box. It doesn't perform a pure 'stretch' on the text but rather the space it should take up. You shouldn't specify the text's size at the same time.
That looks like this:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
MyAppState createState() {
return new MyAppState();
}
}
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: Center(
child: Container(
color: Colors.blue,
width: 300.0,
height: 200.0,
child: FittedBox(
fit: BoxFit.contain,
child: Text("Whee"),
),
),
),
),
),
);
}
}
If you're wanting to actually 'stretch' the text (i.e. make the actual characters wider or taller) you'll have to do something a bit more custom.
If that's the case, look at CustomPaint, CustomPainter, TextPainter, and the Canvas translate & scale options. Basically, you would need to create a class extending CustomPainter in which you created a TextPainter, laid it out at a particular size, painted it onto the canvas, and then scaled it to fit the actual size of the CustomPainter (or do you scale the canvas first - I forget...). Then you'd pass an instance of that class to CustomPaint.
FittedBox is what worked for me but there is a twist. I also had to style my fontSize to a big number for it to work. Hope this helps.
child: FittedBox(
fit: BoxFit.fitHeight,
child: Text(
"Your Expanded Text :)",
style: TextStyle(fontSize: 400.0),
),
),
The code sample in the question has a Text widget as one of the children: of a Column widget. The width of the Text parent is unknown.
So to maximise the width and size of the Text widget in this case, wrap the Text widget in a FittedBox, then an Expanded.
child: Column(children: <Widget>[
Expanded(
child: FittedBox(
fit: BoxFit.contain,
child: Text(
'123',
)),
),
]),
The Text size should also automatically resize correctly even when the device is rotatated, or the screen resized, without overflow issues.
Expanded:
/// A widget that expands a child of a [Row], [Column], or [Flex]
/// so that the child fills the available space.
///
/// Using an [Expanded] widget makes a child of a [Row], [Column], or [Flex]
/// expand to fill the available space along the main axis (e.g., horizontally for
/// a [Row] or vertically for a [Column]). If multiple children are expanded,
/// the available space is divided among them according to the [flex] factor.
from /flutter/packages/flutter/lib/src/widgets/basic.dart
FittedBox:
/// Creates a widget that scales and positions its child within itself according to [fit].
you can use fitted box widget.
FittedBox(child:Text('text sample'));
https://api.flutter.dev/flutter/widgets/FittedBox-class.html
FittedBox would only work if it is provided some constraints, so make sure to provide one, like provide height as shown below:
SizedBox(
height: 400, // 1st set height
child: FittedBox(child: Text("*")), // 2nd wrap in FittedBox
)
Use TextPainter.width and a for loop to find the largest fitting font size (adding +1 is not very efficient, you may want to fine-tune that):
import 'package:flutter/material.dart';
main() => runApp(MaterialApp(
home: MyHomePage(),
theme: ThemeData(platform: TargetPlatform.iOS),
));
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Text autoscale'),
),
body: Padding(
padding: EdgeInsets.all(32.0),
child: Center(
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final text = 'Hello World';
final style = TextStyle(fontWeight: FontWeight.bold); // apply your barcode font here
final fontSize = calculateAutoscaleFontSize(text, style, 30.0, constraints.maxWidth);
return Text(
text,
style: style.copyWith(fontSize: fontSize),
maxLines: 1,
);
},
),
),
),
);
}
}
double calculateAutoscaleFontSize(String text, TextStyle style, double startFontSize, double maxWidth) {
final textPainter = TextPainter(textDirection: TextDirection.ltr);
var currentFontSize = startFontSize;
for (var i = 0; i < 100; i++) {
// limit max iterations to 100
final nextFontSize = currentFontSize + 1;
final nextTextStyle = style.copyWith(fontSize: nextFontSize);
textPainter.text = TextSpan(text: text, style: nextTextStyle);
textPainter.layout();
if (textPainter.width >= maxWidth) {
break;
} else {
currentFontSize = nextFontSize;
// continue iteration
}
}
return currentFontSize;
}
Wrap the text within a FittedBox widget, to force the text to be enclosed by a box. The FittedBox's size will depend on it's parent's widget. Within the FittedBox, the Text widget, can simply 'cover' the box, so the text doesn't stretch to fill the available space within the FittedBox. The enum BoxFit.fill, is a way to stretch the text to fit the entire space available within the FittedBox. You can change the dimensions of the box by altering the height and width of the FittedBox's parent, the Container.
Container(
height: _height,
width: _width,
FittedBox(
fit: BoxFit.fill,
child: Text("Whee"),
)
)