본문 바로가기

Flutter

플러터 기본기 다지기 - 1

"Everything is a Widget" — Flutter의 핵심 철학

Flutter는 위젯 기반의 UI 프레임워크로, 모든 UI 요소를 위젯으로 표현한다. 이는 개발의 일관성과 효율성을 크게 높여준다.

 

선언적 UI 구성

Flutter는 React에서 영감을 받은 선언적 프로그래밍 방식을 채택했다. 개발자는 원하는 UI 상태를 직접 선언하며, 프레임워크가 이를 효율적으로 렌더링한다.

 

명령형 UI

💡 어떠한 상태가 되도록 명령한다.
ViewA a = ViewA();
ViewB b = ViewB();
a.setColor(red) // 빨간색이 되어라
b.setColor(yello) // 노란색이 되어라
a.add(b) // b는 a의 child가 되어라

 

 

선언형 UI

💡 어떠한 최종 상태를 선언한다
// 빨간색 A가 노란색 B를 child로 가지고 있다.
return ViewA(
  color: red,
  child: ViewB(
		color: yello,
	),
);

 

 


 

 

 

💡 Flutter는 선언형 UI 프레임워크라고 불린다.

위 예제에는 아래 상태들이 모여서 화면(UI)를 만들고 있습니다.

  • ViewA가 빨간색인 상태
  • ViewB가 노란색인 상태
  • ViewA가 ViewB를 Child로 가진 상태

선언형 UI
상태만 선언하면 UI는 자동으로 업데이트된다.
최종 상태 선언에만 집중할 수 있다.
예시) Flutter StatefulWidget

명령형 UI
상태 변경에 따른 UI 업데이트를 직접 명령한다.
UI를 보다 세밀하게 제어 할 수 있다.
예시) JavaScript를 이용한 DOM 조작

 

 


 

 

상태 기반 UI 관리

위젯은 현재 상태에 따라 UI를 정의한다. 상태 변화 시 Flutter는 필요한 부분만을 효과적으로 업데이트하여 성능을 최적화한다.

Flutter의 이점

이러한 접근 방식은 성능 향상 뿐만 아니라 개발자의 생산성도 높인다. 복잡한 UI 업데이트 로직 대신 위젯 구성에만 집중할 수 있기 때문이다.

요약하면, Flutter 개발은 위젯을 조합하여 반응형 크로스 플랫폼 애플리케이션을 만드는 과정이다.

 

 


 

 

 

나만의 Flutter 위젯 분류

위젯들을 이해하고 활용하는 것이 Flutter 개발의 핵심이다.

위젯의 종류

Flutter는 풍부한 UI 위젯을 제공합니다. 자세한 내용은 공식 문서에서 확인할 수 있다.

위젯들을 이해하기 쉽게 직접 나누어 보자 (공식적인 분류는 아직 존재하지 않는 것 같음)

1. 플랫폼별 위젯

  • Material (Android) 위젯
    • Google의 Material Design 가이드라인을 따르는 위젯들이다.
  • Cupertino (iOS) 위젯
    • Apple의 Human Interface Guidelines를 기반으로 한 iOS 스타일 위젯들이다.

대부분의 경우 Material 위젯을 사용하지만, 플랫폼별 특성을 살리고 싶다면 각각의 위젯을 적절히 활용할 수 있다.

 

 

2. 기본 위젯 (Basic Widgets)

  • Text: 다양한 스타일의 텍스트를 표현한다.
  • Row와 Column: CSS의 flexbox와 유사한 방식으로 수평, 수직 레이아웃을 구성한다.
  • Stack: 위젯들을 겹쳐 배치할 수 있으며, Positioned 위젯과 함께 사용하여 정교한 위치 조정이 가능한다.
  • Container: 다재다능한 박스 위젯으로, 배경, 테두리, 그림자 등 다양한 스타일링이 가능합니다.

이러한 위젯들을 조합하고 커스터마이징하여 원하는 UI를 구현할 수 있습니다. Flutter의 강력함은 이 위젯들의 유연성과 확장성에 있습니다.

3. 상태 관리에 따른 위젯 분류

Flutter에서는 상태 관리 방식에 따라 위젯을 두 가지로 분류한다:

  • Stateless Widget: 내부 상태가 없는 정적 위젯으로, 한 번 렌더링된 후 변경되지 않는다.
  • Stateful Widget: 동적 상태를 가진 위젯으로, 사용자 상호작용이나 이벤트에 따라 UI가 변경될 수 있다.

