Flutter 레이아웃

Flutter 레이아웃 메커니즘의 핵심은 위젯입니다. Flutter에서는 거의 모든 것이 위젯이고 심지어 레이아웃 모델도 위젯이죠. Flutter 앱 내에서 볼 수 있는 이미지, 아이콘, 글자 모두 위젯입니다. 하지만 row, column, grid 같이 볼 수 없는 위젯들도 있는데요. 이들은 보이는 위젯들을 제어하고, 제한하며, 정렬시켜줍니다.

더 복잡한 위젯들을 만들기 위해 위젯들을 구성하여 레이아웃을 생성하세요. 예를 들어, 아래 첫 번째 스크린샷은 각각 아래에 레이블이 있는 아이콘 3개를 보여줍니다.

Sample layout Sample layout with visual debugging

두 번째 스크린샷은 시각적 레이아웃을 표시하는데, 각각 아이콘과 레이블이 포함된 열 3개가 있는 행을 보여줍니다.

다음은 해당 UI에 대한 위젯 트리 다이어그램입니다.

Node tree

대부분이 예상대로 보이지만, (분홍색으로 표시된)container가 뭔지 궁금하실겁니다. Container는 자식 위젯들을 커스터마이징할 수 있는 위젯 클래스인데요. 여백, 간격, 테두리 또는 배경색을 추가하고 싶을 때 Container를 사용할 수 있습니다.

이 예제에서, 각 Text 위젯은 간격 추가를 위해 Container 안에 배치되었고, 마찬가지로 Row 전체를 감싸는 여백을 추가하기 위해 Container 안에 배치되었습니다.

이 예제의 나머지 UI는 속성에 의해 제어됩니다. Icon의 색상은 color 속성을 이용해 설정했습니다. 폰트, 색상, 굵기 등을 설정하기 위해서는 Text.style 속성을 사용하세요. Column과 Row에는 자식들을 세로 혹은 가로로 정렬하는 방법과 자식들이 차지해야 하는 공간을 지정할 수 있는 속성이 있습니다.

Lay out a widget

How do you layout a single widget in Flutter? This section shows you how to create and display a simple widget. It also shows the entire code for a simple Hello World app.

In Flutter, it takes only a few steps to put text, an icon, or an image on the screen.

1. Select a layout widget

Choose from a variety of layout widgets based on how you want to align or constrain the visible widget, as these characteristics are typically passed on to the contained widget.

This example uses Center which centers its content horizontally and vertically.

2. Create a visible widget

For example, create a Text widget:

Text('Hello World'),

Create an Image widget:

Image.asset(
  'images/lake.jpg',
  fit: BoxFit.cover,
),

Create an Icon widget:

Icon(
  Icons.star,
  color: Colors.red[500],
),

3. Add the visible widget to the layout widget

All layout widgets have either of the following:

  • A child property if they take a single child – for example, Center or Container
  • A children property if they take a list of widgets – for example, Row, Column, ListView, or Stack.

Add the Text widget to the Center widget:

Center(
  child: Text('Hello World'),
),

4. Add the layout widget to the page

A Flutter app is itself a widget, and most widgets have a build() method. Instantiating and returning a widget in the app’s build() method displays the widget.

Material apps

For a Material app, you can use a Scaffold widget; it provides a default banner, background color, and has API for adding drawers, snack bars, and bottom sheets. Then you can add the Center widget directly to the body property for the home page.

lib/main.dart (MyApp)
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter layout demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter layout demo'),
        ),
        body: Center(
          child: Text('Hello World'),
        ),
      ),
    );
  }
}

Non-Material apps

For a non-Material app, you can add the Center widget to the app’s build() method:

lib/main.dart (MyApp)
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(color: Colors.white),
      child: Center(
        child: Text(
          'Hello World',
          textDirection: TextDirection.ltr,
          style: TextStyle(
            fontSize: 32,
            color: Colors.black87,
          ),
        ),
      ),
    );
  }
}

By default a non-Material app doesn’t include an AppBar, title, or background color. If you want these features in a non-Material app, you have to build them yourself. This app changes the background color to white and the text to dark grey to mimic a Material app.

That’s it! When you run the app, you should see Hello World.

App source code:

Hello World

Lay out multiple widgets vertically and horizontally

One of the most common layout patterns is to arrange widgets vertically or horizontally. You can use a Row widget to arrange widgets horizontally, and a Column widget to arrange widgets vertically.

