When developing a mobile application, ensuring the layout looks good on all screen sizes is important. In Flutter, there are several ways to implement a responsive layout. In this article, we will go through five different methods, starting with the simplest but with the lowest responsiveness and ending with the most complex one but with the highest responsiveness. By the end of this article, you will better understand how to create a responsive layout in Flutter.
This is the simplest method to implement a responsive layout in Flutter. This method uses fixed sizes for widgets and SizedBoxes as paddings. The layout looks good only for screens with the same dimensions as the design screen. However, if the actual screen size differs from the design screen size, the layout may not look better.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Responsive Screen'),
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: const [
SizedBox(height: 20),
HeaderText(),
SizedBox(height: 24),
ProfileAvatar(),
SizedBox(height: 16),
NameText(),
RoleText(),
SizedBox(height: 16),
AddressText(),
SizedBox(height: 72),
DescriptionText(),
SizedBox(height: 24),
ContactButton(),
SizedBox(height: 24),
],
),
),
);
}
400×715 looks good
375×667 – bottom overflow
428×962 – bottom empty space
This method is more responsive than the first one. In this method, we use MediaQuery to scale paddings and font sizes based on the screen size. The layout looks good if the actual screen size is close to the design screen size. However, on small screens, the text may be hard to read on small screens due to the small font size, and on big screens, we still have empty bottom space because of different aspect ratios.
@override
Widget build(BuildContext context) {
const designHeight = 715.0;
const designWidth = 400.0;
final screenHeight = MediaQuery.of(context).size.height - MediaQuery.of(context).padding.vertical;
final screenWidth = MediaQuery.of(context).size.width - MediaQuery.of(context).padding.horizontal;
final scaleHeightFactor = screenHeight / designHeight;
final scaleWidthFactor = screenWidth / designWidth;
final scaleFontFactor = scaleWidthFactor;
return MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaleFactor: scaleFontFactor,
),
child: Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(kToolbarHeight * scaleHeightFactor),
child: AppBar(
title: const Text('Responsive Screen'),
),
),
body: Padding(
padding: EdgeInsets.symmetric(horizontal: 16 * scaleWidthFactor),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(height: 20 * scaleHeightFactor),
const HeaderText(),
SizedBox(height: 24 * scaleHeightFactor),
ProfileAvatar(radius: 32 * scaleWidthFactor),
SizedBox(height: 16 * scaleHeightFactor),
const NameText(),
const RoleText(),
SizedBox(height: 16 * scaleHeightFactor),
const AddressText(),
SizedBox(height: 72 * scaleHeightFactor),
DescriptionText(
padding: EdgeInsets.symmetric(
horizontal: 12 * scaleWidthFactor,
vertical: 12 * scaleHeightFactor,
),
),
SizedBox(height: 24 * scaleHeightFactor),
ContactButton(height: 50 * scaleHeightFactor),
SizedBox(height: 24 * scaleHeightFactor),
],
),
),
),
);
}
400×715 looks good
375×667 – text is hard to read since the screen size is small
428×962 – bottom empty space
In this method, we use the Spacer widget to make flexible paddings. Layout looks good on almost all screens, but on tiny screens, the height of all widgets may be bigger than the screen height, and the bottom widgets may not be visible.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Responsive Screen'),
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: const [
SizedBox(height: 20),
HeaderText(),
SizedBox(height: 24),
ProfileAvatar(),
SizedBox(height: 16),
NameText(),
RoleText(),
SizedBox(height: 16),
AddressText(),
SizedBox(height: 72),
DescriptionText(),
SizedBox(height: 24),
ContactButton(),
SizedBox(height: 24),
],
),
),
);
}
428×926 looks good
357×667 looks good
It appears to work well on both small and large screens; however, when implementing a responsive layout, it’s important to consider that users may increase the text scale in accessibility settings, which can cause each text widget to enlarge. Let’s do this to see what it looks like:
375×667 with text scale 1.5 – bottom overflow
In this method, we use a ListView to make the layout scrollable. We also replace Spacer with SizedBox to return fixed paddings since Spacer doesn’t work inside ListView. This method looks good on small screens, but on big screens, there is a lot of empty space at the bottom of the screen.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Responsive Screen'),
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: const [
SizedBox(height: 20),
HeaderText(),
SizedBox(height: 24),
ProfileAvatar(),
SizedBox(height: 16),
NameText(),
RoleText(),
SizedBox(height: 16),
AddressText(),
SizedBox(height: 72),
DescriptionText(),
SizedBox(height: 24),
ContactButton(),
SizedBox(height: 24),
],
),
),
);
}
357×667 with text scale 1.5 looks good
428×962 – bottom empty space
In this method, we use a CustomScrollView and Slivers to create a layout that is scrollable on small screens but has flexible space at the bottom on big screens. This method can be more complex but looks good on any screen size. The key to this approach is determining which parts of the layout should have a flexible or fixed size.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Responsive Screen'),
),
body: CustomScrollView(
slivers: [
SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 16),
sliver: SliverList(
delegate: SliverChildListDelegate(
const [
SizedBox(height: 20),
HeaderText(),
SizedBox(height: 24),
ProfileAvatar(),
SizedBox(height: 16),
NameText(),
RoleText(),
SizedBox(height: 16),
AddressText(),
],
),
),
),
SliverFillRemaining(
hasScrollBody: false,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.end,
children: const [
SizedBox(height: 72),
DescriptionText(),
SizedBox(height: 24),
ContactButton(),
SizedBox(height: 24),
],
),
),
),
],
),
);
}
357×667 with text scale 1.5 gets scrollable and looks good
428×926 looks good with flexible space in the middle
In this article, we have gone through five different methods of implementing responsive layout in Flutter. We started with the simplest method, which uses fixed sizes of widgets and SizedBoxes as paddings, and ended with the most complex method, which uses CustomScrollView and Slivers.
In conclusion, creating a responsive layout in Flutter is essential for providing a seamless user experience across various devices with different screen sizes. We have explored five different methods to achieve responsive layouts, each with its pros and cons. Choosing the right method depends on your specific application requirements and desired level of responsiveness.
To ensure a successful implementation, keep in mind the following tips:
By following these guidelines and carefully evaluating the methods discussed in this article, you can create a responsive layout in Flutter that caters to a diverse range of users and devices.
To check the source code for each method and test on various screens, please follow this repository.