성능 최적화를 위해서는 가능한 Stateless Widget을 사용하는 것이 좋다. Stateless Widget은 빌드 시 한 번만 생성되지만, Stateful Widget은 상태 변경 시마다 재빌드되기 때문이다.

 

 


 

 

 

Flutter 프로젝트 구조 이해하기

Flutter 프로젝트를 효과적으로 개발하기 위해서는 기본 구조를 이해하는 것이 중요하다. 다음은 주요 구성 요소들이다.

  • .프로젝트 폴더 구조
    • 플랫폼별 폴더 (android, ios, linux, macos, web, windows): 각 플랫폼에 필요한 네이티브 코드 포함
    • lib 폴더: 주요 Dart 코드가 위치하는 곳
    • pubspec.yaml: 프로젝트 설정, 의존성, 리소스 등을 관리하는 파일
  • 시작점 (Entry Point)
    • lib/main.dart 파일의 main() 함수가 앱의 시작점
    • 새 프로젝트 시작 시 기본 예제 코드는 삭제하고 시작하는 것이 일반적

 

패키지와 임포트

  • 패키지: Flutter와 커뮤니티에서(다른 개발자들) 제공하는 재사용 가능한 코드 모듈
  • import 문으로 필요한 패키지와 위젯을 프로젝트에 추가
  • 패키지 종류:
    • Dart 기본 패키지 (예: import 'dart:math')
    • Flutter 제공 패키지 (예: import 'package:flutter/material.dart')
    • 외부 또는 사용자 정의 패키지 (예: import '../models/mypackage.dart')
import 'package:package_name/패키지경로/패키지명.dart';

 

 

main()과 runApp() 함수

  • Flutter 앱 시작 시 main() 함수 내의 runApp() 함수가 호출되어 앱 실행
// 안드로이드 UI 와 유사한 머티리얼 디자인 위젯을 사용하기 위해 import 구문 추가
import 'package:flutter/material.dart';

void main() {
	// 일반적으로 MyApp() 등의 클래스를 정의하여, 플러터 프로젝트를 실행함
	runApp(const MyApp()); 
}

 

 

참고: 신규 프로젝트 작성 시 많이 추가하는 설정

  • analysis_options.yaml 파일에 다음 설정 (코드 가이드를 disable 시키는 설정)
rules:
  prefer_typing_uninitialized_variables : false
  prefer_const_constructors_in_immutables : false
  prefer_const_constructors : false
  avoid_print : false

 

 


 

 

 

Basic Widget 살펴 보기

플러터에 공식적인 용어가 정립이 안되어 있음
  1. layout 위젯
  2. visible 위젯
    • 간단하고 기본적인 visible 위젯은 다음과 같음

 

Text 위젯

Text('Hello World')

 

 

Image 위젯

Image.asset('images/lake.jpg')

 

 

Icon 위젯

Icon(Icons.home)
Center(
  // Text('Hello World') 만 작성하면, 배경 방향을 알 수 없으므로 에러
  // TextDirection.ltr : 왼쪽에서 오른쪽으로 기술
  // TextDirection.rtl : 오른쪽에서 왼쪽으로 기술
  child: Text('Hello World', textDirection: TextDirection.ltr),
  // Icon 테스트
  // child: Icon(Icons.star, textDirection: TextDirection.ltr),
),

3. visible 위젯을 layout 위젯 안에 넣음

  • 모든 layout 위젯은 하나의 자식을 가질 수 있는 위젯과 여러 자식을 가질 수 있는 위젯으로 나눔
    • 하나의 자식을 가질 수 있는 위젯은 child라는 property를 사용해야 함
    • 여러 자식을 가질 수 있는 위젯은 children이라는 property를 사용해야 함
  • Center 위젯은 수직/수평의 가운데로 정렬하게 하는 위젯
  • visible 위젯은 배경 방향을 지정해줘야 함

 

마지막 라인에 콤마(,)를 붙이면, 다른 언어는 에러가 나지만, Flutter에서는 콤마(,)를 마지막 라인에 붙이는 것을 추천 함.
Center(
  // Text('Hello World') 만 작성하면, 배경 방향을 알 수 없으므로 에러
  // TextDirection.ltr : 왼쪽에서 오른쪽으로 기술
  // TextDirection.rtl : 오른쪽에서 왼쪽으로 기술
  child: Text('Hello World', textDirection: TextDirection.ltr),
  // Icon 테스트
  // child: Icon(Icons.star, textDirection: TextDirection.ltr),
),

 

 

 

