Flutter : dynamic height for scrollable content inside customScrollView (with sticky behaviors) - flutter

I would like to achieve this result but I can't get my head around it...
Image exemple
The only way I found to "fixed" my header (top) and button (bottom) was to use respectively SliverPinnedHeader (which requires the user of the CustomScrollView widget) and StickySideWidget.bottom.
As I am inside a Column widget, I am required to use the Expanded widget which make the scroll works when the list is long enough, but that also take full screen height even when I only have a few items... I'd like to find a way to remove the Expanded without breaking everything
Here is a simplified code :
import 'package:flutter/material.dart';
import 'package:shared/widgets/commons/safe_area.dart';
import 'package:sliver_tools/sliver_tools.dart';
class DemoWidget extends StatelessWidget {
DemoWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
child: Text('Demo'),
onPressed: () => showModalBottomSheet(
backgroundColor: Colors.white,
isScrollControlled: true,
context: context,
builder: (context) => DemoContent(),
))));
}
}
class DemoContent extends StatelessWidget {
DemoContent({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final items = List.filled(20, '');
final bottomSheetClosingLine = Padding(
padding: const EdgeInsets.only(bottom: 32),
child: Center(child: Container(color: Colors.grey, height: 4, width: 64)),
);
final header = Container(
padding: const EdgeInsets.all(24),
color: Colors.blue,
child: Text('Header'));
final itemWidget = Padding(
padding: const EdgeInsets.symmetric(vertical: 16), child: Text('item'));
final button = Container(
padding: EdgeInsets.only(top: 24),
width: double.infinity,
child: ElevatedButton(onPressed: () {}, child: Text('Save')),
);
return SafeArea(
child: Container(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.80),
padding: EdgeInsets.all(24),
width: double.infinity,
child: Column(mainAxisSize: MainAxisSize.min, children: [
bottomSheetClosingLine,
Expanded(
child: StickySideWidget.bottom( /// Custom widget
body: CustomScrollView(slivers: [
SliverPinnedHeader(child: header),
SliverToBoxAdapter(
child: Column(
children: items.map((item) => itemWidget).toList(),
))
]),
side: button,
))
])));
}
}
Edit : I would also like to avoid as much as possible to add an unnecessary library...

Try to use this Widget
draggableScrollableSheet

Related

I am new to flutter and I think I have misunderstood the widget and layout system

If I want to add more buttons and text widgets where and how should I do it, should I make some sort of column and row system or am I totally off? And is my code programmed wrong?
import 'package:flutter/material.dart';
void main() {
runApp(
const HomeScreen(),
);
}
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Dice'),
centerTitle: true,
),
body: const Dice()));
}
}
class Dice extends StatefulWidget {
const Dice({Key? key}) : super(key: key);
#override
State<Dice> createState() => _DiceState();
}
class _DiceState extends State<Dice> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Row(
children: [
Expanded(
child: Container(
margin: const EdgeInsets.all(15),
child: Image.asset('images/1.png'))),
Expanded(
child: Container(
margin: const EdgeInsets.all(15),
child: Image.asset('images/2.png'))),
Expanded(
child: Container(
margin: const EdgeInsets.all(15),
child: Image.asset('images/3.png'))),
Expanded(
child: Container(
margin: const EdgeInsets.all(15),
child: Image.asset('images/4.png'))),
Expanded(
child: Container(
margin: const EdgeInsets.all(15),
child: Image.asset('images/5.png')))
],
),
));
}
}
I am going to add variables to the children in the container later.
For better understanding I would recommend you checking out this medium article. https://medium.com/flutter-community/flutter-layout-cheat-sheet-5363348d037e Here you can view all the important layout widgets you can use.
In general you have a widget tree starting by MaterialApp and you can add as many items as you want. In flutter if you want multiple widgets you can use Row and Column for that. Both of them provides a children property in brackets [] there you can add multiple widgets inside separated by comma.
Most of the other widgets are also able to provide a children property where you can add even more children widgets. That's how the widget tree in general works. Actually you have unlimited possibility’s.
Your code is totally fine. By the way you can also create custom widgets if the plenty widgets of flutter doesn't fit your use case.
In your example you can add whatever you want in your row, text, more images, buttons, everything you like.
Here you can check out the official widget catalog of flutter: https://docs.flutter.dev/development/ui/widgets
You can place a Column/ListView in between your Center and Row widgets, then you could add multiple rows to it like below:
class _DiceState extends State<Dice> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
Row(
children: [
Expanded(
child: Container(
margin: const EdgeInsets.all(15),
child: Image.asset('images/1.png'))),
Expanded(
child: Container(
margin: const EdgeInsets.all(15),
child: Image.asset('images/2.png'))),
Expanded(
child: Container(
margin: const EdgeInsets.all(15),
child: Image.asset('images/3.png'))),
Expanded(
child: Container(
margin: const EdgeInsets.all(15),
child: Image.asset('images/4.png'))),
Expanded(
child: Container(
margin: const EdgeInsets.all(15),
child: Image.asset('images/5.png')))
],
),
Row(children: [/* more content here */]),
],
),
),
);
}
}

