In this article, you will learn how to convert any user interface image, piece, or screen into Flutter code.

This is not a tutorial on building an app. It is rather a guide that will help you implement any UI you come across into an app you already have. This tutorial also explains a wide variety of UI concepts in Flutter.

Table of Contents

What is Flutter?

Flutter is an open source framework by Google for building beautiful, natively compiled, multi-platform applications from a single codebase. – (source: flutter.dev)

In Flutter, contrary to most frameworks, Dart is the only programming language you use to code. This is an underemphasized benefit of Flutter. Especially for a tool that can build desktop, mobile, and web applications.

Most UI platforms use more than one language. For example, in front-end web development, you have to write HTML, CSS, and JavaScript. For Android, you have to write Kotlin (or Java) and XML. But in Flutter, it's just one language: Dart.

Coupled with the only-one-programming-language benefit, Flutter is simple because everything in Flutter is a widget. For example AnimatedWidget, BottomNavigationBar, Container, Drawer, ElevatedButton, FormField, Image, Opacity, Padding, ...

This is part of what makes Flutter easy to use – it's basically plain English. Widget names reflect what they are and their properties are easy to understand.

Widgets in Flutter

A widget is a Dart class that either extends StatefulWidget or StatelessWidget.

Your local Flutter installation comes with several widgets. To check out the widgets available by default, open the packages folder of your Flutter installation in your preferred editor. Then search across all files for "extends StatefulWidget" and "extends StatelessWidget" and take note of the number of results.

flutter-packages-widget-count

As of Flutter 2.10, you will get 408 StatefulWidgets and 272 StatelessWidgets. That is a total of 680 widgets available for you to use and implement UIs.

These widgets typically have all you need. But at times they may not be enough. pub.dev, Dart and Flutter's package manager, have many more widgets you can use to implement UIs.

