본문 바로가기

flutter

웹 개발자가 바라본 Flutter 위젯

앞서 플러터를 왜 해야 되는지 말씀드렸다면 이번 글에서는 플러터를 직접 구현해보며 감을 익히는것을 목표로 해보겠습니다

 

 

 

가장 먼저 플러터 프로젝트를 명령어로 실행하면 다음과 같이 생성된다

flutter create 'project name'

 

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        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.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

 

 

복잡해보이지만 다뤄야 할 코드, 즉 중점적으로 봐야할 부분은 아래의 이부분입니다.

 

Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        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.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

 

 

전체적인 구조로 살펴보면 Scafford  라는 형태에서 (appbar, body,floatingActionButton) 이라는 parameter 가 있고 그 값에 각각 AppBar,Center,FloatingActionButton 이라는 객체가 선언 및 생성 된것을 알 수 있습니다.

각각의 객체는 모두 위젯 입니다. 그렇다면 다음과 같은 사실을 알 수 있습니다.

 

 

1. 모든것은 위젯으로 이루어 져있다.

 

다 제끼고 코드를 볼때는 '( )' 부분  즉 괄호로 이루어져 있는것만 먼저 보시면 됩니다. 

Scafford() 그리고 그 내부의 appbar 값에 AppBar(), body  값에 Center()

모두 결국 각 클래스( 위젯 ) 내부 그리고 그 내부에 위젯이  속하게 만들어 져 있습니다. 

결국 모든것은 위젯으로 이루어 져있습니다.

 

2. 구성요소 

 

Scafford 의 역할은 앱의 뼈대 역할을 합니다. 즉 아래 앱의 전체를 포괄하는 틀을 만든다고 보시면 됩니다.

 

 

그리고 appbar 는 상단 부분, 그리고 body 는 나머지 부분, floatingbutton 은 오른쪽 하단의 버튼 

 

자 그렇다면 웹개발을 이미 했봤다라는 가정하에 설명 하도록 하겠습니다.

 

1. 모든 것이 위젯이다: div  태그 대신 위젯을 사용

플러터는 모든 것이 위젯으로 이루어져 있습니다. 심지어 스타일 지정해주는것 또한 위젯입니다. 웹 개발로서는 다소 충돌되는 개념인데요. 한번 에제로서 살펴 보겠습니다. 

 

각각 웹과 flutter 의 코드 비교입니다.

 

웹 코드

<div style="display: flex; flex-direction: row;">
  <div>Item 1</div>
  <div>Item 2</div>
  <div>Item 3</div>
</div>

 

 

flutter 코드

Row(
  children: [
    Text('Item 1'),
    Text('Item 2'),
    Text('Item 3'),
  ],
)

 

div 태그로 감싸듯이 Row() 클래스 안의 children 이라는 프로퍼티에 배열로서 Text() 위젯을 넣는 형태 입니다. 

2. 스타일 바꾸기:  style 태그 대신 Flutter 는?

 

CSS에서 padding, margin과 같은 속성을 사용해 여백을 설정하듯, Flutter에서는 이런 스타일도 별도의 위젯으로 감쌉니다. 예를 들어, 웹에서 <div>에 padding을 주고 싶다면 CSS에서 padding: 20px; 같은 속성을 추가하겠지만, Flutter에서는 Padding 위젯으로 감싸서 설정합니다.

 

Padding(
  padding: EdgeInsets.all(20.0),
  child: Text('Hello, Flutter!'),
)

 

즉 웹에서는 HTML 요소와 CSS 스타일이 분리되어 있는 반면, Flutter에서는 모든 것이 위젯입니다. 이를 웹에서의 구성 요소로 비유하자면, Flutter의 Container는 HTML의

와 CSS 스타일을 하나로 묶어 놓은 것과 비슷합니다. 예를 들어, 웹에서는 div에 class를 적용하고 CSS로 스타일을 지정하지만, Flutter에서는 Container 하나로 스타일을 포함한 레이아웃을 제어할 수 있습니다.

 

대표적인 위젯들

 