To create a row or column in Flutter, you add a list of children widgets to a Row or Column widget. In turn, each child can itself be a row or column, and so on. The following example shows how it is possible to nest rows or columns inside of rows or columns.

This layout is organized as a Row. The row contains two children: a column on the left, and an image on the right:

Screenshot with callouts showing the row containing two children

The left column’s widget tree nests rows and columns.

Diagram showing a left column broken down to its sub-rows and sub-columns

You’ll implement some of Pavlova’s layout code in Nesting rows and columns.

Aligning widgets

You control how a row or column aligns its children using the mainAxisAlignment and crossAxisAlignment properties. For a row, the main axis runs horizontally and the cross axis runs vertically. For a column, the main axis runs vertically and the cross axis runs horizontally.

Diagram showing the main axis and cross axis for a row Diagram showing the main axis and cross axis for a column

The MainAxisAlignment and CrossAxisAlignment classes offer a variety of constants for controlling alignment.

In the following example, each of the 3 images is 100 pixels wide. The render box (in this case, the entire screen) is more than 300 pixels wide, so setting the main axis alignment to spaceEvenly divides the free horizontal space evenly between, before, and after each image.

Row(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  children: [
    Image.asset('images/pic1.jpg'),
    Image.asset('images/pic2.jpg'),
    Image.asset('images/pic3.jpg'),
  ],
);

Row with 3 evenly spaced images

App source: row_column

Columns work the same way as rows. The following example shows a column of 3 images, each is 100 pixels high. The height of the render box (in this case, the entire screen) is more than 300 pixels, so setting the main axis alignment to spaceEvenly divides the free vertical space evenly between, above, and below each image.

Column(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  children: [
    Image.asset('images/pic1.jpg'),
    Image.asset('images/pic2.jpg'),
    Image.asset('images/pic3.jpg'),
  ],
);

App source: row_column

Column showing 3 images spaced evenly

Sizing widgets

When a layout is too large to fit a device, a yellow and black striped pattern appears along the affected edge. Here is an example of a row that is too wide:

Overly-wide row

Widgets can be sized to fit within a row or column by using the Expanded widget. To fix the previous example where the row of images is too wide for its render box, wrap each image with an Expanded widget.

Row(
  crossAxisAlignment: CrossAxisAlignment.center,
  children: [
    Expanded(
      child: Image.asset('images/pic1.jpg'),
    ),
    Expanded(
      child: Image.asset('images/pic2.jpg'),
    ),
    Expanded(
      child: Image.asset('images/pic3.jpg'),
    ),
  ],
);

Row of 3 images that are too wide, but each is constrained to take only 1/3 of the space

App source: sizing

Perhaps you want a widget to occupy twice as much space as its siblings. For this, use the Expanded widget flex property, an integer that determines the flex factor for a widget. The default flex factor is 1. The following code sets the flex factor of the middle image to 2:

Row(
  crossAxisAlignment: CrossAxisAlignment.center,
  children: [
    Expanded(
      child: Image.asset('images/pic1.jpg'),
    ),
    Expanded(
      flex: 2,
      child: Image.asset('images/pic2.jpg'),
    ),
    Expanded(
      child: Image.asset('images/pic3.jpg'),
    ),
  ],
);

Row of 3 images with the middle image twice as wide as the others

App source: sizing

Packing widgets

By default, a row or column occupies as much space along its main axis as possible, but if you want to pack the children closely together, set its mainAxisSize to MainAxisSize.min. The following example uses this property to pack the star icons together.

Row(
  mainAxisSize: MainAxisSize.min,
  children: [
    Icon(Icons.star, color: Colors.green[500]),
    Icon(Icons.star, color: Colors.green[500]),
    Icon(Icons.star, color: Colors.green[500]),
    Icon(Icons.star, color: Colors.black),
    Icon(Icons.star, color: Colors.black),
  ],
)

Row of 5 stars, packed together in the middle of the row

App source: pavlova

Nesting rows and columns

The layout framework allows you to nest rows and columns inside of rows and columns as deeply as you need. Let’s look the code for the outlined section of the following layout:

Screenshot of the pavlova app, with the ratings and icon rows outlined in red

The outlined section is implemented as two rows. The ratings row contains five stars and the number of reviews. The icons row contains three columns of icons and text.

The widget tree for the ratings row:

Ratings row widget tree

The ratings variable creates a row containing a smaller row of 5 star icons, and text:

