Responsive layout article in Flutter

image

Sergey Shalamov

image

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.

Method 1: Strict layout with fixed sizes of widgets and SizedBoxes as paddings

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

Method 2: Using MediaQuery to scale paddings and font sizes based on screen size

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

Method 3: Using Spacer widget to make flexible paddings

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

Method 4: Using ListView to make a scrollable layout

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

Method 5: Using CustomScrollView and Slivers

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:

  • Prioritize accessibility and test with different settings.
  • Test on multiple screen sizes. Some packages like device_preview can help to quickly change the screen size.
  • Use MediaQuery and textScaleFactor for scaling dimensions.
  • Implement flexible widgets like Spacer, Expanded, and Flexible.
  • Create scrollable layouts with ListView or CustomScrollView when necessary.
  • Avoid fixed-size containers for text content.

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.

Brainstorming the idea on how to build a great mobile app?

Share your idea with us, and we’ll come up with the best development solution for your case.

Get in touch today