꿈꾸는 시스템 디자이너

Flutter 강좌 - Layout Tutorial | 레이아웃 구성 본문

Development/Flutter

Flutter 강좌 - Layout Tutorial | 레이아웃 구성

독행소년 2019. 6. 10. 19:28

 

Flutter 강좌 목록 : https://here4you.tistory.com/120

 

1 단계 - 코드 기반의 앱을 생성

우선 다음과 같은 코드를 작성한다.

import 'package:flutter/material.dart';

// MyApp을 시작 위젯으로 설정하여 앱을 실행
void main() => runApp(MyApp());

// 앱의 시작점에 해당하는 위젯
class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    // 매트리얼앱을 생성하여 반환
    return MaterialApp(
        title: 'Flutter Layout Demo', // 앱의 타이틀
        theme: ThemeData( // 앱의 테마 설정
          primarySwatch: Colors.red,  // 주요 테마 색상
        ),
        // 홈 설정. 홈은 메트리얼앱의 시작점
        home:Scaffold(
          appBar: AppBar(
            title: Text("Flutter layout demo"),
          ),
          body: Center(
            child: Text("Hello World"),
          ),
        )
    );
  }
}

 

소스코드는 기존 강좌에서 살펴본 코드와 비슷한 구성입니다. 메인함수에서 runApp 함수를 통해 MyApp 위젯을 호출합니다. MyApp 위젯은 매트리얼앱을 생성하여 반환하여 앱이 화면에 출력되게 된다.

상기 코드의 실행화면은 다음과 같다.

 

2 단계 - 타이틀 로우 구현

앞서 작성한 코드를 다음과 같이 추가 수정한다.

import 'package:flutter/material.dart';

// MyApp을 시작 위젯으로 설정하여 앱을 실행
void main() => runApp(MyApp());

// 앱의 시작점에 해당하는 위젯
class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    // 매트리얼앱을 생성하여 반환
    return MaterialApp(
        title: 'Flutter Layout Demo', // 앱의 타이틀
        theme: ThemeData( // 앱의 테마 설정
          primarySwatch: Colors.red,  // 주요 테마 색상
        ),
        // 홈 설정. 홈은 메트리얼앱의 시작점
        home:Scaffold(
          appBar: AppBar(
            title: Text("Flutter layout demo"),
          ),
          // 기존 body 삭제
          // body: Center(
          // child: Text("Hello World"),
          // ),
          // 새로운 body 선언
          body: Column( // 컬럼으로 교체
            // 자식들을 추가
            children: <Widget>[
              titleSection  // titleSection 컨테이너 추가
            ],
          ),
        )
    );
  }

  // 컨테이터 위젯 구현
  Widget titleSection = Container(
    // 컨테이너 내부 상하좌우에 32픽셀만큼의 패딩 삽입
    padding: const EdgeInsets.all(32),
    // 자식으로 로우를 추가
    child: Row(
      // 로우에 위젯들(Expanded, Icon, Text)을 자식들로 추가
      children: <Widget>[
        // 첫번째 자식
        Expanded(
          // 첫번째 자식의 자식으로 컬럼 추가
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start, // 자식들을 왼쪽정렬로 배치함
            // 컬럼의 자식들로 컨테이너와 텍스트를 추가
            children: <Widget>[
              // 컬럼의 첫번째 자식 컨테이너
              Container(
                padding: const EdgeInsets.only(bottom: 8), // 컨테이너 내부 하단에 8픽셀만큼 패딩 삽입
                child: Text(  // 컨테이너의 자식으로 텍스트 삽입
                  "Oeschinen Lake Campground",
                  style: TextStyle(
                      fontWeight: FontWeight.bold // 텍스트 강조 설정
                  ),
                ),
              ),
              // 컬럼의 두번째 자식으로 텍스트 삽입
              Text(
                'Kandersteg, Switzerland',
                style: TextStyle(
                    color: Colors.grey[500] // 텍스트의 색상을 설정
                ),
              )
            ],
          ),
        ),
        // 두번째 자식 아이콘
        Icon(
          Icons.star, // 별모양 아이콘 삽입
          color: Colors.red[500], // 빨간색으로 설정
        ),
        // 세번째 자식
        Text('43')  // 텍스트 표시
      ],
    ),
  );
}

 

우선 Scaffold 위젯 body 항목을 Center 위젯에서 Column 위젯으로 변경하였다. 컬럼 위젯을 이용하면 수직으로 여러 위젯을 배열할 수 있게된다. 현재 코드에는 titleSection 위젯만을 자식들로 추가한 상태이다.