var stars = Row(
  mainAxisSize: MainAxisSize.min,
  children: [
    Icon(Icons.star, color: Colors.green[500]),
    Icon(Icons.star, color: Colors.green[500]),
    Icon(Icons.star, color: Colors.green[500]),
    Icon(Icons.star, color: Colors.black),
    Icon(Icons.star, color: Colors.black),
  ],
);

final ratings = Container(
  padding: EdgeInsets.all(20),
  child: Row(
    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
    children: [
      stars,
      Text(
        '170 Reviews',
        style: TextStyle(
          color: Colors.black,
          fontWeight: FontWeight.w800,
          fontFamily: 'Roboto',
          letterSpacing: 0.5,
          fontSize: 20,
        ),
      ),
    ],
  ),
);

The icons row, below the ratings row, contains 3 columns; each column contains an icon and two lines of text, as you can see in its widget tree:

Icon widget tree

The iconList variable defines the icons row:

final descTextStyle = TextStyle(
  color: Colors.black,
  fontWeight: FontWeight.w800,
  fontFamily: 'Roboto',
  letterSpacing: 0.5,
  fontSize: 18,
  height: 2,
);

// DefaultTextStyle.merge() allows you to create a default text
// style that is inherited by its child and all subsequent children.
final iconList = DefaultTextStyle.merge(
  style: descTextStyle,
  child: Container(
    padding: EdgeInsets.all(20),
    child: Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        Column(
          children: [
            Icon(Icons.kitchen, color: Colors.green[500]),
            Text('PREP:'),
            Text('25 min'),
          ],
        ),
        Column(
          children: [
            Icon(Icons.timer, color: Colors.green[500]),
            Text('COOK:'),
            Text('1 hr'),
          ],
        ),
        Column(
          children: [
            Icon(Icons.restaurant, color: Colors.green[500]),
            Text('FEEDS:'),
            Text('4-6'),
          ],
        ),
      ],
    ),
  ),
);

The leftColumn variable contains the ratings and icons rows, as well as the title and text that describes the Pavlova:

final leftColumn = Container(
  padding: EdgeInsets.fromLTRB(20, 30, 20, 20),
  child: Column(
    children: [
      titleText,
      subTitle,
      ratings,
      iconList,
    ],
  ),
);

The left column is placed in a Container to constrain its width. Finally, the UI is constructed with the entire row (containing the left column and the image) inside a Card.

The Pavlova image is from Pixabay. You can embed an image from the net using Image.network() but, for this example, the image is saved to an images directory in the project, added to the pubspec file, and accessed using Images.asset(). For more information, see Adding assets and images.

body: Center(
  child: Container(
    margin: EdgeInsets.fromLTRB(0, 40, 0, 30),
    height: 600,
    child: Card(
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Container(
            width: 440,
            child: leftColumn,
          ),
          mainImage,
        ],
      ),
    ),
  ),
),

App source: pavlova


Common layout widgets

Flutter has a rich library of layout widgets. Here are a few of those most commonly used. The intent is to get you up and running as quickly as possible, rather than overwhelm you with a complete list. For information on other available widgets, refer to the Widget catalog, or use the Search box in the API reference docs. Also, the widget pages in the API docs often make suggestions about similar widgets that might better suit your needs.

The following widgets fall into two categories: standard widgets from the widgets library, and specialized widgets from the Material library. Any app can use the widgets library but only Material apps can use the Material Components library.

Standard widgets

  • Container: Adds padding, margins, borders, background color, or other decorations to a widget.
  • GridView: Lays widgets out as a scrollable grid.
  • ListView: Lays widgets out as a scrollable list.
  • Stack: Overlaps a widget on top of another.

Material widgets

  • Card: Organizes related info into a box with rounded corners and a drop shadow.
  • ListTile: Organizes up to 3 lines of text, and optional leading and trailing icons, into a row.

Container

Many layouts make liberal use of Containers to separate widgets using padding, or to add borders or margins. You can change the device’s background by placing the entire layout into a Container and changing its background color or image.

Summary (Container)

  • Add padding, margins, borders
  • Change background color or image
  • Contains a single child widget, but that child can be a Row, Column, or even the root of a widget tree
Diagram showing: margin, border, padding, and content

Examples (Container)

This layout consists of a column with two rows, each containing 2 images. A Container is used to change the background color of the column to a lighter grey.

Widget _buildImageColumn() => Container(
      decoration: BoxDecoration(
        color: Colors.black26,
      ),
      child: Column(
        children: [
          _buildImageRow(1),
          _buildImageRow(3),
        ],
      ),
    );