How to remove padding from some children in a Column?

Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
// ... some widgets
Padding(
padding: const EdgeInsets.all(-20.0), // Error: How to do something like this?
child: FooWidget()
),
// ... more widgets
BarWidget(), // Remove padding from here also
// ... and some more widgets
],
),
)
I'm providing a padding of 20 to my Column, but I want to remove this padding from some of its children, like FooWidget, BarWidget, etc. How can I do that?
Note: I'm not looking for workarounds like provide the padding to other widgets instead of the root Column or wrap those widgets in another Column and provide that column a padding, etc.
you can apply transformation to the widgets that you want to remove the padding for, for example:
Container(
transform: Matrix4.translationValues(-20.0, 0, 0.0), //here
child: FooWidget()),
This working solution uses UnconstrainedBox that only takes away the left side and right side of padding. You might do the calculation of overflowWidth first when screenWidth is not feasible to use.
In addition, this comes up with a RenderConstraintsTransformBox overflowed exception that will be gone away in app release version, e.g. flutter build appbundle for android app.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: UnboundedWidget(),
);
}
}
class UnboundedWidget extends StatelessWidget {
const UnboundedWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final double overflowWidth = MediaQuery.of(context).size.width;
return Scaffold(
appBar: AppBar(
title: const Text('Unbounded demo'),
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
UnconstrainedBox(
child: Container(
color: Colors.red,
width: overflowWidth,
child: const Text('123'),
),
),
],
)),
);
}
}
There is no such thing as negative margins in flutter.
You can try workarounds with transforms on x, y, z axis as transform property on Container widget.
Or try with SizedBox which ignores parent padding.
Here is a similar example that should work:
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
Container(
width: double.infinity, height: 20, color: Colors.green),
// This child ignores parent padding.
SizedBox(
width: MediaQuery.of(context).size.width,
height: 20,
child: Expanded(
child: OverflowBox(
maxWidth: MediaQuery.of(context).size.width,
child: Container(
width: double.infinity,
height: 20,
color: Colors.red)),
),
),
Container(
width: double.infinity,
height: 20,
color: Colors.blue),
],
),
),
Use Stack widget with Positioned widget Positioned(left: -20, child: widget)
on the other hand, for padding less.
you can create custom 2 widget name as:
paddingLessWidget(child: your widget)
paddingWithWidget(child: your widget)
then use this into column() widget.
Remove padding from column's parents.
use as:
Column(
children:[
paddingLessWidget(child: your widget),
paddingWithWidget(child: your widget)
]
),

Flutter, How to fit width in Column to first child