titleSection 위젯을 구현내용을 살펴보면 컨테이너 위젯을 구현하고 있다. 이 컨테이너는 로우를 자식으로 가지며, 로우는 다시 Expanded와 Icon 그리고 Text 위젯을 자식들로 가진다. 이 세 위젯들은 로우에 등록되어 수평적으로 출력되게 된다.

첫번째 자식인 Expanded는 컬럼을 자식으로 할당하므로 컬럼에 추가될 자식들은 수직적으로 추가될 것이다.

컴럼을 crossAxisAlignment: CrossAxisAlignment.start 로 설정하면 자식 위젯들이 왼쪽정렬로 추가된다.

컬럼의 첫번째 자식은 컨테이너이며 내부 하단에 8픽셀의 패딩값을 가지는 Text로 구성된다. 컬럼의 두번째 자식은 텍스트이며 글자색이 회색으로 설정된다.

위 코드의 실행화면은 다음과 같다.

 

3 단계 - 버튼 로우 구현

소스코드를 다음과 같이 수정한다.

import 'package:flutter/material.dart';

// MyApp을 시작 위젯으로 설정하여 앱을 실행
void main() => runApp(MyApp());

// 앱의 시작점에 해당하는 위젯
class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    // 본 앱의 테마의 대표색상을 필드에 저장
    Color color = Theme.of(context).primaryColor;

    // 버튼 로우 구성을 위한 컨테이너 위젯
    Widget buttonSection = Container(
      child: Row( // 로우를 자식으로 가짐
        mainAxisAlignment: MainAxisAlignment.spaceEvenly, // 자식들이 여유 공간을 공편하게 나눠가짐
        children: <Widget>[ // 세개의 위젯들을 자식들로 가짐
          _buildButtonColumn(color, Icons.call, 'CALL'),
          _buildButtonColumn(color, Icons.near_me, 'ROUTH'),
          _buildButtonColumn(color, Icons.share, 'SHARE')
        ],
      ),
    );

    // 매트리얼앱을 생성하여 반환
    return MaterialApp(
        title: 'Flutter Layout Demo', // 앱의 타이틀
        theme: ThemeData( // 앱의 테마 설정
          primarySwatch: Colors.red,  // 주요 테마 색상
        ),
        // 홈 설정. 홈은 메트리얼앱의 시작점
        home:Scaffold(
          appBar: AppBar(
            title: Text("Flutter layout demo"),
          ),
          // 기존 body 삭제
          // body: Center(
          // child: Text("Hello World"),
          // ),
          // 새로운 body 선언
          body: Column( // 컬럼으로 교체
            // 자식들을 추가
            children: <Widget>[
              titleSection,  // titleSection 컨테이너 추가
              buttonSection,  // buttonSection 컨테이너 추가
            ],
          ),
        )
    );
  }

  // 타이틀 로우 구성을 위한 컨테이터 위젯 구현
  Widget titleSection = Container(...);

  // 버튼과 텍스트로 구성된 컬럼을 생성하여 반환하는 함수
  Column _buildButtonColumn(Color color,IconData icon, String label){
    // 컬럼을 생성하여 반환
    return Column(
      mainAxisSize: MainAxisSize.min, // 여유공간을 최소로 할당
      mainAxisAlignment: MainAxisAlignment.center, // 가운데 정렬
      // 컬럼의 자식들로 아이콘과 컨테이너를 등록
      children: <Widget>[
        Icon(
          icon,
          color: color,
        ),
        Container(
          margin: const EdgeInsets.only(top: 8),  // 컨테이너 상단에 8픽셀의 마진을 할당
          child: Text(  // 텍스트 할당
            label,
            style: TextStyle(
                fontSize: 12,
                fontWeight: FontWeight.w400,
                color: color
            ),
          ),
        )
      ],
    );
  }
}

 

우선 build 함수 내부에 color 필드가 추가되었으며 본 앱의 테마의 대표색상을 저장하고 있다.

다음으로 MaterialApp 함수 내부의 body 항목에 새로운 자식으로 buttonSection이 추가되었다. 이로써 titleSection 하단에 buttonSection이 출력될 것이다.