Screenshot showing 2 rows, each containing 2 images

A Container is also used to add a rounded border and margins to each image:

Widget _buildDecoratedImage(int imageIndex) => Expanded(
      child: Container(
        decoration: BoxDecoration(
          border: Border.all(width: 10, color: Colors.black38),
          borderRadius: const BorderRadius.all(const Radius.circular(8)),
        ),
        margin: const EdgeInsets.all(4),
        child: Image.asset('images/pic$imageIndex.jpg'),
      ),
    );

Widget _buildImageRow(int imageIndex) => Row(
      children: [
        _buildDecoratedImage(imageIndex),
        _buildDecoratedImage(imageIndex + 1),
      ],
    );

You can find more Container examples in the tutorial and the Flutter Gallery.

App source: container


GridView

Use GridView to lay widgets out as a two-dimensional list. GridView provides two pre-fabricated lists, or you can build your own custom grid. When a GridView detects that its contents are too long to fit the render box, it automatically scrolls.

Summary (GridView)

  • Lays widgets out in a grid
  • Detects when the column content exceeds the render box and automatically provides scrolling
  • Build your own custom grid, or use one of the provided grids:
    • GridView.count allows you to specify the number of columns
    • GridView.extent allows you to specify the maximum pixel width of a tile

Examples (GridView)

A 3-column grid of photos

Uses GridView.extent to create a grid with tiles a maximum 150 pixels wide.

App source: grid_and_list

A 2 column grid with footers

Uses GridView.count to create a grid that’s 2 tiles wide in portrait mode, and 3 tiles wide in landscape mode. The titles are created by setting the footer property for each GridTile.

Dart code: grid_list_demo.dart from the Flutter Gallery

Widget _buildGrid() => GridView.extent(
    maxCrossAxisExtent: 150,
    padding: const EdgeInsets.all(4),
    mainAxisSpacing: 4,
    crossAxisSpacing: 4,
    children: _buildGridTileList(30));

// The images are saved with names pic0.jpg, pic1.jpg...pic29.jpg.
// The List.generate() constructor allows an easy way to create
// a list when objects have a predictable naming pattern.
List<Container> _buildGridTileList(int count) => List.generate(
    count, (i) => Container(child: Image.asset('images/pic$i.jpg')));

ListView

ListView, a column-like widget, automatically provides scrolling when its content is too long for its render box.

Summary (ListView)

  • A specialized Column for organizing a list of boxes
  • Can be laid out horizontally or vertically
  • Detects when its content won’t fit and provides scrolling
  • Less configurable than Column, but easier to use and supports scrolling

Examples (ListView)

ListView containing movie theaters and restaurants

Uses ListView to display a list of businesses using ListTiles. A Divider separates the theaters from the restaurants.

App source: grid_and_list

ListView containing shades of blue

Uses ListView to display the Colors from the Material Design palette for a particular color family.

Dart code: colors_demo.dart from the Flutter Gallery

Widget _buildList() => ListView(
      children: [
        _tile('CineArts at the Empire', '85 W Portal Ave', Icons.theaters),
        _tile('The Castro Theater', '429 Castro St', Icons.theaters),
        _tile('Alamo Drafthouse Cinema', '2550 Mission St', Icons.theaters),
        _tile('Roxie Theater', '3117 16th St', Icons.theaters),
        _tile('United Artists Stonestown Twin', '501 Buckingham Way',
            Icons.theaters),
        _tile('AMC Metreon 16', '135 4th St #3000', Icons.theaters),
        Divider(),
        _tile('Kescaped_code#39;s Kitchen', '757 Monterey Blvd', Icons.restaurant),
        _tile('Emmyescaped_code#39;s Restaurant', '1923 Ocean Ave', Icons.restaurant),
        _tile(
            'Chaiya Thai Restaurant', '272 Claremont Blvd', Icons.restaurant),
        _tile('La Ciccia', '291 30th St', Icons.restaurant),
      ],
    );

ListTile _tile(String title, String subtitle, IconData icon) => ListTile(
      title: Text(title,
          style: TextStyle(
            fontWeight: FontWeight.w500,
            fontSize: 20,
          )),
      subtitle: Text(subtitle),
      leading: Icon(
        icon,
        color: Colors.blue[500],
      ),
    );

Stack

Use Stack to arrange widgets on top of a base widget—often an image. The widgets can completely or partially overlap the base widget.

