Make Divider match width of shrink-wrapped container? - flutter

I want to build a widget containing two Text widgets and one Divider widget, stacked vertically. The Divider should have the same width as the wider of the two Text widgets. The whole combined widget should consume no more layout space than its visible elements; that is, it should be shrink-wrapped.
I would like it to look like the following image:
(I added the light gray background here only to make my desired bounding box more apparent.)
It is unclear to me how to simultaneously force the widget hierarchy to assume the natural maximum width of the text while causing the Divider to stretch to a width only as great as the width of the text. If possible, I would like to do this without writing any custom layout widgets and without directly assigning a width to the Divider using measurements of widgets following the first layout or rendering pass.
Here is some code I tried in DartPad (https://dartpad.dev/flutter), but, naturally, it does not have the desired effect:
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: Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
Text("Lorem ipsum"),
Text("dolor"),
Divider(thickness: 1.0, color: Colors.red),
]),
)));
}
}
My end goal is to have code that works in Android Studio 3.5.3, Windows 10 64-bit.
How can I fix (or replace) my code to accomplish this?

Use IntrinsicWidth see https://medium.com/flutter-community/flutter-layout-cheat-sheet-5363348d037e for detailed options
https://dartpad.dev/79edda902aa584124dafd56c35d612fd
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: IntrinsicWidth(
child:Column(mainAxisSize: MainAxisSize.min , children: <Widget>[
Text("Lorem ipsum"),
Text("dolor"),
Divider(thickness: 1.0, color: Colors.red),
]))),
));
}
}

Related

What is PreferredSize after all?

I understand that it is used to adjust AppBar, but what is PreferredSize after all?
What is the use of PreferredSize widget in flutter?
The following text is found in the official documents, but I do not understand what it means.
https://api.flutter.dev/flutter/widgets/PreferredSize-class.html
It just advertises a preferred size which can be used by the parent.
The explanation in the official documentation is also limited to the AppBar, which I could not understand further.
I wrote the following code as a test, but the height of the green container was spread across the entire screen.
import 'package:flutter/material.dart';
void main() {
runApp(const _MyApp());
}
class _MyApp extends StatelessWidget {
const _MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: PreferredSize(
preferredSize: const Size.fromHeight(100.0),
child: Container(
width: 300,
color: Colors.green,
),
),
),
),
);
}
}
Here is the Short Details about it:
Preferred Size is a custom widget lets you allow to design your custom appbar for you with the same height, width, elevation and feel similar to Appbar.
Sometimes you want to create tabs or more effective design for your appbar then you can create a customChild for your appBar with the help of PreferredSizeWidget.
The size this widget would prefer if it were otherwise unconstrained.
In many cases it's only necessary to define one preferred dimension.
For example the [Scaffold] only depends on its app bar's preferred
height. In that case implementations of this method can just return
Size.fromHeight(myAppBarHeight).

How do you position a PageView inside a Stack?

