본문 바로가기
Development/Flutter

Flutter - 7. Widget? Scaffold(AppBar, BottomNavigationBar, FloatingActionButton, Drawer)

by du.it.ddu 2021. 1. 17.
반응형

전부는 아니지만, 많은 모바일 앱이 공통으로 띄는 형태가 있다. (주관적입니다.)

  1. 앱의 상단에 제목, 뒤로가기, 검색 등의 옵션이 있는 AppBar
  2. 앱의 하단에 페이지를 이동할 수 있는 BottomNavigationBar
  3. 앱 혹은 앱의 현재 페이지의 대표적인 행동을 할 수 있는 FloatingActionButton
  4. 그리고 앱의 컨텐츠

위 네 가지의 형태가 가장 많이 보이는 형태가 아닐까 싶다.

 

대충 이런느낌?! (카카오 Oven을 사용해서 이해를 돕기 위해 만들었다. 프로토타이핑은 어렵다 ㅠㅠ)

위의 형태를 Flutter에서는 아주 손쉽게 구현할 수 있도록 제공하고 있다.
제목에도 쓰여있듯, Scaffold가 바로 그것이다.

이 포스팅 전 까지 몇 개의 Widget을 보면서 Scaffold가 쓰였었는데, 이제 정말 Scaffold를 활용해볼 차례다.
위 이미지 형태를 가진 앱을 구성해보자.


1. Scaffold란?

Scaffold의 사전적 정의는 "발판" 이다. 
Flutter에서도 이것을 의미하는것은 잘 모르겠으나, 개인적으론 맞다고 생각한다.
앱을 구성하기 위한 발판을 제공하는 Widget이라는 느낌을 받았기 때문이다.

api.flutter.dev/flutter/material/Scaffold-class.html

 

Scaffold class - material library - Dart API

Implements the basic material design visual layout structure. This class provides APIs for showing drawers, snack bars, and bottom sheets. To display a snackbar or a persistent bottom sheet, obtain the ScaffoldState for the current BuildContext via Scaffol

api.flutter.dev

공식문서에서 Scaffold class에 대한 설명을 보면, "basic material design visual layout structure"라고 되어있다.
구글은 머터리얼 디자인을 제공하고 있으며, 이 디자인을 따르는 기본적인 디자인 시각적 구조라는 것이다.
(참고로 iOS는 쿠퍼티노 디자인이다.)

 

Scaffold의 생성자를 보자. 굉장히 많지만, "Widget"을 받는 파라미터를 중점으로 보자.

appBar와 floatingActionButton, drawer, endDrawer, bottomNavigationBar, bottomSheet 파라미터가 있다.
여기서 위에서 소개한 것을 모두 받는다는 것을 확인할 수 있다. (bottomSheet는 이 포스팅에선 다루지 않는다.)

나머지 파라미터는 이 포스팅에선 사용되지 않을 예정이다.


2. Scaffold에 AppBar를 달아보자.

Flutter는 AppBar를 만들기 위한 Widget을 제공한다. 이름도 AppBar이다.

title과 검색과 같은 액션을 위한 List<Widget> actions를 전달할 수 있다.
나머지 UI를 위한 파라미터도 아주 많다.

이 포스팅에선 제목과 검색 옵션이 있는 AppBar를 만들어보겠다.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      ...
      home: SafeArea(
        child: Scaffold(
          appBar: _createAppBar(),
        )
      )
    );
  }

  Widget _createAppBar() {
    return AppBar(
      title: Text("My Flutter App"),
      actions: [
        IconButton(
          icon: Icon(Icons.search, color: Colors.white),
          onPressed: null,
        )
      ],
    );
  }
}

아주 간단한 코드로 제목과 검색 옵션(액션)이 있는 AppBar가 만들어졌다.
title, actions는 모두 Widget을 받으므로 원하는 형태의 Widget을 구성하면 될 것이다.


3. Scaffold에 BottomNavigationBar를 달아보자.

Flutter는 BottomNavigationBar 역시 기본으로 제공해준다.
BottomNavigationBar를 구성할 Items를 List<BottomNavigationBarItem> items로 전달할 수 있다.

아이템이 선택되었을 때 이벤트 처리를 위한 onTap, 현재 아이템의 인덱스는 물론, BottomNavigationBar를 디자인할 수 있는 파라미터도 아주 많다.

이 포스팅에선 간단하게 3개의 아이템으로 구성된 BottomNavigationBar를 만들어보자.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      ...
      home: SafeArea(
        child: Scaffold(
          ...
          bottomNavigationBar: _createBottomNavigationBar(),
        )
      )
    );
  }

  ...
  
  Widget _createBottomNavigationBar() {
    return BottomNavigationBar(
        backgroundColor: Colors.blue,
        selectedItemColor: Colors.white,
        unselectedItemColor: Colors.black,
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.map), label: "Map"),
          BottomNavigationBarItem(icon: Icon(Icons.print), label: "Print"),
          BottomNavigationBarItem(icon: Icon(Icons.stream), label: "Stream")
        ]
    );
  }
}

아주 간단하게 BottomNavigationBar를 만들었다.