buttonSection 위젯은 컨테이너 위젯으로 로우를 자식으로 가지며 mainAxisAlignment: MainAxisAlignment.spaceEvenly 로 설정되어 추가되는 자식들은 여유공간을 동일하게 나눠가지게 된다. 자식들은 _buildButtonColum 함수 호출을 통해 로우에 추가되는 형태이다. _buildButtonColum 함수 호출시 build 함수 호출시 필드에 저장한 color 값과, 생성할 Icon의 데이터, 그리고 텍스트 값을 파라미터로 전달하게 된다.

_buildButtonColum 함수는 Color과 IconData, String 타입의 아규먼트를 받아 컬럼을 생성하여 반환하는 함수이다. 반환되는 컬럼은 아이콘과 텍스트를 포함하는 컨테이너로 구성되어 반환된다.

위 코드의 실행화면은 다음과 같다.

 

앞서 구현한 titleSection 밑에 buttonSection이 추가된 것을 확인할 수 있다.

 

4 단계 - 텍스트 영역 구현

소스코드를 다음과 같이 수정한다.

import 'package:flutter/material.dart';

// MyApp을 시작 위젯으로 설정하여 앱을 실행
void main() => runApp(MyApp());

// 앱의 시작점에 해당하는 위젯
class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    // 본 앱의 테마의 대표색상을 필드에 저장
    Color color = Theme.of(context).primaryColor;

    // 버튼 로우 구성을 위한 컨테이너 위젯
    Widget buttonSection = Container(...);

    // 매트리얼앱을 생성하여 반환
    return MaterialApp(
        title: 'Flutter Layout Demo', // 앱의 타이틀
        theme: ThemeData( // 앱의 테마 설정
          primarySwatch: Colors.red,  // 주요 테마 색상
        ),
        // 홈 설정. 홈은 메트리얼앱의 시작점
        home:Scaffold(
          appBar: AppBar(
            title: Text("Flutter layout demo"),
          ),
          // 기존 body 삭제
          // body: Center(
          // child: Text("Hello World"),
          // ),
          // 새로운 body 선언
          body: Column( // 컬럼으로 교체
            // 자식들을 추가
            children: <Widget>[
              titleSection,  // titleSection 컨테이너 추가
              buttonSection,  // buttonSection 컨테이너 추가
              textSection // textSection 컨테이너 추가
            ],
          ),
        )
    );
  }

  // 타이틀 로우 구성을 위한 컨테이터 위젯 구현
  Widget titleSection = Container(...);

  // 버튼과 텍스트로 구성된 컬럼을 생성하여 반환하는 함수
  Column _buildButtonColumn(Color color,IconData icon, String label){...}

  // 텍스트로 구성된 컨테이너를 구현하는 위젯
  Widget textSection = Container(
    padding: const EdgeInsets.all(32),
    child: Text(
      'Lake Oeschinen lies at the foot of the Blüemlisalp in the Bernese '
          'Alps. Situated 1,578 meters above sea level, it is one of the '
          'larger Alpine Lakes. A gondola ride from Kandersteg, followed by a '
          'half-hour walk through pastures and pine forest, leads you to the '
          'lake, which warms to 20 degrees Celsius in the summer. Activities '
          'enjoyed here include rowing, and riding the summer toboggan run.',
      softWrap: true, // 텍스트가 영역을 넘어갈 경우 줄바꿈 여부
    ),
  );
}

 

MaterialApp 함수내부의 body 부분에 textSection을 추가한다.

textSection 위젯은 텍스트를 자식으로 가지는 컨테이너다. 텍스트는 길 문장으로 이루어져있는데 softWrap 항목이 true로 설정되어 있다. softWrap 항목은 텍스트가 해당 영역을 넘어갈 경우 줄바꿈을 할지 여부를 설정할 수 있다.

상기 코드의 실행화면은 다음과 같다.

 

5 단계 - 이미지 영역 구현

이번 단계를 위해서는 우선 이미지를 프로젝트에 추가해야 한다. 사용한 이미지는 아래의 링크에서 다운로드 가능하다. 프로젝트의 루트 위치에 images라는 디렉토리를 만든 후 다운로드한 이미지를 해당 폴더로 복사한다.

복사한 이미지를 앱의 자원으로 등록하기 위해서 pubspec.yaml 파일을 다음과 같이 수정한다.

# The following section is specific to Flutter.
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  assets:
    - images/lake.jpg
  #  - images/a_dot_ham.jpeg

위와 같이 수정한 후 yaml 파일을 저장하고 에러 메시지가 출력되지 않는다면 정상적으로 추가된 것이다.

다음과 같이 소스 코드를 수정한다.

import 'package:flutter/material.dart';