I have a PageView and a button inside a Stack (the PageView is at the bottom of the Stack). I have the button in the bottomCenter using Stack's alignment property.
I want to position the PageView inside the Stack so that it appears above the button (as in vertically above, not above in the Stack). However, it seems that you can't use the Positioned widget with a PageView to accomplish this.
I am using a Stack because the PageView contains a zoomable widget using InteractiveViewer, and I want the button to stay on top when this widget is zoomed in.
Wrapping the PageView with a Positioned widget gives different errors based on if you run the app from scratch, if you do a Flutter Hot Restart, or if you do a Flutter Hot Reload. (However, there are no errors if you do not specify any of the other Positioned parameters, i.e. bottom, left, etc.)
Another problem that occurs when you hot reload is that it does do something to the PageView. It shifts it up, but by more than the specified amount in the bottom parameter. It also cuts off a lot of the width (but doesn't give any overflow errors). You can see this in the second picture that is included.
If you specify the bottom parameter and then do a hot reload it gives this error:
The following assertion was thrown during performResize():
Horizontal viewport was given unbounded width.
Viewports expand in the scrolling direction to fill their container. In this case, a horizontal viewport was given an unlimited amount of horizontal space in which to expand. This situation typically happens when a scrollable widget is nested inside another scrollable widget.
If this widget is always nested in a scrollable widget there is no need to use a viewport because there will always be enough horizontal space for the children. In this case, consider using a Row instead. Otherwise, consider using the "shrinkWrap" property (or a ShrinkWrappingViewport) to size the width of the viewport to the sum of the widths of its children.
If you specify the bottom parameter, then either stop and run the app from scratch or do a hot restart, it gives these errors:
Error 1:
The following NoSuchMethodError was thrown during performLayout():
The method 'toStringAsFixed' was called on null.
Receiver: null
Tried calling: toStringAsFixed(1)
Error 2 (several variations of this error):
RenderBox was not laid out: RenderPointerListener#99b5a relayoutBoundary=up8 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
'package:flutter/src/rendering/box.dart':
Failed assertion: line 1785 pos 12: 'hasSize'
Here is a simple app that shows my problem:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) => MaterialApp(home: MyHomePage());
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
PageController _pageController = PageController(initialPage: 0);
int currentIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
TopToolbar(currentIndex == 0
? 'Car'
: currentIndex == 1
? 'Bike'
: 'Walk'),
Expanded(
child: Stack(
alignment: AlignmentDirectional.bottomCenter,
children: [
Positioned(
// vvvvvvvvvvvvvvvv Specifying this causes the errors: vvvvvvvvvvvvvvvvvvvvvvvv
bottom: 50, // 50 because that is the height of the button
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
child: PageView(
controller: _pageController,
onPageChanged: (page) {
setState(() {
currentIndex = page;
});
},
children: [
CarScreen(),
BikeScreen(),
WalkScreen(),
],
),
),
Container(
height: 50, // <-------------------- Height of the button
color: Colors.lightBlueAccent,
child: Text('A button', style: TextStyle(fontSize: 20)))
],
),
),
],
),
),
// ==== I don't think the code below is relevant to the question, but who knows:
bottomNavigationBar: BottomNavigationBar(
currentIndex: currentIndex,
onTap: (value) {
setState(() {
currentIndex = value;
_pageController.jumpToPage(value);
});
},
items: [
BottomNavigationBarItem(
icon: Icon(Icons.directions_car), label: 'Car'),
BottomNavigationBarItem(
icon: Icon(Icons.directions_bike), label: 'Bike'),
BottomNavigationBarItem(
icon: Icon(Icons.directions_walk), label: 'Walk'),
],
),
);
}
}
class TopToolbar extends StatelessWidget {
final String label;
TopToolbar(this.label);
#override
Widget build(BuildContext context) => Container(
color: Colors.lightBlueAccent,
child: Row(
children: [
Text('Custom toolbar for $label', style: TextStyle(fontSize: 20)),
],
),
);
}
class CarScreen extends StatelessWidget {
#override
Widget build(BuildContext context) => ScreenWidget('Car');
}
class BikeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) => ScreenWidget('Bike');
}
class WalkScreen extends StatelessWidget {
#override
Widget build(BuildContext context) => ScreenWidget('Walk');
}
class ScreenWidget extends StatelessWidget {
final String title;
ScreenWidget(this.title);
#override
Widget build(BuildContext context) => Center(
child: Container(
width: 200,
height: 550,
alignment: Alignment.center,
color: Colors.grey,
child: Text('$title Screen', style: TextStyle(fontSize: 20))));
}
What it looks like without the bottom parameter specified. See that the button covers up part of the PageView, I don't want this to happen, this is what I'm trying to fix:
What it looks like when you specify the bottom parameter and then do a hot reload. You can see it shifts it up by more than what's specified by bottom, and it cuts off a lot of the width without giving overflow errors:
What it looks like when you specify the bottom parameter and then either hot restart, or stop and run the app from scratch. The PageView and the button are gone:

How to add multiple rows or columns after the body

I'm still trying to understand how to structure widgets. I have placed a container in the body already so how can I now add another row. I've removed some code to simplify my situation but hopefully this gives an idea of how my project is structured at the moment.
class AddButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
alignment: Alignment.bottomCenter,
//Stats Button
child: Row(
), //container
//How can I enter a new row here <------- WHERE I WANT TO ENTER A ROW
);
}
}
The short answer is, you cannot. You can take advantage of the children property of a Column, the most common layout widget in all of Flutter. Flutter works on a system of nested widgets, you cannot have many parents as it all starts with one widget.
class AddButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
alignment: Alignment.bottomCenter,
//Stats Button
child: Column(
children: <Widget>[
Row(
children: <Widget>[
// nested widgets
],
),
],
),
),
);
}
}
Judging by your class name, you just want a button. Not every widget starts with a Scaffold, that's only if you want an entire layout with an app bar or a bottom navigation bar. For simple widgets like a button, you can get rid of Scaffold entirely and just use MaterialButton like this.
class AddButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialButton(
onPressed: () {}, //empty function
);
}
}
More reading:
https://flutter.dev/docs/development/ui/layout
https://pusher.com/tutorials/flutter-building-layouts
https://medium.com/flutter-community/flutter-layout-cheat-sheet-5363348d037e

How to display an image partially in flutter to get a curtain-raising effect?

I want to display an image in flutter such that it gives an effect of curtain-raising animation based on the slider values.
For example, I am displaying the IMAGE.jpg which has a fixed height and width on the flutter app.
The slider has 0 to 10 range.
When 0: the slider value is 0 then the image layout area is displayed 100% black.
When 1: Bottom 10% is displayed and the top 90% is black
When 2: Bottom 20% is displayed and the top 80% is black
and similarly
When 10: 100% is displayed
How can this effect be created?
To create this curtain effect you can use the Slider and Align widgets. You can set heightFactor inside the Align class to create a percentage curtain effect. Inside the Slider widget you can now set the value of the heightFactor to create the curtain effect.
Here a minimal working example:
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(home: CurtainScaffold());
}
}
class CurtainScaffold extends StatefulWidget {
#override
_CurtainScaffoldState createState() => _CurtainScaffoldState();
}
class _CurtainScaffoldState extends State<CurtainScaffold> {
double curtain = 0.0;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Slider(
onChanged: (double val) {
setState(() {
this.curtain = val;
});
},
value: curtain,
min: 0.0,
max: 1.0,
),
ClipRect(
child: Align(
alignment: Alignment.bottomCenter,
heightFactor: curtain,
child: Image.network(
'https://upload.wikimedia.org/wikipedia/commons/b/bd/Dts_news_bill_gates_wikipedia.JPG'),
),
),
],
),
),
);
}
}

Fix last element of ListView to the bottom of screen