This is my code .
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.5),
child: (message.caption != null)
? Column(
children: [
Flexible(
child: ImageLoader(message: message, controller: _),
),
Container(
child: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: BubbleText(
message: message,
fromCaption: true,
account: account),
),
),
],
)
: ...
This is what my column looks like with 2 children
Here is how I want it to look
if you want to dynamic width you must use statefull widget, because you have storing first child width. So below code will works for your sitution
class Challenge extends StatefulWidget {
const Challenge({Key? key}) : super(key: key);
#override
_ChallengeState createState() => _ChallengeState();
}
class _ChallengeState extends State<Challenge> {
final GlobalKey _firstChildKey = GlobalKey();
double? childWidth;
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) {
final RenderBox renderBoxRed = _firstChildKey.currentContext.findRenderObject();
final size = renderBoxRed.size;
setState(() {
childWidth = size.width;
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return ConstrainedBox(
constraints: BoxConstraints(maxHeight: size.height * 0.5, maxWidth: childWidth ?? size.width * .2),
child: Column(
children: [
Flexible(
key: _firstChildKey,
child: ImageLoader(message: message, controller: _),
),
Container(
child: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: BubbleText(message: message, fromCaption: true, account: account),
),
),
],
));
}
}
It actually amazes me how often does this need arise and how no one has wrote a package about this yet.
So I did: https://pub.dev/packages/flex_with_main_child
It'll look something like this:
ColumnWithMainChild(
mainChild: Flexible(
child: ImageLoader(message: message, controller: _),
),
childrenBelow: [
Container(
child: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: BubbleText(
message: message,
fromCaption: true,
account: account),
),
),
],
)

I have several expanded widgets in column. First I expand one widget. when I expanding the second one first one should auto collapse

I have implement several no of expanded items using expansion widget.
These are the steps
All the widgets are collapsed at the beginning
First widget was expanded
Second widget also expanded without collapse first one
I want to automatically collapse first one when expanding second one
import 'package:expansion_widget/expansion_widget.dart';
import 'package:flutter/material.dart';
class CustomExpansionTile extends StatefulWidget {
final Widget HeaderBody;
final Widget ExpandedBody;
final Color HeaderColor;
final Color ExpandedBodyColor;
final double Padding;
const CustomExpansionTile({
Key? key,
required this.HeaderBody,
required this.ExpandedBody,
required this.HeaderColor,
required this.ExpandedBodyColor,
required this.Padding,
}) : super(key: key);
#override
_CustomExpansionTileState createState() => _CustomExpansionTileState();
}
class _CustomExpansionTileState extends State<CustomExpansionTile> {
#override
Widget build(BuildContext context) {
return Column(
children: [
Card(
elevation: 0,
child: ExpansionWidget(
initiallyExpanded: false,
titleBuilder:
(double animationValue, _, bool isExpaned, toogleFunction) {
return Container(
decoration: BoxDecoration(
color: widget.HeaderColor,
borderRadius: BorderRadius.circular(6)),
height: 59,
child: InkWell(
onTap: () {
toogleFunction(animated: true);
},
child: Padding(
padding: EdgeInsets.symmetric(horizontal: widget.Padding),
child: Row(
children: [
widget.HeaderBody,
const Spacer(),
Transform.rotate(
angle: 0,
child: Icon(
isExpaned
? Icons.keyboard_arrow_down_rounded
: Icons.keyboard_arrow_right,
size: 40),
alignment: Alignment.centerRight,
)
],
),
)),
);
},
content: Container(
color: widget.ExpandedBodyColor,
width: double.infinity,
padding: const EdgeInsets.all(20),
child: Column(
children: [widget.ExpandedBody],
),
),
),
),
],
);
}
}
This is my Code for calling custom widget
CustomExpansionTile(
HeaderBody: Row(
children: [
Text('Hellooo'),
Text('Hellooo'),
],
),
ExpandedBody: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text('Hellooo'),
Text('Hellooo'),
],
),
HeaderColor: Colors.white,
ExpandedBodyColor: Colors.white,
Padding: 0,
),
To achieve that you can do one of the following:
1- [O(n): O(1)] create a new property called currentSelectedItem its type is as your data model type and onTap method change its value to your tapped item and do not forget to add the check to the expansion property of the expanded card as -> expanded: currentSelectedItem == itemModel,
2- [O(n): O(n)] add a new boolean property to your model item called "isExpanded" and config on tap action to loop throw all models list changing items' isExpanded to false and update current tapped item's isExpanded to true