It is difficult to count the widgets in pub.dev. But searching an empty string (don't enter anything in the search bar and then press the search icon) and setting the SDK to Flutter returns the current total number of published packages.

pub.dev-widget-count

At the time of writing, there are more than 23000 Flutter packages in pub.dev. Each package has at least one widget. This means that you have more than 23000 widgets from pub.dev to implement, in addition to the available 680. This means that you can really implement any UI you want easily in Flutter.

Adding to the many available widgets, you can also create your own widgets as you implement UIs.

The Widget Tree

The following is part of the code you get when you create a new Flutter project and remove the comments:

 @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
Part of the placeholder code for new Flutter projects

The parent Scaffold takes the appBar, body, and floatingActionButton parameters. In turn, the AppBar also takes a title parameter that has a Text value.

body takes a Center value that has a Column child. The Column in turn has two Texts as children. The FloatingActionButton takes the onPressed callback, 'Increment' tooltip, and an Icon for a child.

Screenshot--142--2
Flutter widget tree breakdown

This is a simple widget tree. It has parents and descendants. child and children are common properties of most Flutter widgets. As widgets continuously take more widget children, your app gradually grows into a large widget tree.

As you implement UIs in Flutter, bear in mind that you are building a widget tree. You will notice that your code indents inwards from the left margin. It seems to develop some kind of virtual greater than sign (of empty space) at the left.

Note: Huge indentation levels are a sign that you need to refactor your code. It means that you need to extract some widget hierarchy into a separate widget.

How to Implement Any UI in Flutter

1. Write your code starting at the top left and move down to the bottom right

You'll implement the UI widget after widget according to each element's position in the UI. So you will first write code for things that appear at the top of the UI. Then you keep writing code for the other items moving down the page until you reach the bottom of that UI.

Screenshot--143-

This is intuitive.

On the horizontal axis, go from left to right. If need be, or if it is a right-to-left UI, then implement it from right to left instead.

2. Choose a Widget

Next you'll need to logically determine the widget you want to use for a given UI element. At a bare minimum, for a given UI element, you will use simple widgets you're familiar with based on what their names say they do.

Chances are the name of what the UI component looks like is the name of the widget. If you find it hard to make a choice, a quick online search will give you the answers. Flutter has a great online community.

3. Use widget groups

If a group of UI items is arranged vertically, one after another, use a Column. If they are arranged horizontally, one after another, use a Row. If they are placed on top of each other, use a Stack, with the floating widgets wrapped in Positioned widgets.

a. Column/Row

Inside a Column or Row, you can change or adjust how the widgets will align themselves on the main or cross axis. Use their CrossAxisAlignment and MainAxisAlignment properties for such adjustments.

For the cross axis, you can align to center, end, start, and stretch. For the main axis, you can align to center, end, space around, space between, space evenly, and end.

In a Column, the vertical axis is the main axis while the horizontal axis is the cross axis. In a Row, the horizontal axis is the main axis while the vertical axis is the cross axis.

1Untitled-1-1
Adapted from https://arzerin.com/2019/11/20/flutter-column/
Untitled-1
Adapted from https://arzerin.com/2019/11/20/flutter-row/

In Columns and Rows, if you want a particular child widget to take as much available space as possible, wrap that widget inside an Expanded widget. If you are familiar with web frontend, you'll notice that Columns and Rows are like display: flex; in CSS.

b. Stack Widget

With Stack, the last widget(s) in the children's list appears on top of the earlier children.

You might have to edit the Stack's alignment to indicate the relative positions of the widgets. Like topCenter, center, bottomEnd, and so on.

The Stack's size is calculated based on non-positioned widgets (Widgets in the children list not wrapped in a Positioned parent). When coding, remember that your Stack should either have at least one non-positioned widget, or it should be wrapped in a parent widget that explicitly sets the Stack's size.

Positioned takes any or all of bottom, top, left, right. They set the child's position relative to the Stack. Negative values move the child in the opposite direction. However, negative values clip parts of the child out. Use clipBehavior: Clip.none on the Stack to show all the parts of the positioned widget.

Screenshot--148-
Full code here.

4. Create custom widgets

As you build the widget tree, you will notice two things:

  1. Either a chunk of the tree grows too big and it is a logical unit on its own.
  2. Or some chunks or sets of widgets might repeat themselves with slight changes.

These are two indications that you should refactor your code. It means that you should extract out those widgets and define them in another Dart file.

Your code editor will help you with refactoring. With or without the editor, all you need to do is:

  1. Create a new Dart file. The file name should reflect the new widget's name.
  2. Create a new class that extends StatefulWidget or StatelessWidget, depending on if the new widget has State or not.
  3. Then return the widget chunk from a build method.
  4. (Optional) If need be, your new Dart class can take positional or named parameters to its constructor to customize the widget's look.
// in counter_display.dart
import 'package:flutter/material.dart';

class CounterDisplay extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
  	  mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text('You have pushed the button this many times:'),
        Text('$counter', style: TextStyle(fontSize: 24)),
      ],
    );
  }
}

// in main.dart
//
// ... 
  body: Center(child: CounterDisplay()),
// ...

You will build many custom widgets and they in turn will be descendants to more custom widgets, and that's fine. The widget tree is meant to continuously grow as the need arises.

5. Add more customization

You won't customize widgets only because of refactoring and repetitions (DRY code). You will create custom widgets because of the UI you are implementing.

You will create custom widgets because the many available widgets don't always meet the exact needs of a given UI. You'll need to combine them in some special way to implement a particular UI.

a. Container Widget

Container is a powerful widget. You can style it in different ways. If you are used to web frontend, you'll notice that it is like a div in HTML.

Container is a base widget. You can use it to create any UI piece.

Some Container parameters are constraints, decoration, margin, padding, transform, among others. Of course, Container takes a child which can be any widget.

The decoration property can take a BoxDecoration, which in turn can take several other properties. This is the heart of Container's flexibility. BoxDecoration takes parameters like border, borderRadius, boxShadow, color, gradient, image, shape, among others.

With these parameters and their values, you can implement any UI to your taste. You can use Container instead of the many material widgets that Flutter comes with. That way your app is to your taste.

