I have a StatelessWidget defined as:
class FlexElement extends StatelessWidget {
final List<Widget> widgets;
final bool isVisible;
const FlexElement({
Key? key,
required this.widgets,
required this.isVisible,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Visibility(
visible: isVisible,
child: Container(
child: Row(
children: <Widget>[...widgets],
),
),
);
}
}
I would like to wrap each widget with a Flexible, so it doesn't overflow. (Row(children: Flexible([...widgets])). But since it is a list of widgets, I can only assign to multiple children and not to a single child. How would I solve this?
This is the result i would like:
Row(
children: [
Flexible(
child: Widget1(), // <-- Wrapped in Flexible.
),
Flexible(
child: Widget2(), // <-- Wrapped in Flexible.
),
...
],
)
You can do,
children: <Widget>[...widgets.map((w)=>Flexible(child:w))],
Or direct
children:widgets.map((w)=>Flexible(child:w)).toList(),
This is what I always do:
Row(
children: [
Flexible(
flex: 20,
fit: FlexFit.tight,
child: Widget1(), // <-- Wrapped in Flexible.
),
Flexible(
flex: 30,
fit: FlexFit.tight,
child: Widget2(), // <-- Wrapped in Flexible.
),
...
],
)
I put the flex values to total 100 so I think of each as a percentage of the width of the Row.
Related
Is there any way to achieve text baseline with VericalDivider() where the Rows() under the Column() and required IntrinsicHeight() to make the VerticalDiver() render properly.
Meanwhile, look like there is limitation around CrossAxisAlignment.baseline and IntrinsicHeight https://github.com/flutter/flutter/issues/71990#issuecomment-747141796
From the picture below, I can't use CrossAxisAlignment.baseline with IntrinsicHeight Widget so the text that has different size will look something like this. But if I remove IntrinsicHeight the VericalDivider Widget will not render. (Height = 0)
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("hey"),
IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(child: Text("aaaaaxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")),
VerticalDivider(
width: 50,
thickness: 50,
),
Expanded(child: Text("bbbbbb", style: TextStyle(fontSize: 30),))
],
),
),
],
),
);
}
}
Is it possible to shrink a Text widget to the width of the longest line when the text is wrapped because it becomes too long to fit on a single line?
Basically I have this custom widget:
class MyWidget extends StatelessWidget {
final String text1;
final String text2;
const MyWidget({Key? key, this.text1='', this.text2=''}) : super(key: key);
#override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Container(
color: Colors.lightBlue,
child: Center(child: Text(text1))
)
),
Expanded(
child: Container(
color: Colors.lightGreen,
alignment: Alignment.centerRight,
child: Text(text2, textAlign: TextAlign.start)
)
)
]
);
}
}
Here's what it looks like with one short and one long text:
But what I want is this:
Note that I do not want the text to be right-aligned - the wrapped lines should be aligned to the left, but the entire Text widget should shrink to the longest line.
Just found the answer myself when investigating the more exotic properties of the Text widget. Apparently, textWidthBasis: TextWidthBasis.longestLine does exactly what I want.
class MyWidget extends StatelessWidget {
final String text1;
final String text2;
const MyWidget({Key? key, this.text1='', this.text2=''}) : super(key: key);
#override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Container(
color: Colors.lightBlue,
child: Center(child: Text(text1))
)
),
Expanded(
child: Container(
color: Colors.lightGreen,
alignment: Alignment.centerRight,
child: Text(text2, textWidthBasis: TextWidthBasis.longestLine)
)
)
]
);
}
}
you can wrap Text with FittedBox
FittedBox(
fit: BoxFit.fitWidth,
child: Text(
"some text"
),),
This answered for me. You can replace the following part in your code:
Expanded(
child: Container(
color: Colors.lightGreen,
alignment: Alignment.centerRight,
child: LayoutBuilder(builder: (context, size) {
final span = TextSpan(text: text2);
final tp = TextPainter(
text: span,
maxLines: 1,
textDirection: Directionality.of(context));
tp.layout(maxWidth: size.maxWidth);
if (tp.didExceedMaxLines) {
return Padding(
padding: const EdgeInsetsDirectional.only(start: 16.0),
child: Text(text2),
);
} else {
return Text(text2);
}
})))
Totally I need a scrollable DataTable with refresh possibility on pull down between header and footer fixed width widgets.
I have a widget, using on every page in my app. THis widget return a Scaffold with appbar and body like column (). This widget used like CommonPage(title, widgetsArrayForBodyColumn) from other widgets.
For now I need to use a follow widgets in that body:
Lookup (for filtering or for example, any fixed height widget)
DataTable (for show data)
Card (to show another data)
DataTable should be scrollable and refreshable on pull down.
I tried many different ways, but still has no success. My last solution is using stack, but list hidden beside the card.
I can't understand how to achieve my goal: have refreshing datatable between non scrollable widgets in a column.
class CommonPage extends StatefulWidget {
final String title;
final List<Widget> children;
const CommonPage({
Key? key,
required this.title,
required this.children,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
...
body: Column(children: [
FixedHeightWidget(),
PageScreen(),
]);
}
}
class PageScreen {
#override
Widget build(BuildContext context) {
return CommonPage(
title: Title,
children: [
FixedHeightWidget(),
PageList(),
],
);
}
}
class PageList {
Widget build(BuildContext context) {
//TODO Return fixedWidthWidget at top, expandedRefreshableScrollableDataTable, fixedWidthWidget at bottom
}
}
My current solution: (almost achieves my goal, but as mentioned card hides part of list and no ways to scroll and check bottom of the list):
return Expanded(
child: Stack(
children: [
RefreshIndicator(
onRefresh: onRefresh,
child: ListView(
children: [
listItems.isNotEmpty
? DataTable(...)
: Container(),
],
),
),
Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
alignment: Alignment.bottomCenter,
child: Column(
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 5),
child: Card(...),
),
),
),
],
),
],
),
);
I found the solution:
return Expanded(
child: Column(
children: [
Expanded(
child: RefreshIndicator(
onRefresh: onRefresh,
child: ListView(
children: [listItems.isNotEmpty
? DataTable(...)
: Container(),
],
),
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 5),
child: Card(...),
),
],
),
);
I want overlay a widget over the bottom widget to hide it. hence I use stack. I don't know the bottom widget size so I can't give top widget fixed size.
what I have:
what I want to achieve:
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'Winner: ',
style: TextStyle(fontSize: 34),
),
Stack(
children: [
Text('YOU!!!', style: TextStyle(fontSize: 34)),
Container(
color: Colors.amber,
)
],
),
],
),
This is simplified version of what I want to achive. I have to use Row and assume Texts are any widget with any size.
Stack sizes its children based on the Constraint it receives from parent not based on children sizes.
how can I overlay top widget with exact size of bottom widget?
To know the size of your widget and apply things depending on this, you can use GlobalKey like this GlobalKey key;. Initialize it in your initSate and listen to it's size
GlobalKey key;
double yourWidgetWidth;
double yourWidgetHeight;
#override
initState() {
// .... other stuffs
key = GlobalKey();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
yourWidgetWidth = containerKey.currentContext.size.width;
yourWidgetHeight = containerKey.currentContext.size.height;
}
});
}
And with yourWidgetWidth and yourWidgetHeight you can achieve what you want
Edit
Here is a concept of what am trying to explain
// In variable declaration scope
GlobalKey youTextKey = GlobalKey();
ValueNotifier widthNotifier = ValueNotifier(0);
ValueNotifier heightNotifier = ValueNotifier(0);
// In initState
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
widthNotifier.value = youTextKey.currentContext.size.width - 1.0;
heightNotifier.value = youTextKey.currentContext.size.height - 5.0;
}
});
// Your row
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'Winner: ',
style: TextStyle(fontSize: 34),
),
Stack(
children: [
Text('YOU!!!', style: TextStyle(fontSize: 34), key: youTextKey,),
ValueListenableBuilder(
valueListenable: widthNotifier,
builder: (context, width, _) {
return ValueListenableBuilder(
valueListenable: heightNotifier,
builder: (context, height, _) {
return Container(
width: width,
height: height,
color: Colors.amber,
);
},
);
},
),
],
),
],
)
Using Positioned.Fill solved the problem:
Positioned.Fill Creates a Positioned object with [left], [top], [right], and [bottom] set to 0.0 unless a value for them is passed.
so the Positioned child get stretched to the edges of the stack's biggest child.
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'Winner: ',
style: TextStyle(fontSize: 34),
),
Stack(
children: [
Text('YOU!!!', style: TextStyle(fontSize: 34)),
Positioned.fill(
child: Container(
color: Colors.amber,
),
)
],
),
],
),
I have an issue with refactoring my code of DropdownButton widget in Flutter. I have simple DropdownButton.
DropdownButton(
items: [
DropdownMenuItem(
child: Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text('Ascending'),
if (widget.currentDateSortOrder == SortOrderType.Ascending)
Icon(Icons.check)
],
),
),
value: 'Asc',
),
DropdownMenuItem(
child: Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text('Descending'),
if (widget.currentDateSortOrder == SortOrderType.Descending)
Icon(Icons.check)
],
),
),
value: 'Desc',
)
],
onChanged: (itemIdentifier) {
...
},
)
I want to move DropdownMenuItem to separate widget to make my widget tree leaner. So I then moved it.
import 'package:flutter/material.dart';
class FileListDropdownMenuItem extends StatelessWidget {
final String labelText;
final bool showSelectedMark;
final String itemValue;
FileListDropdownMenuItem(this.labelText, this.showSelectedMark, this.itemValue);
#override
Widget build(BuildContext context) {
return DropdownMenuItem(
child: Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(labelText),
if (showSelectedMark)
Icon(Icons.check)
],
),
),
value: itemValue,
);
}
}
And when I'm trying to use it in DropdownButton like this:
...
items: [
FileListDropdownMenuItem(
'Ascending',
widget.currentDateSortOrder == SortOrderType.Ascending,
'Asc')
],
...
I get this error:
The argument type 'List<FileListDropdownMenuItem>' can't be assigned to the parameter type 'List<DropdownMenuItem<dynamic>>'.
Is there a way to make such approach work? I know that I can leave DropdownMenuItem in DropdownButton and move only 'child' property of it to the separate widget. However then I would have to manage 'value' and other properties of DropdownMenuItem in the main file.
DropdownButton requires its items to be List<DropdownMenuItem>. But your class, FileListDropdownMenuItem, extends just StatelessWidget. If you want to use it as replacement for DropdownMenuItem, you should extend it:
class FileListDropdownMenuItem extends DropdownMenuItem {
FileListDropdownMenuItem(
String labelText,
bool showSelectedMark,
String itemValue,
) : super(
child: Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(labelText),
if (showSelectedMark)
Icon(Icons.check)
],
),
),
value: itemValue,
);
}