I have a space where I to show either widget1 or widget2 (and never both). How?
In fact, widget1 to contain a simple case of retrieved information and a button to switch to show widget2 (with more information) instead.
The only solution that I came up is to use Column(children: [widget1, widget2]) and tweak visibility of both widgets, but that's ugly. For example, there is no indication what is better Column or Row.
To do this, you have multiple options:
(1) You can use conditional spread operator inside a column/widget
Column(
children: [
if (yourCondition == true) ...[
const Text("Widget 1")
] else ...[
const Text("Widget 2")
],
],
),
(2) You can use ternary operator inside a column/widget
Column(
children: [
yourCondition == true
? const Text("Widget 1")
: const Text("Widget 2"),
],
),
(3) You can use Visibility widget inside a column/widget
Column(
children: [
Visibility(
visible: yourCondition == true,
child: const Text("Widget 1"),
),
Visibility(
visible: yourCondition == false,
child: const Text("Widget 2"),
),
],
),
For better code readability, use ternary operator if you have only one condition or non-nested conditions
For better code readability, use conditional spread operator if you have multiple conditions or nested conditions
Otherwise use Visibility widget (bur not recommended)
To simply solve that, you can use a ternary operator with a widget like a container (or any that has a single child):
Container(
child: condition == true
? Text("Widget 1")
: Text("Widget 2")
)
Related
I would like to create a list with expandable items/children based on data provided from the stream. Expanded items would include textual details and actions which the user can take.
One of the key requirements for this layout is that - at any time only one list item can be expanded.
From what I've managed to found out - with current out-of-the-box Flutter widgets this needs to be managed programmatically. Once user expands one tile to set values as not-expanded for all other tiles and rebuild UI.
I've managed to make this work in two ways:
Using ListView.builder & ExpansionTile widgets as children
Using ExpansionPanelList & ExpansionPanel widgets as children
Both approached were combined with Riverpod's StateProvider to keep state of the selected/expanded item and invoke rebuild once the state changes (user taps on a item/tile).
Both approaches have its trade-offs from UI perspective - e.g. ExpansionPanelList children padding is poor looking and cannot be modified. Then we have ExpansionTile approach which looses animation once "key" is set with unique PageStorageKey - a necessary config in order for ExpansionTile to save and restore its expanded state (basically programmatic expanding and collapsing of tiles does not work without this...).
What I don't understand is the performance impact of both approached and which one is better in this regard.
How does setting unique key: param and rebuilding ExpansionTile widgets compare ExpansionPanelList and generating list of its items (List<ExpansionPanel>) with each widget rebuild - in order to define which Panel is expanded and which is not.
Intuitively it seems that ListView.builder & ExpansionTile approach is better - but I am not sure what is the impact of setting "key:". Please help in understanding which way is better performance wise.
You can see bellow implementation for both approaches.
ListView.builder & ExpansionTile approach
return ListView.builder(
padding: EdgeInsets.all(10),
itemCount: data.length,
itemBuilder: ((context, index) {
return ExpansionTile(
key: PageStorageKey("${DateTime.now().millisecondsSinceEpoch}"),
initiallyExpanded: (selectedItem == data[index].id.toString()),
onExpansionChanged: ((value) =>
ref.read(selectedProductListItem.notifier).state = (value == true) ? data[index].id.toString() : null),
tilePadding: EdgeInsets.all(10),
leading: Icon(Icons.account_box),
title: Text(data[index].name, style: TextStyle(fontWeight: FontWeight.bold)),
childrenPadding: EdgeInsets.all(10),
expandedAlignment: Alignment.center,
children: [
Text(data[index].description),
SizedBox(height: 10),
ElevatedButton(onPressed: () {}, child: Text('Apply Now')),
],
);
}),
);
ExpansionPanelList & ExpansionPanel approach
final List<ExpansionPanel> listData = [];
for (var i = 0; i < data.length; i++) {
listData.add(
ExpansionPanel(
headerBuilder: (context, isExpanded) {
return Padding(padding: EdgeInsets.all(10), child: Text(data[i].name));
},
body: Padding(
padding: EdgeInsets.all(10),
child: Column(
children: [
Text(data[i].description),
SizedBox(height: 10),
ElevatedButton(onPressed: () {}, child: Text('Apply Now')),
],
),
),
isExpanded: (data[i].id == selectedItem),
canTapOnHeader: true,
),
);
}
return SingleChildScrollView(
padding: EdgeInsets.all(15),
child: ExpansionPanelList(
children: listData,
expansionCallback: (panelIndex, isExpanded) {
ref.read(selectedProductListItem.notifier).state = isExpanded ? null : data[panelIndex].id;
},
),
);
I have a stateless widget in Flutter that responds to changes to a property in a Provider model. When I click on an "edit" button, I toggle a boolean in my model and my reminders go into edit mode like this:
Here's a simplified version of my code:
Widget build(BuildContext context) {
//A boolean from my model
var editMode = Provider.of<ModelDashboard>(context).sidebarEditMode;
return Column(
children: [
Container(
padding: EdgeInsets.fromLTRB(editMode ? 5 : 15, 15, editMode ? 5 : 15, 12),
child: Row(
children: [
if (editMode) ...[
PPReorder(
index: index,
),
const SizedBox(width: 5),
],
//Rest of row...
],
),
),
]
);
}
So as editMode changes, it adjusts some padding and shows/hides a couple elements in my row.
I looked at some of the animation options and they don't seem right to me:
AnimatedContainer - I tried animating the width to 0 and I got overflow errors.
AnimatedOpacity - The item still takes up space when its opacity is 0.
AnimatedPositioned - My items are not positioned within a Stack.
What would be the appropriate way to animate the appearance of these conditional items in my Row?
For this you can use AnimatedCrossFade.
I had almost same situation where I need to show and hide one element with animation and AnimatedCrossFade is easiest and best way to implement animation.
Widget build(BuildContext context) {
//A boolean from my model
var editMode = Provider.of<ModelDashboard>(context).sidebarEditMode;
return Column(
children: [
Container(
padding: EdgeInsets.fromLTRB(editMode ? 5 : 15, 15, editMode ? 5 : 15, 12),
child: Row(
children: [
AnimatedCrossFade(
duration: const Duration(seconds: 3),
firstChild: Row(
children: [
PPReorder(
index: index,
),
const SizedBox(width: 5),
],
),
secondChild: Container(),
crossFadeState: editMode
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
)
//Rest of row...
],
),
),
]
);
}
You can play around with this as per your requirments.
I wanna use multiple widget in ternary operator like this.
But it's error.Can I do?
Container(
child: (if value != null)
? Widget1(...),Widget2(...)
: const SizedBox.shrink(),
)
Try wrapping each of those Widgets inside a Column() (or into an other widget you find appropriate for your app as Row).
Container(
child: value != null
? Column(children:
[Widget1(...),
Widget2(...)]
)
: const SizedBox.shrink(),
)
Container only support a single child widget, so you cannot use it this way. What you can do is use something like Column or Row:
Container(
child: value != null
? Column(children: [Widget1(...), Widget2(...)])
: const SizedBox.shrink(),
)
I’m getting an error message
“The argument for the named parameter ‘body’ was already specified.”
You can't. I'm not sure what you need to do. But here's a list of widgets and Codes that might help you achieve what you want:
Column widget :List widgets in a column
body: Column(
children: [
Container(),
Container(),
Container()
],
)
Stack widget :Stack widgets on top of each other
body: Stack(
children: [
Container(),
Container(),
Container()
],
)
PageView widget : Create pages that users can navigate to by swiping the screen left and right
body: PageView(
children: [
Container(),
Container(),
Container()
],
)
I need to fix a minimum width to my Column Widgets. Inside each of them, I have Text Widgets which can be very short or very long. I need to fix a minimum width to them in order to have an acceptable size of Column even if the text is short. The other Column need obviously to adapt himself.
Row(children: [
Column(
children: [
Container(
constraints: BoxConstraints(minWidth: 80), // do not work
child: Text("short text"),
),
],
),
Column(
children: [
Container(
constraints: BoxConstraints(minWidth: 110), // do not work
child: RichText(
text: TextSpan(
text:"very very longggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg text")),
),
],
),
],
)
There's probably a dozen ways to do what you want. And likely none of them straightforward or easy to understand. (The subject of constraints & sizes is quite complicated. See this constraints page for more examples & explanations.)
Here's one potential solution.
This will set a minimum width for the blue column (based on stepWidth), but will expand/grow if the text (child) inside wants to.
The yellow column will resize to accommodate the blue column.
class ExpandedRowPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Expanded Row Page'),
),
body: SafeArea(
child: Center(
child: Row(
children: [
IntrinsicWidth(
stepWidth: 100,
// BLUE Column
child: Container(
color: Colors.lightBlueAccent,
child: Column(
children: [
//Text('Short'),
Text('shrt')
],
)
),
),
// YELLOW Column
Flexible(
child: Container(
alignment: Alignment.center,
color: Colors.yellow,
child: Column(
children: [
Text('Very lonnnnnnnnnnnnnnnnnnnnnnnnnnnng texttttttttttttt'),
],
)
),
)
],
)
),
),
);
}
}
You could do the above without a Flexible yellow column, but a very long text child would cause an Overflow warning without a Flexible or Expanded wrapping widget.
A Row widget by itself has an infinite width constraint. So if a child wants to be bigger than screen width, it can, and will cause an overflow. (Try removing Flexible above and rebuild to see.)
Flexible and Expanded, used only inside Row & Column (or Flex, their superclass), checks screen width and other widgets inside a Row, and provides its children with a defined constraint size instead of infinite. Children (inside Flexible/Expanded) can now look up to parent for a constraint and size themselves accordingly.
A Text widget for example, will wrap its text when it's too wide for constraints given by Flexible/Expanded.
use FittedBox();
suppose Example:
Row(
children: [
Column(
children: [
Container(
constraints: BoxConstraints(minWidth: 80), // do not work
child: Text("short text"),
),
],
),
Column(
children: [
Container(
constraints: BoxConstraints(minWidth: 110), // do not work
child:
FittedBox(
child: RichText(
text: TextSpan(
text:
"very very longggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg text")),
),
),
],
),
],
);