// MyApp을 시작 위젯으로 설정하여 앱을 실행
void main() => runApp(MyApp());

// 앱의 시작점에 해당하는 위젯
class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    // 본 앱의 테마의 대표색상을 필드에 저장
    Color color = Theme.of(context).primaryColor;

    // 버튼 로우 구성을 위한 컨테이너 위젯
    Widget buttonSection = Container(...);

    // 매트리얼앱을 생성하여 반환
    return MaterialApp(
        title: 'Flutter Layout Demo', // 앱의 타이틀
        theme: ThemeData( // 앱의 테마 설정
          primarySwatch: Colors.red,  // 주요 테마 색상
        ),
        // 홈 설정. 홈은 메트리얼앱의 시작점
        home:Scaffold(
          appBar: AppBar(
            title: Text("Flutter layout demo"),
          ),
          // 기존 body 삭제
          // body: Center(
          // child: Text("Hello World"),
          // ),
          // 새로운 body 선언
          body: Column( // 컬럼으로 교체
            // 자식들을 추가
            children: <Widget>[
              // 이미지 추가
              Image.asset(
                'images/lake.jpg',
                width: 600,
                height: 240,
                fit: BoxFit.cover,
              ),
              titleSection,  // titleSection 컨테이너 추가
              buttonSection,  // buttonSection 컨테이너 추가
              textSection // textSection 컨테이너 추가
            ],
          ),
        )
    );
  }

  // 타이틀 로우 구성을 위한 컨테이터 위젯 구현
  Widget titleSection = Container(...);

  // 버튼과 텍스트로 구성된 컬럼을 생성하여 반환하는 함수
  Column _buildButtonColumn(Color color,IconData icon, String label){...}

  // 텍스트로 구성된 컨테이너를 구현하는 위젯
  Widget textSection = Container(...);
}

 

MaterialApp의 body 컬럼에 첫번째 자식으로 Image를 추가하고 있다. fit 항목에서 이미지를 어떻게 출력할지 설정할 수 있으며 cover로 설정되면 이미지를 가능한 작게하여 할당된 영역을 채우게 된다. cover를 포함한 다른 설정 방식은 Flutter BoxFit 레퍼런스를 참고한다.

상기 소스코드의 실행 화면은 다음과 같다.

 

6 단계 - 마지막 손질

다음 소스 코드와 같이 body 항목을 컬럼에서 리스트뷰로 변경하였다. 이는 일부 화면의 크기가 작은 장비에서도 등록한 자식들이 스크롤되면서 정상 출력되도록 하기 위함이다.

// 앱의 시작점에 해당하는 위젯
class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    // 본 앱의 테마의 대표색상을 필드에 저장
    Color color = Theme.of(context).primaryColor;

    // 버튼 로우 구성을 위한 컨테이너 위젯
    Widget buttonSection = Container(...);

    // 매트리얼앱을 생성하여 반환
    return MaterialApp(
        title: 'Flutter Layout Demo', // 앱의 타이틀
        theme: ThemeData( // 앱의 테마 설정
          primarySwatch: Colors.red,  // 주요 테마 색상
        ),
        // 홈 설정. 홈은 메트리얼앱의 시작점
        home:Scaffold(
          appBar: AppBar(
            title: Text("Flutter layout demo"),
          ),
          // 기존 body 삭제
          // body: Center(
          // child: Text("Hello World"),
          // ),
          // 새로운 body 선언
          // body: Column( // 컬럼으로 교체
          body: ListView( // 리스트뷰로 교체
            // 자식들을 추가
            children: <Widget>[
              // 이미지 추가
              Image.asset(
                'images/lake.jpg',
                width: 600,
                height: 240,
                fit: BoxFit.cover,
              ),
              titleSection,  // titleSection 컨테이너 추가
              buttonSection,  // buttonSection 컨테이너 추가
              textSection // textSection 컨테이너 추가
            ],
          ),
        )
    );
  }

 


Flutter Code Examples 강좌를 추천합니다.

  • 제 블로그에서 Flutter Code Examples 프로젝트를 시작합니다.
  • Flutter의 다양한 예제를 소스코드와 실행화면으로 제공합니다.
  • 또한 모든 예제는 Flutter Code Examples 앱을 통해 테스트 가능합니다.

Flutter Code Examples 강좌로 메뉴로 이동

Flutter Code Examples 강좌 목록 페이지로 이동

Flutter Code Examples 앱 설치 | Google Play Store로 이동

 

Flutter Code Examples - Google Play 앱

Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.

play.google.com

Comments