b. GestureDetector / InkWell

GestureDetector as the name implies detects user interactions. Not every UI piece is a button. And while implementing UIs you will need some widgets to react to user actions. In such a case, use GestureDetector.

GestureDetector can detect different types of gestures: taps, double taps, swipes, ... GestureDetector of course takes a child (which can be any widget), and different callbacks for different gestures like onTap, onDoubleTap, onPanUpdate (for swipes), ...

Note: By default, when users interact with the empty spaces in the child of GestureDetectors, the callbacks are not called. If you want your GestureDetector to react to gestures on empty space (within its child), then set the behavior property of the GestureDetector to HitTestBehavior.translucent.

GestureDetector(
  // set behavior to detect taps on empty spaces
  behavior: HitTestBehavior.translucent,
  child: Column(
    children: [
      Text('I have space after me ...'),
      SizedBox(height: 32),
      Text('... that can detect taps.'),
    ],
  ),
  onTap: () => print('Tapped on empty space.'),
)
Usage of GestureDetector, indicating HitTestBehavior.translucent behavior.

InkWell is similar to GestureDetector. It responds to some gestures that GestureDetector responds to. However, it shows ripple effects when interacted with (which GestureDetectors don't).

QqEZ3
From https://stackoverflow.com/q/58285012/13644299

InkWell must have a Material ancestor. So, if your topmost widget is MaterialApp you need not worry. Else, wrap the InkWell in a Material.

You should also do this wrapping if you are changing the colors of the InkWell's parent or child. If you don't, the ripple won't show. You also have to set the color of the Material widget for the ripple to show. You can set the color to Colors.transparent and Flutter will take care of the rest.

How to Implement Scrolling Interfaces

Scrolling is a little delicate topic. By default, widgets don't scroll in Flutter. If your Column or Row will be scrollable, use a ListView instead. ListView takes children parameter too.

ListView also has factory constructors like ListView.builder and ListView.separated. The builder gives you more control over the build process of the children whereas the separated takes into account a Separator (like Divider for example).

By default, ListViews scroll their children vertically. However, you can change the scrollDirection of a ListView to Axis.horizontal to scroll its children horizontally.

At times, you might want to use SingleChildScrollView instead of ListView. As the name implies, it takes a single child and it can scroll. You can pass widget groups as its child.

There are other scrolling widgets.

But take special note of CustomScrollView. It gives you huge control of scrolling, unlike the others. It takes slivers, which in turn are scrolling widgets with powerful scroll mechanisms.

SliverFillRemaining, SliverFillViewport, SliverGrid, SliverList, SliverPersistentHeader among others, are examples of widgets you include in the list of slivers. Most of these widgets take a delegate, which handles how scrolling occurs.

A good case to use CustomScrollView is with SliverAppBar, where you want the AppBar to be expanded by default and shrunk on scroll.

ap

Another example could be with a DraggableScrollableSheet where you keep some action button sticked to the bottom.

bs

About CustomPaint

This is where Flutter gave ultimate flexibility to the UI world.

CustomPaint is to Flutter what the Canvas API is to HTML or SVG is to images.

CustomPaint is a widget in Flutter that gives you the ability to design and draw without limitations. It gives you a canvas on which you can draw with a painter.

visualizer
From https://blog.codemagic.io/flutter-custom-painter/

You will rarely use CustomPaint. But be aware that it exists. Because there might be very complex UIs that widget combinations might not implement them and you will have no choice than drawing with CustomPaint.

When that time comes, it won't be hard for you because you are already familiar with other widgets.

Summary

For a given UI piece, choose a widget, write its code, build the widget with other widgets, and see what great UI you are implementing with Flutter.

Implementing UIs is a major part of mobile, web, and desktop app development. Flutter is a UI toolkit that build cross-platform for those platforms. Flutter's declarative nature and its widget abundance make UI implementation simple.

Keep implementing UIs in Flutter. As you do, it will become second nature to you. And you will be able to implement any UI in Flutter.