1.  레이아웃 위젯

  • Row: 수평 방향으로 자식 위젯을 나열하는 데 사용됩니다. mainAxisAlignment와 crossAxisAlignment 속성을 통해 정렬을 설정할 수 있어, 화면의 수평 배치를 관리하는 데 유용합니다.
  • Column: 수직 방향으로 자식 위젯을 나열하는 데 사용됩니다. Row와 비슷하게 정렬 속성을 통해 배치를 조정할 수 있습니다. 예를 들어, 버튼을 세로로 나열하거나 이미지와 텍스트를 수직으로 쌓아올릴 때 Column을 사용합니다.
  • Stack: 겹쳐서 배치해야 할 때 사용하는 위젯입니다. 예를 들어, 배경 이미지 위에 텍스트를 겹쳐 놓는 것처럼, 위젯을 위로 쌓아 올릴 때 사용됩니다.
  • Container: 패딩, 여백, 색상, 크기, 테두리 등 스타일을 설정하는 데 많이 사용됩니다. 보통 다른 위젯을 감싸서 레이아웃을 조정하거나 스타일을 부여할 때 사용합니다.

 

웹 개발에서의 flex와 grid를 생각하면 됩니다. Flutter의 Row와 Column은 HTML/CSS에서 flex-direction: row와 flex-direction: column을 적용한 flex 컨테이너와 유사합니다.

예를 들어, HTML에서는 이렇게 수평 정렬을 할 것입니다:

 

2. 기본 위젯


기본 위젯은 UI의 기본적인 구성 요소를 나타냅니다:
Text: 텍스트를 표시합니다.
Image: 이미지를 표시합니다.
Icon: 아이콘을 표시합니다.
Button: ElevatedButton, TextButton 등 다양한 버튼 위젯이 있습니다.

 

레이아웃 위젯과 결합하면 다음과 같습니다.

Container(
  child: Column(
    children: [
      Text('Welcome to Flutter'),
      Image.network('https://example.com/image.jpg'),
      Icon(Icons.star),
      ElevatedButton(
        onPressed: () {},
        child: Text('Click me'),
      ),
    ],
  ),
)

 

3. GestureDetector


GestureDetector는 사용자의 제스처를 감지하고 반응하는 위젯입니다. 이 위젯을 사용하면 탭, 더블 탭, 롱 프레스, 드래그 등 다양한 사용자 상호작용을 처리할 수 있습니다 즉 태그로 치자면 onclick , onpress 이벤트를 처리한다고 보시면 됩니다.

 

GestureDetector(
  onTap: () {
    print('Widget tapped');
  },
  onDoubleTap: () {
    print('Widget double tapped');
  },
  onLongPress: () {
    print('Widget long pressed');
  },
  child: Container(
    width: 200,
    height: 200,
    color: Colors.blue,
    child: Center(child: Text('Interact with me')),
  ),
)
 

4. 컴포넌트 위젯 (Card, ElevatedButton 등)

 

Flutter의 Card나 ElevatedButton은 웹의 <button>, <div class="card">와 같은 UI 요소로 생각할 수 있습니다. 특히 ElevatedButton은 CSS에서 box-shadow나 border-radius를 적용하여 입체감을 준 버튼과 유사합니다.

예를 들어, 웹에서 그림자가 있는 카드 컴포넌트를 만든다면 HTML과 CSS를 이렇게 작성할 수 있습니다:

 
<div class="card">
  <p>카드 콘텐츠</p>
</div>

<style>
  .card {
    padding: 16px;
    border-radius: 8px;
    box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
    background-color: white;
  }
</style>

Flutter에서는 이와 비슷한 스타일을 Card 위젯 하나로 간단히 구성할 수 있습니다.

Card(
  elevation: 4,  // 그림자 크기 조정
  shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(8),
  ),
  child: Padding(
    padding: EdgeInsets.all(16.0),
    child: Text('카드 콘텐츠'),
  ),
)

 

정리

  • Flutter의 Row, Column은 웹의 flex 레이아웃과 비슷합니다.
  • Container는 HTML의 <div> + CSS 스타일을 합친 개념으로, 여백, 패딩, 색상 등을 하나로 설정합니다.
  • Flutter의 스타일은 단순 속성이 아닌 위젯으로 존재하여, 레이아웃을 위젯 트리로 구성하는 방식입니다.
  • Card나 ElevatedButton 같은 Flutter의 컴포넌트 위젯은 웹의 <button>, <div class="card">와 유사한 기능을 합니다.

웹 개발자가 Flutter를 이해할 때 모든 것이 위젯으로 구성된 일관된 레이아웃 트리라는 개념을 떠올리면 훨씬 수월하게 접근할 수 있을 것입니다.