How to build a responsive layout in fluent (Section 5)

Flutter is a cross platform application development framework that supports devices with large screen size changes: it can run on devices as small as smart watches or on large TVs. Using the same code base to adapt your application to such a variety of screen sizes and pixel densities is always a challenge. There are no hard and fast rules for designing responsive layouts in the shuttle. In this article, I'll show you some ways to follow when designing such layouts. Before continuing to build a responsive layout in fluent, I want to explain

How Android and iOS handle native layouts with different screen sizes. Well, let's start, but first, let's know

How many mobile application projects do you have in the Git repository?

Android method

In order to handle different screen sizes and pixel densities, the following concepts are used in Android:

1. Constraint layout

One of the revolutionary tools for UI design introduced in the Android world is? ConstraintLayout. It can be used to create flexible and responsive UI designs that adapt to different screen sizes and sizes. Constraintlayout allows you to specify the location and size of each view based on its spatial relationship with other views in the layout.

But this does not solve the problem of large devices. In this case, just stretching or resizing UI components is not the most elegant way to use screen space. This also applies to devices like smart watches, which have small screen space, and resizing components to fit the screen size may lead to strange UI.

2. Alternative layout

To solve the above problems, you can use alternative layouts for devices of different sizes. For example, you can use split views in devices such as tablets to provide a good user experience and use large screen space wisely.! [

In Android, you can define different screen sizes

Separate layout files, the Android framework will automatically handle the switching between these layouts according to the screen size of the device[

? keep abreast of application development news

3. Fragment

With? Fragment, you can extract UI logic into separate components so that when designing a multi pane layout for a large screen size, you do not have to define logic separately. You can reuse the logic you define for each fragment.

4. Vector graphics

In contrast to pixel bitmap creation, vector graphics are images defined as paths and colors in XML files. They can be scaled to any size without scaling artifacts. In Android, you can use? VectorDrawable for any type of illustration, such as icons.

iOS method

The concepts used by iOS to define responsive layout are as follows:

1. Automatic layout

? automatic layout can be used to build an adaptive interface in which you can define rules (called constraints) to manage application content. When some environmental changes (called characteristics) are detected, automatic layout will automatically readjust the layout according to the specified constraints.

2. Size grade

Size classes are features that are automatically assigned to the content area according to the size. iOS dynamically adjusts the layout according to the size category of the content area. On the iPad, size classes are also applicable when your app is running in multi tasking configuration.

3. Some UI elements

There are other UI elements that can be used to build responsive UI on iOS, such as? UIStackView,? UIViewController, and [? UISplitViewController.

How is Flutter different

Even if you are not an Android or iOS developer, you should already know how these platforms handle native responses. In Android, to display multiple UI views on a single screen, you can use Fragments, which are like reusable components that can run within an application's Activity.

You can run multiple fragments in one Activity, but you cannot run multiple activities in a single application at the same time.

In iOS, UISplitViewController manages sub view controllers with a hierarchical interface to control multiple view controllers. Now, let's continue the discussion

Flutter. Flutter introduces[

? the concept of widgets. Basically, they are building blocks that can be connected together to build the entire application.

Remember that in fluent, every screen and even the entire application are widgets!

Widgets are reusable in nature, so you don't need to learn any other concepts when building responsive layouts in fluent.

Responsiveness in fluent

As I said earlier, I'll introduce the important concepts needed to develop responsive layouts, and then you can choose how to implement them in your application.

1. Media query

You can use MediaQuery to retrieve the screen size (width / height) and orientation (vertical / horizontal). An example is as follows:


class HomePage extends StatelessWidget {

  @override

  Widget build(BuildContext context) {

    Size screenSize = MediaQuery.of(context).size;

    Orientation orientation = MediaQuery.of(context).orientation;



    return Scaffold(

      body: Container(

        color: CustomColors.android,

        child: Center(

          child: Text(

            'View\n\n' +

                '[MediaQuery width]: ${screenSize.width.toStringAsFixed(2)}\n\n' +

                '[MediaQuery orientation]: $orientation',

            style: TextStyle(color: Colors.white, fontSize: 18),

          ),

        ),

      ),

    );

  }

}

2. Layout builder

Using the [? LayoutBuilder class, you can obtain the [? BoxConstraints object, which can be used to determine the maxWidth and maxHeight of the widget.

Remember: the main difference between MediaQuery and LayoutBuilder is that MediaQuery uses the full range of the screen, not just the size of your specific icon, while LayoutBuilder can determine the maximum width and height of a specific part.

Examples of this are as follows:


class HomePage extends StatelessWidget {

  @override

  Widget build(BuildContext context) {

    Size screenSize = MediaQuery.of(context).size;



    return Scaffold(

      body: Row(

        children: [

          Expanded(

            flex: 2,

            child: LayoutBuilder(

              builder: (context, constraints) => Container(

                color: CustomColors.android,

                child: Center(

                  child: Text(

                    'View 1\n\n' +

                        '[MediaQuery]:\n ${screenSize.width.toStringAsFixed(2)}\n\n' +

                        '[LayoutBuilder]:\n${constraints.maxWidth.toStringAsFixed(2)}',

                    style: TextStyle(color: Colors.white, fontSize: 18),

                  ),

                ),

              ),

            ),

          ),

          Expanded(

            flex: 3,

            child: LayoutBuilder(

              builder: (context, constraints) => Container(

                color: Colors.white,

                child: Center(

                  child: Text(

                    'View 2\n\n' +

                        '[MediaQuery]:\n ${screenSize.width.toStringAsFixed(2)}\n\n' +

                        '[LayoutBuilder]:\n${constraints.maxWidth.toStringAsFixed(2)}',

                    style: TextStyle(color: CustomColors.android, fontSize: 18),

                  ),

                ),

              ),

            ),

          ),

        ],

      ),

    );

  }

}

3. Current direction

To determine the current orientation of the widget, you can use the [? OrientationBuilder class.

**Remember: * * this is different from the device direction you can use to retrieve MediaQuery.

Examples of this are as follows:


class HomePage extends StatelessWidget {

  @override

  Widget build(BuildContext context) {

    Orientation deviceOrientation = MediaQuery.of(context).orientation;



    return Scaffold(

      body: Column(

        children: [

          Expanded(

            flex: 2,

            child: Container(

              color: CustomColors.android,

              child: OrientationBuilder(

                builder: (context, orientation) => Center(

                  child: Text(

                    'View 1\n\n' +

                        '[MediaQuery orientation]:\n$deviceOrientation\n\n' +

                        '[OrientationBuilder]:\n$orientation',

                    style: TextStyle(color: Colors.white, fontSize: 18),

                  ),

                ),

              ),

            ),

          ),

          Expanded(

            flex: 3,

            child: OrientationBuilder(

              builder: (context, orientation) => Container(

                color: Colors.white,

                child: Center(

                  child: Text(

                    'View 2\n\n' +

                        '[MediaQuery orientation]:\n$deviceOrientation\n\n' +

                        '[OrientationBuilder]:\n$orientation',

                    style: TextStyle(color: CustomColors.android, fontSize: 18),

                  ),

                ),

              ),

            ),

          ),

        ],

      ),

    );

  }

}

4. Scalability and flexibility

A particularly useful widget in aColumn or aRow is Expandedand Flexible. The "extension" extends the children of rows, columns, or Flex to fill the available space, while "flexible" does not necessarily fill the entire available space. An example shows various combinations given

Expanded and Flexible are as follows:


class HomePage extends StatelessWidget {

  @override

  Widget build(BuildContext context) {

    return Scaffold(

      backgroundColor: Colors.white,

      body: SafeArea(

        child: Column(

          children: [

            Row(

              children: [

                ExpandedWidget(),

                FlexibleWidget(),

              ],

            ),

            Row(

              children: [

                ExpandedWidget(),

                ExpandedWidget(),

              ],

            ),

            Row(

              children: [

                FlexibleWidget(),

                FlexibleWidget(),

              ],

            ),

            Row(

              children: [

                FlexibleWidget(),

                ExpandedWidget(),

              ],

            ),

          ],

        ),

      ),

    );

  }

}



class ExpandedWidget extends StatelessWidget {

  @override

  Widget build(BuildContext context) {

    return Expanded(

      child: Container(

        decoration: BoxDecoration(

          color: CustomColors.android,

          border: Border.all(color: Colors.white),

        ),

        child: Padding(

          padding: const EdgeInsets.all(16.0),

          child: Text(

            'Expanded',

            style: TextStyle(color: Colors.white, fontSize: 24),

          ),

        ),

      ),

    );

  }

}



class FlexibleWidget extends StatelessWidget {

  @override

  Widget build(BuildContext context) {

    return Flexible(

      child: Container(

        decoration: BoxDecoration(

          color: CustomColors.androidAccent,

          border: Border.all(color: Colors.white),

        ),

        child: Padding(

          padding: const EdgeInsets.all(16.0),

          child: Text(

            'Flexible',

            style: TextStyle(color: CustomColors.android, fontSize: 24),

          ),

        ),

      ),

    );

  }

}

5. FractionallySizedBox

The FractionallySizedBox widget contributes to a small portion of the total free space of the size and its children. It is particularly useful in internal Expanded or Flexible widgets. An example is as follows:


class HomePage extends StatelessWidget {

  @override

  Widget build(BuildContext context) {

    return Scaffold(

      backgroundColor: Colors.white,

      body: SafeArea(

        child: Column(

          mainAxisAlignment: MainAxisAlignment.start,

          children: [

            Row(

              crossAxisAlignment: CrossAxisAlignment.start,

              children: [

                FractionallySizedWidget(widthFactor: 0.4),

              ],

            ),

            Row(

              crossAxisAlignment: CrossAxisAlignment.start,

              children: [

                FractionallySizedWidget(widthFactor: 0.6),

              ],

            ),

            Row(

              crossAxisAlignment: CrossAxisAlignment.start,

              children: [

                FractionallySizedWidget(widthFactor: 0.8),

              ],

            ),

            Row(

              crossAxisAlignment: CrossAxisAlignment.start,

              children: [

                FractionallySizedWidget(widthFactor: 1.0),

              ],

            ),

          ],

        ),

      ),

    );

  }

}



class FractionallySizedWidget extends StatelessWidget {

  final double widthFactor;

  FractionallySizedWidget({@required this.widthFactor});



  @override

  Widget build(BuildContext context) {

    return Expanded(

      child: FractionallySizedBox(

        alignment: Alignment.centerLeft,

        widthFactor: widthFactor,

        child: Container(

          decoration: BoxDecoration(

            color: CustomColors.android,

            border: Border.all(color: Colors.white),

          ),

          child: Padding(

            padding: const EdgeInsets.all(16.0),

            child: Text(

              '${widthFactor * 100}%',

              style: TextStyle(color: Colors.white, fontSize: 24),

            ),

          ),

        ),

      ),

    );

  }

}

6. Aspect ratio

You can use the? AspectRatio widget to adjust the child to a specific aspect ratio. This widget first tries the maximum width allowed by the layout constraint, and then determines the height by applying the given aspect ratio to the width.

dart

class HomePage extends StatelessWidget {

  @override

  Widget build(BuildContext context) {

    return Scaffold(

      backgroundColor: Colors.white,

      body: SafeArea(

        child: Column(

          children: [

            AspectRatioWidget(ratio: '16 / 9'),

            AspectRatioWidget(ratio: '3 / 2'),

          ],

        ),

      ),

    );

  }

}



class AspectRatioWidget extends StatelessWidget {

  final String ratio;



  AspectRatioWidget({@required this.ratio});



  @override

  Widget build(BuildContext context) {

    return AspectRatio(

      aspectRatio: Fraction.fromString(ratio).toDouble(),

      child: Container(

        decoration: BoxDecoration(

          color: CustomColors.android,

          border: Border.all(color: Colors.white),

        ),

        child: Padding(

          padding: const EdgeInsets.all(16.0),

          child: Center(

            child: Text(

              'AspectRatio - $ratio',

              style: TextStyle(color: Colors.white, fontSize: 24),

            ),

          ),

        ),

      ),

    );

  }

}

We've studied most of the important concepts needed to build a responsive layout in fluent, except one. Let's learn the last concept when building a sample responsive application.

Building responsive applications

Now, we will apply some of the concepts I described in the previous section. In addition, you will learn another important concept of building a large screen layout: split view. We will build a

Flow example chat application design.

The application will consist mainly of two main screens:

  • Home page (PeopleView, BookmarkView, ContactView)
  • Chat page (PeopleView, ChatView)

homepage

After startup, the main screen of the application will be HomePage. It consists of two types of Views:

  • HomeViewSmall (including AppBar, Drawer, BottomNavigationBar, and DestinationView)
  • HomeViewLarge (DestinationView composed of split view, MenuWidget, and)
dart

class _HomePageState extends State {

  int _currentIndex = 0;



  @override

  Widget build(BuildContext context) {

    return Scaffold(

      body: LayoutBuilder(

        builder: (context, constraints) {

          if (constraints.maxWidth < 600) {

            return HomeViewSmall();

          } else {

            return HomeViewLarge();

          }

        },

      ),

    );

  }

}

Here, LayoutBuilder is a widget used to determine the switching between maxWidth and HomeViewSmall and HomeViewLarge.

dart

class _HomeViewSmallState extends State {

  int _currentIndex = 0;



  @override

  Widget build(BuildContext context) {

    return Scaffold(

      appBar: AppBar(

          // ...

      ),

      drawer: Drawer(

          // ...

      ),

      bottomNavigationBar: BottomNavigationBar(

          // ...

      ),

      body: IndexedStack(

        index: _currentIndex,

        children: allDestinations.map((Destination destination) {

          return DestinationView(destination);

        }).toList(),

      ),

    );

  }

}

IndexedStackwithDestinationView is used to create an index based on the BottomNavigationBar

If you want more information, check out the GitHub repository for this sample application provided at the end of this article.

dart

class _HomeViewLargeState extends State {

  int _index = 0;



  @override

  Widget build(BuildContext context) {

    return Container(

      child: Row(

        crossAxisAlignment: CrossAxisAlignment.start,

        mainAxisAlignment: MainAxisAlignment.start,

        children: [

          Expanded(

            flex: 2,

            child: MenuWidget(

              selectedIndex: _index,

              onTapped: (selectedIndex) {

                setState(() {

                  _index = selectedIndex;

                });

              },

            ),

          ),

          Expanded(

            flex: 3,

            child: IndexedStack(

              index: _index,

              children: allDestinations.map((Destination destination) {

                return DestinationView(destination);

              }).toList(),

            ),

          ),

        ],

      ),

    );

  }

}

For the large screen, we will display the split view DestinationView containing the MenuWidget and. As you can see, it's really easy to create split views in fluent. You just use a to place them side by side in the Row, and then, to fill the entire space, you just wrap the two views with the Expanded widget. You can also define the flex property Expanded for widgets, which lets you specify how many screens each widget should cover (Flex is set to 1 by default).!

But now, if you move to a specific screen and switch between views, you will lose the context of the page; That is, you will always return to the first page, that is

Chats. To solve this problem, I used multiple callback functions to return the selected page to the HomePage. In fact, you should use state management technology to deal with this situation. Since the sole purpose of this article is to teach you how to build responsive layouts, I won't cover any complexity of state management. modify

HomeViewSmall:

dart

class HomeViewSmall extends StatefulWidget {

  final int currentIndex;

  /// Callback function

  final Function(int selectedIndex) onTapped;

  HomeViewSmall(this.currentIndex, this.onTapped);



  @override

  _HomeViewSmallState createState() => _HomeViewSmallState();

}



class _HomeViewSmallState extends State {

  int _currentIndex = 0;



  @override

  void initState() {

    super.initState();

    _currentIndex = widget.currentIndex;

  }



  @override

  Widget build(BuildContext context) {

    return Scaffold(

      // ...

      bottomNavigationBar: BottomNavigationBar(

        // ...

        currentIndex: _currentIndex,

        onTap: (int index) {

          setState(() {

            _currentIndex = index;

            // Invoking the callback

            widget.onTapped(_currentIndex);

          });

        },

        items: allDestinations.map((Destination destination) {

          return BottomNavigationBarItem(

            icon: Icon(destination.icon),

            label: destination.title,

          );

        }).toList(),

      ),

    );

  }

}

Modify HomeViewLarge:

dart

class HomeViewLarge extends StatefulWidget {

  final int currentIndex;

  /// Callback function

  final Function(int selectedIndex) onTapped;

  HomeViewLarge(this.currentIndex, this.onTapped);



  @override

  _HomeViewLargeState createState() => _HomeViewLargeState();

}



class _HomeViewLargeState extends State {

  int _index = 0;



  @override

  void initState() {

    super.initState();

    _index = widget.currentIndex;

  }



  @override

  Widget build(BuildContext context) {

    return Container(

      child: Row(

        crossAxisAlignment: CrossAxisAlignment.start,

        mainAxisAlignment: MainAxisAlignment.start,

        children: [

          Expanded(

            flex: 2,

            child: MenuWidget(

              selectedIndex: _index,

              onTapped: (selectedIndex) {

                setState(() {

                  _index = selectedIndex;

                  // Invoking the callback

                  widget.onTapped(_index);

                });

              },

            ),

          ),

          // ...

        ],

      ),

    );

  }

}
```dart

modify`HomePage`: 

dart

class HomePage extends StatefulWidget {

@override

_HomePageState createState() => _HomePageState();

}

class _HomePageState extends State {

int _currentIndex = 0;

@override

Widget build(BuildContext context) {

return Scaffold(

  body: LayoutBuilder(

    builder: (context, constraints) {

      if (constraints.maxWidth < 600) {

        return HomeViewSmall(_currentIndex, (index) {

          setState(() {

            _currentIndex = index;

          });

        });

      } else {

        return HomeViewLarge(_currentIndex, (index) {

          setState(() {

            _currentIndex = index;

          });

        });

      }

    },

  ),

);

}

}

Now, your response is complete`HomePage`Completed.

### Chat page

This will be similar to`HomePage`,However, it will contain the following two views:

- **ChatViewSmall**(include`AppBar`,`ChatList`,and`SendWidget`Plug in)
- **ChatViewLarge**(include`PeopleView`,`ChatList`,and`SendWidget`Plug in)

```dart
dart

class ChatPage extends StatelessWidget {

  final Color profileIconColor;

  ChatPage(this.profileIconColor);



  @override

  Widget build(BuildContext context) {

    return Scaffold(

      body: OrientationBuilder(

        builder: (context, orientation) => LayoutBuilder(

          builder: (context, constraints) {

            double breakpointWidth = orientation == Orientation.portrait ? 600 : 800;



            if (constraints.maxWidth < breakpointWidth) {

              return ChatViewSmall(profileIconColor);

            } else {

              return ChatViewLarge(profileIconColor);

            }

          },

        ),

      ),

    );

  }

}

Here, I use OrientationBuilder LayoutBuilder to change the breakpointWidth according to the direction, because I don't want PeopleView to be displayed on a small screen phone when it is in landscape mode.

dart

class ChatViewSmall extends StatelessWidget {

  final Color profileIconColor;

  ChatViewSmall(this.profileIconColor);



  @override

  Widget build(BuildContext context) {

    return Scaffold(

      appBar: AppBar(

      ),

      body: Container(

        color: Colors.white,

        child: Column(

          children: [

            Expanded(child: ChatList(profileIconColor)),

            SendWidget(),

          ],

        ),

      ),

    );

  }

}
dart

class ChatViewLarge extends StatelessWidget {

  final Color profileIconColor;

  ChatViewLarge(this.profileIconColor);



  @override

  Widget build(BuildContext context) {

    return Container(

      child: Row(

        children: [

          Expanded(

            flex: 2,

            child: SingleChildScrollView(

              child: PeopleView(),

            ),

          ),

          Expanded(

            flex: 3,

            child: Container(

              color: Colors.white,

              child: Column(

                children: [

                  Expanded(child: ChatList(profileIconColor)),

                  SendWidget(),

                ],

              ),

            ),

          ),

        ],

      ),

    );

  }

}

conclusion

We have successfully created a fully responsive application in fluent. You can still make many improvements to this application, one of which may be to define different fontsizes according to different screen sizes. Some of the amazing Flutter plug-ins you can use when using responsiveness are as follows:

  • ?device_preview
  • ?device_preview
  • ?responsive_builde
  • ?https://pub.dev/packages/responsive_framework

Posted on Tue, 30 Nov 2021 15:24:52 -0500 by renegade44