실습 코드 1
import 'package:flutter/material.dart';

// 코드의 시작점
void main() {
  runApp(const MyApp());
}

// 상태기반으로 위젯을 분류 한다.
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.redAccent,
      child: Center(
        child: Icon(
          Icons.holiday_village_outlined,
          textDirection: TextDirection.ltr,
        ),
      ),
    );
  }
}

 

 

 

Container 위젯

  • 박스를 지정하여 padding, margin, border, background color 등을 설정할 수 있는 기본 위젯
  • Container 내에 여러 위젯을 나열하는 것이 일반적임

margin과 padding

  • 전부: EdgeInsets.all(10),
  • 특정 지정: EdgeInsets.only(top: 10, bottom: 10, left: 10, right: 10),
  • 인덱스, 위로, 오른쪽, 아래쪽: EdgeInsets.fromLTRB(50, 10, 30, 40),
  • 가로, 세로: EdgeInsets.symmetric(horizontal: 20, vertical: 50),
import 'package:flutter/material.dart';

// 코드의 시작점
void main() {
  runApp(const MyApp());
}

// 상태기반으로 위젯을 분류 한다.
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.amberAccent,
      margin: EdgeInsets.symmetric(vertical: 100, horizontal: 0),
      child: Center(
        child: Text('안녕 나의 위젯~', textDirection: TextDirection.ltr),
      ),
    );
  }
}

 

border

  • Container 위젯에 border를 적용하려면, decoration: BoxDecoration()을 넣어줘야 함
import 'package:flutter/material.dart';

// 코드의 시작점
void main() {
  runApp(const MyApp());
}

// 상태기반으로 위젯을 분류 한다.
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      // color: Colors.amberAccent, docoreation 속성 사용시 color 속성 넣으면 오류 발생
      margin: EdgeInsets.symmetric(vertical: 100, horizontal: 0),
      decoration: BoxDecoration(
        color: Colors.blue,
        border: Border.all(
          width: 1,
          color: Colors.black,
        ),
      ),
      child: Center(
        child: Text('안녕 나의 위젯~', textDirection: TextDirection.ltr),
      ),
    );
  }
}

 

 

border radius

  • 전부 적용1: BorderRadius.circular(10)
  • 전부 적용2: BorderRadius.all(Radius.circular(10))
  • 특정 방향만 적용: BorderRadius.only(topLeft: Radius.circular(10))
borderRadius: BorderRadius.only(
  topLeft: Radius.circular(50),
  topRight: Radius.circular(50),
  bottomLeft: Radius.circular(50),
  bottomRight: Radius.circular(50),
),
import 'package:flutter/material.dart';

// 코드의 시작점
void main() {
  runApp(const MyApp());
}

// 상태기반으로 위젯을 분류 한다.
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      // color: Colors.amberAccent, docoreation 속성 사용시 color 속성 넣으면 오류 발생
      margin: EdgeInsets.symmetric(vertical: 100, horizontal: 0),
      decoration: BoxDecoration(
        color: Colors.blue,
        borderRadius: BorderRadius.circular(50.0),
        // borderRadius: BorderRadius.all(
        //   Radius.circular(
        //     10.0,
        //   ),
        //),
        border: Border.all(
          width: 1,
          color: Colors.black,
        ),
      ),
      child: Center(
        child: Text('안녕 나의 위젯~', textDirection: TextDirection.ltr),
      ),
    );
  }
}

 

 

Row와 Column 위젯

  • Row 위젯: 위젯들을 수평으로 배치하는 위젯
  • Column 위젯: 위젯들을 수직으로 배치하는 위젯
  • Column과 Row 위젯은 children property를 가져서, 여러 자식을 가질 수 있음
    • children은 여러 자식을 가지므로, [] 리스트로 위젯을 표기해야 함

 

주축방향 정렬

mainAxisAlignment

: Row 위젯에서는 수평 정렬, Column 위젯에서는 수직 정렬을 설정

  • start: 주축의 시작점에서 자식 위젯들을 배치
  • end: 주축의 끝점에서 자식 위젯들을 배치
  • center: 주축의 중간에 자식 위젯들을 배치
  • spaceBetween: 자식 위젯 사이에 공간을 균등하게 배치
  • spaceAround: 자식 위젯 사이에 공간을 균등하게 배치하고, 첫 번째와 마지막 자식 위젯 앞뒤에 균등한 공간의 절반을 배치
  • spaceEvenly: 자식 위젯 사이에 공간을 균등하게 배치하고, 첫 번째와 마지막 자식 위젯 앞뒤에 균등한 공간을 배치
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';