Summary (Stack)

  • Use for widgets that overlap another widget
  • The first widget in the list of children is the base widget; subsequent children are overlaid on top of that base widget
  • A Stack’s content can’t scroll
  • You can choose to clip children that exceed the render box

Examples (Stack)

Circular avatar image with a label

Uses Stack to overlay a Container (that displays its Text on a translucent black background) on top of a CircleAvatar. The Stack offsets the text using the alignment property and Alignments.

App source: card_and_stack

An image with a grey gradient across the top

Uses Stack to overlay a gradient to the top of the image. The gradient ensures that the toolbar’s icons are distinct against the image.

Dart code: contacts_demo.dart from the Flutter Gallery

Widget _buildStack() => Stack(
    alignment: const Alignment(0.6, 0.6),
    children: [
      CircleAvatar(
        backgroundImage: AssetImage('images/pic.jpg'),
        radius: 100,
      ),
      Container(
        decoration: BoxDecoration(
          color: Colors.black45,
        ),
        child: Text(
          'Mia B',
          style: TextStyle(
            fontSize: 20,
            fontWeight: FontWeight.bold,
            color: Colors.white,
          ),
        ),
      ),
    ],
  );

Card

A Card, from the Material library, contains related nuggets of information and can be composed from almost any widget, but is often used with ListTile. Card has a single child, but its child can be a column, row, list, grid, or other widget that supports multiple children. By default, a Card shrinks its size to 0 by 0 pixels. You can use SizedBox to constrain the size of a card.

In Flutter, a Card features slightly rounded corners and a drop shadow, giving it a 3D effect. Changing a Card’s elevation property allows you to control the drop shadow effect. Setting the elevation to 24, for example, visually lifts the Card further from the surface and causes the shadow to become more dispersed. For a list of supported elevation values, see Elevation in the Material guidelines. Specifying an unsupported value disables the drop shadow entirely.

Summary (Card)

  • Implements a Material card
  • Used for presenting related nuggets of information
  • Accepts a single child, but that child can be a Row, Column, or other widget that holds a list of children
  • Displayed with rounded corners and a drop shadow
  • A Card’s content can’t scroll
  • From the Material library

Examples (Card)

Card containing 3 ListTiles

A Card containing 3 ListTiles and sized by wrapping it with a SizedBox. A Divider separates the first and second ListTiles.

App source: card_and_stack

Card containing an image, text and buttons

A Card containing an image and text.

Dart code: cards_demo.dart from the Flutter Gallery

Widget _buildCard() => SizedBox(
    height: 210,
    child: Card(
      child: Column(
        children: [
          ListTile(
            title: Text('1625 Main Street',
                style: TextStyle(fontWeight: FontWeight.w500)),
            subtitle: Text('My City, CA 99984'),
            leading: Icon(
              Icons.restaurant_menu,
              color: Colors.blue[500],
            ),
          ),
          Divider(),
          ListTile(
            title: Text('(408) 555-1212',
                style: TextStyle(fontWeight: FontWeight.w500)),
            leading: Icon(
              Icons.contact_phone,
              color: Colors.blue[500],
            ),
          ),
          ListTile(
            title: Text('costa@example.com'),
            leading: Icon(
              Icons.contact_mail,
              color: Colors.blue[500],
            ),
          ),
        ],
      ),
    ),
  );

ListTile

Use ListTile, a specialized row widget from the Material library, for an easy way to create a row containing up to 3 lines of text and optional leading and trailing icons. ListTile is most commonly used in Card or ListView, but can be used elsewhere.

Summary (ListTile)

  • A specialized row that contains up to 3 lines of text and optional icons
  • Less configurable than Row, but easier to use
  • From the Material library

Examples (ListTile)

Card containing 3 ListTiles

A Card containing 3 ListTiles.

App source: card_and_stack

3 ListTiles, each containing a pull-down button

Uses ListTile to list 3 drop down button types.
Dart code: buttons_demo.dart from the Flutter Gallery


Videos

다음 비디오들은 Flutter in Focus 시리즈의 일부로, Stateless 및 Stateful 위젯에 대해서 설명합니다.

Flutter in Focus 재생 목록


Widget of the Week 시리즈의 각 에피소드는 위젯에 중점을 둡니다. 그 중에서 몇 가지는 레이아웃 위젯을 포함합니다.

Flutter Widget of the Week playlist

Other resources

The following resources may help when writing layout code.