Expanded with max width / height?

I want widgets that has certain size but shrink if available space is too small for them to fit.
Let's say available space is 100px, and each of child widgets are 10px in width.
Say parent's size got smaller to 90px due to resize.
By default, if there are 10 childs, the 10th child will not be rendered as it overflows.
In this case, I want these 10 childs to shrink in even manner so every childs become 9px in width to fit inside parent as whole.
And even if available size is bigger than 100px, they keep their size.
Wonder if there's any way I can achieve this.
return Expanded(
child: Row(
children: [
...List.generate(Navigation().state.length * 2, (index) => index % 2 == 0 ? Flexible(child: _Tab(index: index ~/ 2, refresh: refresh)) : _Seperator(index: index)),
Expanded(child: Container(color: ColorScheme.brightness_0))
]
)
);
...
_Tab({ required this.index, required this.refresh }) : super(
constraints: BoxConstraints(minWidth: 120, maxWidth: 200, minHeight: 35, maxHeight: 35),
...
you need to change Expanded to Flexible
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(appBar: AppBar(), body: Body()),
);
}
}
class Body extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
width: 80,
color: Colors.green,
child: Row(
children: List.generate(10, (i) {
return Flexible(
child: Container(
constraints: BoxConstraints(maxWidth: 10, maxHeight: 10),
foregroundDecoration: BoxDecoration(border: Border.all(color: Colors.yellow, width: 1)),
),
);
}),
),
);
}
}
two cases below
when the row > 100 and row < 100
optional you can add mainAxisAlignment property to Row e.g.
mainAxisAlignment: MainAxisAlignment.spaceBetween,
Try this
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 10,maxHeigth:10),
child: ChildWidget(...),
)
The key lies in a combination of using Flexible around each child in the column, and setting the child's max size using BoxContraints.loose()
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(
title: 'Make them fit',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int theHeight = 100;
void _incrementCounter() {
setState(() {
theHeight += 10;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Playing with making it fit'),
),
body: Container(
color: Colors.blue,
child: Padding(
// Make the space we are working with have a visible outer border area
padding: const EdgeInsets.all(8.0),
child: Container(
height: 400, // Fix the area we work in for the sake of the example
child: Column(
children: [
Expanded(
child: Column(
children: [
Flexible(child: SomeBox('A')),
Flexible(child: SomeBox('A')),
Flexible(child: SomeBox('BB')),
Flexible(child: SomeBox('CCC')),
Flexible(
child: SomeBox('DDDD', maxHeight: 25),
// use a flex value to preserve ratios.
),
Flexible(child: SomeBox('EEEEE')),
],
),
),
Container(
height: theHeight.toDouble(), // This will change to take up more space
color: Colors.deepPurpleAccent, // Make it stand out
child: Center(
// Child column will get Cross axis alighnment and stretch.
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text('Press (+) to increase the size of this area'),
Text('$theHeight'),
],
),
),
)
],
),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class SomeBox extends StatelessWidget {
final String label;
final double
maxHeight; // Allow the parent to control the max size of each child
const SomeBox(
this.label, {
Key key,
this.maxHeight = 45,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return ConstrainedBox(
// Creates box constraints that forbid sizes larger than the given size.
constraints: BoxConstraints.loose(Size(double.infinity, maxHeight)),
child: Padding(
padding: const EdgeInsets.all(2.0),
child: Container(
decoration: BoxDecoration(
color: Colors.green,
border: Border.all(
// Make individual "child" widgets outlined
color: Colors.red,
width: 2,
),
),
key: Key(label),
child: Center(
child: Text(
label), // pass a child widget in stead to make this generic
),
),
),
);
}
}