void main() {
  runApp(MyHome());
}

// 상태 여부 위젯 선정 

// const, final
// const <--- 컴파일 시점에 초기화 되는 코드를 사용
// final <--- 런타임 시점에 초기화 되는 코드를 사용 
class MyHome extends StatelessWidget {
  // const 생성자
  const MyHome({super.key});

  // build 메서는 화면에 그림을 그려주는 메서드 이다.
  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      // 자료구조 - 리스트를 나태낸다.
      children: [
        Container(
          width: 50,
          height: 50,
          color: Colors.red,
          margin: const EdgeInsets.only(bottom: 50),
        ),
        Container(
          width: 50,
          height: 50,
          color: Colors.blue,
        ),

      ],
    );
  }
}

 

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';


void main() {
  runApp(MyHome());
}

// 상태 여부 위젯 선정 

// const, final
// const <--- 컴파일 시점에 초기화 되는 코드를 사용
// final <--- 런타임 시점에 초기화 되는 코드를 사용 
class MyHome extends StatelessWidget {
  // const 생성자
  const MyHome({super.key});

  // build 메서는 화면에 그림을 그려주는 메서드 이다.
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.green,
      child: Column(
        // 주축 방향 정렬
        mainAxisAlignment: MainAxisAlignment.center,
        // 교차축 방향 정렬
        crossAxisAlignment: CrossAxisAlignment.center,
        // 자료구조 - 리스트를 나태낸다.
        children: [
          Container(
            width: 50,
            height: 50,
            color: Colors.red,
            margin: const EdgeInsets.only(bottom: 50),
          ),
          Container(
            width: 50,
            height: 50,
            color: Colors.blue,
          ),

        ],
      ),
    );
  }
}

 

 


 

 

도전 과제

 

 

문제 풀이
import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(home: MyHome()));
}

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

  // build 에서는 화면에 그림을 그려주는 메서드이다.
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Container(
              width: 50,
              height: 50,
              color: Colors.redAccent,
            ),
            Container(
              width: 50,
              height: 50,
              color: Colors.blueAccent,
            ),
            Container(
              width: 50,
              height: 50,
              color: Colors.yellowAccent,
            ),
          ],
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.end,
          crossAxisAlignment: CrossAxisAlignment.end,
          children: [
            Container(
              width: 50,
              height: 50,
              color: Colors.redAccent,
            ),
            Container(
              width: 50,
              height: 50,
              color: Colors.blueAccent,
            ),
            Container(
              width: 50,
              height: 50,
              color: Colors.yellowAccent,
            ),
          ],
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Container(
              width: 50,
              height: 50,
              color: Colors.redAccent,
            ),
            Container(
              width: 50,
              height: 50,
              color: Colors.blueAccent,
            ),
            Container(
              width: 50,
              height: 50,
              color: Colors.yellowAccent,
            ),
          ],
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Container(
              width: 50,
              height: 50,
              color: Colors.redAccent,
            ),
            Container(
              width: 50,
              height: 50,
              color: Colors.blueAccent,
            ),
            Container(
              width: 50,
              height: 50,
              color: Colors.yellowAccent,
            ),
          ],
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Container(
              width: 50,
              height: 50,
              color: Colors.redAccent,
            ),
            Container(
              width: 50,
              height: 50,
              color: Colors.blueAccent,
            ),
            Container(
              width: 50,
              height: 50,
              color: Colors.yellowAccent,
            ),
          ],
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Container(
              width: 50,
              height: 50,
              color: Colors.redAccent,
            ),
            Container(
              width: 50,
              height: 50,
              color: Colors.blueAccent,
            ),
            Container(
              width: 50,
              height: 50,
              color: Colors.yellowAccent,
            ),
          ],
        )
      ],

    );
  }
}
728x90

'Flutter' 카테고리의 다른 글

SingleChildScrollView 위젯  (0) 2024.11.07
기초적인 Flutter 화면을 구성하는 패턴  (1) 2024.11.06
Flutter UI 프레임워크  (4) 2024.11.05
flutter App화면 구현  (1) 2024.09.04
컬렉션(자료구조)  (0) 2024.09.04