I am trying to implement a custom navigation drawer using Flutter. I would like to attach log out option to the bottom of the drawer. The problem is that number of elements above log out option is unknow (from 3 to 17).
So if these widgets take half of the space of a drawer, then log out option will be on the bottom, and if there is too much of them and you have to scroll to see them all, then the log out option will be simply the last.
I am also trying to give the first two options a green background color. Which widget tree would you recommend me? I had a thought about the ListView widget, it takes List of widgets as an argument in constructor.
Therefore I can solve the different background color for the first two items. But I still can't figure out how to attach the log out option to the bottom. In this case it's at the bottom of drawer, but it can happen, that other options will be bigger than screen size and in that case, it should be placed at the bottom of whole list.
EDIT: I've add a design to the question. The logout option is the one called Odhlášení. In this case it's at the bottom of drawer, but it can happen, that other options will be bigger than the screen size and in that case, it should be placed at the bottom of whole list.
Design:
You can simply use ListView to manage the "17" navigation options. Wrap this ListView inside an Column. The ListView will be the first child of the Column the second child, therefore placing at the bottom, will be your logout action.
If you are using transparent widgets (like ListTile) inside your ListView to display the navigation options, you can simply wrap it inside a Container. The Container, besides many other widgets, allows you to set a new background color with its color attribute.
Using this approach the widget tree would look like the following:
- Column // Column to place your LogutButton always below the ListView
- ListView // ListView to wrap all your navigation scrollable
- Container // Container for setting the color to green
- GreenNavigation
- Container
- GreenNavigation
- Navigation
- Navigation
- ...
- LogOutButton
Update 1 - Sticky LogOutButton :
To achieve the LogOutButton sticking to the end of the ListView you'll neeed to do two things:
Replace the Expanded with an Flexible
Set shrinkWrap: true inside the ListView
Update 2 - Spaced LogOutButton until large List:
Achieving the described behavior is a more difficult step. You'll have to check if the ListView exceeds the screen and is scrollable.
To do this I wrote this short snippet:
bool isListLarge() {
return controller.positions.isNotEmpty && physics.shouldAcceptUserOffset(controller.position);
}
It will return true if the ListView exceeds its limitations. Now we can refresh the state of the view, depending on the result of isListViewLarge. Below again a full code example.
Standalone code example (Update 2: Spaced LogOutButton until large List):
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
drawer: MyDrawer(),
),
);
}
}
class MyDrawer extends StatefulWidget {
#override
_MyDrawerState createState() => _MyDrawerState();
}
class _MyDrawerState extends State<MyDrawer> {
ScrollController controller = ScrollController();
ScrollPhysics physics = ScrollPhysics();
int entries = 4;
#override
Widget build(BuildContext context) {
Widget logout = IconButton(
icon: Icon(Icons.exit_to_app),
onPressed: () => {setState(() => entries += 4)});
List<Widget> navigationEntries = List<int>.generate(entries, (i) => i)
.map<Widget>((i) => ListTile(
title: Text(i.toString()),
))
.toList();
if (this.isListLarge()) { // if the List is large, add the logout to the scrollable list
navigationEntries.add(logout);
}
return Drawer(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, // place the logout at the end of the drawer
children: <Widget>[
Flexible(
child: ListView(
controller: controller,
physics: physics,
shrinkWrap: true,
children: navigationEntries,
),
),
this.isListLarge() ? Container() : logout // if the List is small, add the logout at the end of the drawer
],
),
);
}
bool isListLarge() {
return controller.positions.isNotEmpty && physics.shouldAcceptUserOffset(controller.position);
}
}
Standalone code example (Update 1: Sticky LogOutButton):
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
drawer: MyDrawer(),
),
);
}
}
class MyDrawer extends StatefulWidget {
#override
_MyDrawerState createState() => _MyDrawerState();
}
class _MyDrawerState extends State<MyDrawer> {
int entries = 4;
#override
Widget build(BuildContext context) {
return Drawer(
child: Column(
children: <Widget>[
Flexible(
child: ListView(
shrinkWrap: true,
children: List<int>.generate(entries, (i) => i)
.map((i) => ListTile(
title: Text(i.toString()),
))
.toList(),
),
),
IconButton(
icon: Icon(Icons.exit_to_app),
onPressed: () => {setState(() => entries += 4)})
],
),
);
}
}
Standalone code example (Old: Sticking to bottom):
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
drawer: MyDrawer(),
),
);
}
}
class MyDrawer extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Drawer(
child: Column(
children: <Widget>[
Expanded(
child: ListView(
children: List<int>.generate(40, (i) => i + 1)
.map((i) => ListTile(
title: Text(i.toString()),
))
.toList(),
),
),
IconButton(icon: Icon(Icons.exit_to_app), onPressed: () => {})
],
),
);
}
}