다만 아이템들을 클릭하여도 변화는 없을 것이다. onTap 함수를 정의하여 페이지 클릭 시 상태를 변경하는 방법을 제공하여야 한다.
이 부분은 다음 포스팅을 기약하겠다.


4. Scaffold에 FloatingActionButtom을 달아보자.

FloatingActionButton은 화면에 둥둥 떠다니는 듯한 버튼이다.
이 포스팅에선 BottomNavigationBar의 오른쪽 끝 약간 위에 떠있는 FloatingActionButton을 만들것이다.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      ...
      home: SafeArea(
        child: Scaffold(
          ...
          floatingActionButton: _createFloatingActionButton(),
          floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
        )
      )
    );
  }

  ...
  
  Widget _createFloatingActionButton() {
    return FloatingActionButton(
      backgroundColor: Colors.blue,
      elevation: 8.0,
      child: Icon(Icons.add, color: Colors.white),
        onPressed: null
    );
  }
}

Scaffold에서 사용된 파라미터는 두 가지이지만, FloatingActionButton과 관련된 파라미터는 세 가지이다.

  1. floatingActionButton -> 화면에 나타낼 FloatingActionButton Widget을 전달받는다. Widget을 전달 받으므로, 위 예제처럼 FloatingActionButton일 필요는 없다.
  2. floatingActionButtonLocation -> 화면에 나타낼 FloatingActionButton의 위치를 결정한다. FloatingActionButtonLocation안에 다양한 상수가 정의되어 있다.
  3. floatingActionButtonAnimator -> FloatingActionButton이 상호작용하며 사용할 애니메이션을 결정한다.

 

하면의 우측 하단, BottomNavigationBar 위에 FloatingActionButton이 생겼다.


FloatingActionButton은 Scaffold에 포함된 Widget이므로 페이지가 변경해도 계속 떠 있게 된다.
물론 페이지에 따라 가려주는 로직을 구현하면 없앨 수 있고 버튼의 아이콘이나 형태 등을 변경하는 것이 가능하다.


5. Scaffold에 Drawer를 달아보자.

포스팅 초반에 앱의 기본적인 구성에 소개된 것은 아니지만, AppBar의 우측엔 action이 있다면 좌측엔 Drawer가 자주 등장한다. (혹은 뒤로가기 버튼이 제공된다.)

Drawer는 "서랍장" 같은 Widget을 의미한다. 열고 닫히는 듯하며 어떤 액션이나 정보(계정, 유저정보, 노트 앱의 폴더 이동 등)를 제공하는 기능을 한다.

Drawer 자체는 굉장히 심플한 생성자를 갖는다.
단일 child를 받지만, 내부에 리스트 형식을 갖게 한다던가 등 자유롭게 구성할 수 있다.

이 포스팅에선 Drawer의 Header에 유저 정보를, 그 아래엔 폴더 같은 리스트 구조를 갖게 하겠다.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      ...
      home: SafeArea(
        child: Scaffold(
          ...
          drawer: _createDrawer()
        )
      )
    );
  }

  ...
  
  Widget _createDrawer() {
    return Drawer(
      child: ListView(
        children: [
          DrawerHeader(
              child: Row(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Icon(Icons.person, color: Colors.white),
                  Text("du.it.ddu", style: TextStyle(color: Colors.white, fontSize: 16.0))
                ],
              ),
            decoration: BoxDecoration(
              color: Colors.blue
            ),
          ),
          _createFolderInDrawer("Flutter"),
          _createFolderInDrawer("Android"),
          _createFolderInDrawer("iOS"),
          _createFolderInDrawer("Something1"),
          ...
        ],
      ),
    );
  }

  Widget _createFolderInDrawer(String folderName) {
    return Container(
      padding: EdgeInsets.all(8.0),
      child: Text(folderName),
    );
  }
}

 

Drawer를 추가하면 AppBar의 타이틀 좌측에 자동으로 아이콘이 생긴다.
그리고 아이콘을 누르면 좌측에서 우측으로 Drawer가 나타난다.

DrawerHeader는 Drawer의 Header를 생성해주는 Widget이다. 물론 Header를 생성하기 위해 반드시 사용해야하는 것은 아니며, 본인이 원하는 Widget을 디자인해서 넣어주어도 무방하다.

BoxDecoration는 Widget들 중 형태를 디자인하기 위해 Decoration을 전달받는 Widget이 있다. BoxDecoration은 Decoration의 구현체 중 하나이다.

Drawer도 결국은 Widget이고 Widget을 그려주는 것에 불과하므로 자유롭게 구성하면 된다.


간단한 방법으로 앱의 구성을 알차게 만들어주는 Scaffold에 대해 알아보았다.

앱의 성격이나 디자인에 따라 Scaffold가 꼭 필요한 것은 아니다.
그리고 필요하다고 하여도 모든 구성요소를 사용할 필요도 없다.


중요한 것은 Flutter가 앱을 구성하기 위해 얼마나 편리한 방법을 제공하고 있고, 필요할 때 어떻게 활용할 수 있는지에 대한 것이라고 생각한다.

다음 포스팅은 앱 개발에 가장 자주 등장하는 Todo앱을 Flutter로 구현할 것이다.

반응형