Creating responsive layouts is a crucial aspect of modern app development, ensuring that your application looks and functions well on various screen sizes and devices. Flutter provides powerful tools like Flexible and Expanded widgets to achieve this flexibility. These widgets allow you to manage how child widgets occupy available space within a row, column, or flex container, leading to adaptive and dynamic user interfaces.
Understanding Flexible and Expanded in Flutter
Both Flexible and Expanded widgets are used to control how a child widget utilizes the available space in a flex container, such as Row or Column. They allow you to create layouts that adapt to different screen sizes by distributing space proportionally among child widgets.
Flexible Widget
The Flexible widget allows a child to either fill the available space or shrink to fit its content, depending on the fit property:
FlexFit.loose: The child can have its own size. The widget does not force the child to fill all the available space.FlexFit.tight: The child must fill the available space. This is the default behavior.
Expanded Widget
The Expanded widget is essentially a Flexible widget with FlexFit.tight, meaning it forces the child to fill the available space in the flex container. It’s a shorthand for Flexible(fit: FlexFit.tight, child: ...) and is commonly used to divide space equally among multiple widgets.
Implementing Responsive Layouts with Flexible and Expanded
Here’s how you can use Flexible and Expanded to create responsive layouts in Flutter:
Basic Usage of Expanded
Use Expanded to distribute space equally among multiple widgets:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Expanded Example'),
),
body: Row(
children: [
Expanded(
child: Container(
color: Colors.red,
child: Center(
child: Text('Widget 1', style: TextStyle(color: Colors.white)),
),
),
),
Expanded(
child: Container(
color: Colors.green,
child: Center(
child: Text('Widget 2', style: TextStyle(color: Colors.white)),
),
),
),
Expanded(
child: Container(
color: Colors.blue,
child: Center(
child: Text('Widget 3', style: TextStyle(color: Colors.white)),
),
),
),
],
),
),
);
}
}
In this example, three Expanded widgets equally divide the horizontal space within the Row. Each widget occupies one-third of the available space.
Using Flexible with FlexFit.loose
When you use Flexible with FlexFit.loose, the child widget takes only the space it needs, and the remaining space is left unoccupied:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Flexible Loose Example'),
),
body: Row(
children: [
Flexible(
fit: FlexFit.loose,
child: Container(
color: Colors.red,
padding: EdgeInsets.all(20),
child: Text('Widget 1', style: TextStyle(color: Colors.white)),
),
),
Flexible(
fit: FlexFit.tight,
child: Container(
color: Colors.green,
child: Center(
child: Text('Widget 2', style: TextStyle(color: Colors.white)),
),
),
),
],
),
),
);
}
}
In this example, Widget 1 takes only the space needed to display the text, and Widget 2 fills the remaining space due to FlexFit.tight.
Flex Factor
Both Flexible and Expanded widgets accept a flex factor, which determines how the available space is distributed among multiple flexible widgets. By default, flex is 1, meaning space is divided equally. You can use different flex values to distribute space proportionally:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Flex Factor Example'),
),
body: Row(
children: [
Expanded(
flex: 2,
child: Container(
color: Colors.red,
child: Center(
child: Text('Widget 1', style: TextStyle(color: Colors.white)),
),
),
),
Expanded(
flex: 1,
child: Container(
color: Colors.green,
child: Center(
child: Text('Widget 2', style: TextStyle(color: Colors.white)),
),
),
),
],
),
),
);
}
}
Here, Widget 1 takes two-thirds of the available space, while Widget 2 takes one-third, because Widget 1 has a flex value of 2 and Widget 2 has a flex value of 1.
Advanced Responsive Layout Techniques
Mixing Flexible and Expanded
Combine Flexible and Expanded to achieve more complex layouts. For example:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Mixing Flexible and Expanded'),
),
body: Column(
children: [
Flexible(
fit: FlexFit.loose,
child: Container(
color: Colors.yellow,
padding: EdgeInsets.all(16.0),
child: Text('Header Widget'),
),
),
Expanded(
child: Container(
color: Colors.grey[200],
child: Center(
child: Text('Content Area'),
),
),
),
Flexible(
fit: FlexFit.loose,
child: Container(
color: Colors.cyan,
padding: EdgeInsets.all(16.0),
child: Text('Footer Widget'),
),
),
],
),
),
);
}
}
In this case, the header and footer widgets take only the necessary space, while the content area fills the remaining vertical space.
Adaptive Design Based on Screen Size
You can adjust the flex factors based on the screen size to create different layouts for different devices. For example, use MediaQuery to detect the screen size and adjust the layout accordingly:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Adaptive Layout'),
),
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
if (constraints.maxWidth > 600) {
// Wide screen layout
return Row(
children: [
Expanded(
flex: 2,
child: Container(
color: Colors.red,
child: Center(
child: Text('Widget 1 (Wide)', style: TextStyle(color: Colors.white)),
),
),
),
Expanded(
flex: 1,
child: Container(
color: Colors.green,
child: Center(
child: Text('Widget 2 (Wide)', style: TextStyle(color: Colors.white)),
),
),
),
],
);
} else {
// Narrow screen layout
return Column(
children: [
Expanded(
child: Container(
color: Colors.red,
child: Center(
child: Text('Widget 1 (Narrow)', style: TextStyle(color: Colors.white)),
),
),
),
Expanded(
child: Container(
color: Colors.green,
child: Center(
child: Text('Widget 2 (Narrow)', style: TextStyle(color: Colors.white)),
),
),
),
],
);
}
},
),
),
);
}
}
In this adaptive layout, for screens wider than 600 logical pixels, widgets are arranged in a Row with a 2:1 space distribution. For narrower screens, widgets are arranged in a Column, each taking up equal space.
Conclusion
The Flexible and Expanded widgets are essential for creating responsive layouts in Flutter. By using these widgets effectively, you can build UIs that adapt to various screen sizes, ensuring a consistent and enjoyable user experience on all devices. Experiment with different flex factors and fit options to achieve the desired layout for your application. Combining these techniques with adaptive design principles based on screen size will result in robust, flexible, and responsive Flutter applications.