Flutter 튜토리얼 34편: 반응형 디자인 기초

요약#

핵심 요지#

  • 문제 정의: 하나의 앱이 휴대폰, 태블릿, 데스크톱 등 다양한 화면 크기에서 동작해야 하는데, 고정된 레이아웃은 모든 화면에서 잘 보이지 않는다.
  • 핵심 주장: Flutter의 반응형1 설계는 3단계 접근법(Abstract, Measure, Branch)을 따르며, MediaQuery2LayoutBuilder3를 상황에 맞게 사용해야 한다.
  • 주요 근거: MediaQuery.sizeOf()는 화면 전체 크기를 알려주고, LayoutBuilder는 부모가 허용한 공간을 알려준다. SafeArea는 노치와 시스템 UI를 피해 콘텐츠를 배치한다.
  • 실무 기준: 전체 레이아웃 변경은 MediaQuery, 위젯별 적응은 LayoutBuilder, 화면 가장자리 안전 영역은 SafeArea를 사용한다.

문서가 설명하는 범위#

  • 반응형 디자인과 적응형 디자인의 차이
  • 3단계 접근법: Abstract, Measure, Branch
  • MediaQuery.sizeOf()와 LayoutBuilder 비교
  • SafeArea로 안전 영역 확보하기
  • 반응형 디자인 베스트 프랙티스

읽는 시간: 18분 | 난이도: 중급


참고 자료#


문제 상황#

Flutter 앱을 만들었는데, 휴대폰에서는 잘 보이지만 태블릿에서는 어색합니다.
좁은 화면에 맞춰 만든 레이아웃이 넓은 화면에서 너무 늘어나 보입니다.
노치가 있는 기기에서는 콘텐츠가 잘려서 보입니다.

// 고정 크기 레이아웃 - 모든 화면에서 같은 모습
Scaffold(
body: Column(
children: [
Container(width: 300, height: 200), // 작은 화면에서 넘침
Row(children: [
Expanded(child: Card()),
Expanded(child: Card()),
]), // 넓은 화면에서 카드가 너무 늘어남
],
),
)

문제는 다음과 같습니다.

  • 작은 화면에서 콘텐츠가 넘치거나 잘린다.
  • 큰 화면에서 UI 요소가 과도하게 늘어난다.
  • 노치, 상태 바 등 시스템 UI와 콘텐츠가 겹친다.
  • 가로/세로 모드 전환 시 레이아웃이 깨진다.

해결 방법#

Flutter는 화면 크기에 따라 레이아웃을 조정하는 다양한 도구를 제공합니다.
반응형 디자인은 콘텐츠가 화면 크기에 맞게 흐르듯 조정되는 방식이고, 적응형 디자인4은 특정 중단점에서 완전히 다른 레이아웃을 보여주는 방식입니다.

챕터 1: 반응형과 적응형 디자인의 차이#

Why#

NOTE

“반응형”과 “적응형”이라는 용어가 혼용되어 혼란스러울 수 있습니다.
두 개념의 차이를 이해해야 상황에 맞는 접근법을 선택할 수 있습니다.

// 반응형: 화면 너비에 따라 패딩이 부드럽게 변함
padding: EdgeInsets.all(width * 0.05)
// 적응형: 특정 너비에서 완전히 다른 레이아웃
width > 600 ? TwoColumnLayout() : SingleColumnLayout()

What#

NOTE

반응형 디자인 (Responsive)

  • 화면 크기에 따라 콘텐츠가 연속적으로 조정됨
  • 비율 기반 크기, Flex 위젯 사용
  • 예: 카드 너비가 화면의 90%를 차지

적응형 디자인 (Adaptive)

  • 특정 중단점5에서 불연속적으로 레이아웃 변경
  • 조건문으로 다른 위젯 선택
  • 예: 600px 이하면 1열, 이상이면 2열
특성반응형적응형
변화 방식연속적불연속적
구현 방법비율, Flex조건문, 중단점
사용 예패딩, 폰트 크기레이아웃 구조 변경

How#

TIP

반응형 예시: 화면 비율에 맞는 패딩

class ResponsivePadding extends StatelessWidget {
const ResponsivePadding({super.key, required this.child});
final Widget child;
@override
Widget build(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
// 화면 너비의 5%를 패딩으로 사용
return Padding(
padding: EdgeInsets.symmetric(horizontal: width * 0.05),
child: child,
);
}
}

적응형 예시: 중단점에 따른 레이아웃 변경

class AdaptiveLayout extends StatelessWidget {
const AdaptiveLayout({super.key});
@override
Widget build(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
// 600px를 기준으로 레이아웃 변경
if (width < 600) {
return const MobileLayout();
} else if (width < 900) {
return const TabletLayout();
} else {
return const DesktopLayout();
}
}
}

실제 앱에서는 두 가지를 함께 사용합니다.
큰 구조는 적응형으로, 세부 요소는 반응형으로 처리합니다.

Watch out#

WARNING

반응형만 사용하면 극단적인 화면 크기에서 문제가 생깁니다.

// ❌ 문제: 아주 넓은 화면에서 텍스트가 너무 길어짐
Container(
width: MediaQuery.sizeOf(context).width * 0.9,
child: Text('매우 긴 텍스트...'),
)
// ✅ 해결: 최대 너비 제한
Container(
width: min(MediaQuery.sizeOf(context).width * 0.9, 800),
child: Text('매우 긴 텍스트...'),
)

적응형만 사용하면 중단점 근처에서 갑작스러운 레이아웃 변경이 일어나 사용자 경험이 나빠집니다.
두 접근법을 균형 있게 조합하세요.

결론: 반응형은 부드러운 조정에, 적응형은 구조적 변경에 사용합니다.


챕터 2: 3단계 접근법 (Abstract, Measure, Branch)#

Why#

NOTE

화면 크기에 따라 다른 UI를 보여주려면 체계적인 접근이 필요합니다.
무작정 조건문을 추가하면 코드가 복잡해지고 유지보수가 어려워집니다.

// ❌ 체계 없이 조건문을 추가한 복잡한 코드
Widget build(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
return Column(
children: [
if (width > 600) const SideMenu(),
Container(
padding: EdgeInsets.all(width > 600 ? 24 : 16),
child: width > 900
? const ThreeColumnGrid()
: width > 600
? const TwoColumnGrid()
: const SingleColumnList(),
),
],
);
}

Flutter 공식 문서는 Abstract → Measure → Branch 3단계를 권장합니다.

What#

NOTE

1단계: Abstract (추상화)

  • 화면 크기에 따라 달라지는 값을 추출
  • 예: 열 개수, 패딩 크기, 폰트 크기

2단계: Measure (측정)

  • MediaQuery나 LayoutBuilder로 현재 크기 측정
  • 필요한 정보만 측정 (너비, 높이, 방향 등)

3단계: Branch (분기)

  • 측정된 값에 따라 적절한 UI 선택
  • 추상화된 값을 사용하여 분기
flowchart LR A[Abstract<br/>변화 요소 추출] --> B[Measure<br/>현재 크기 측정] B --> C[Branch<br/>UI 선택]

How#

TIP

예제: 그리드 열 개수 조정

// 1단계: Abstract - 화면 크기에 따른 열 개수 정의
int getColumnCount(double width) {
if (width < 600) return 1;
if (width < 900) return 2;
if (width < 1200) return 3;
return 4;
}
// 2단계: Measure - 현재 너비 측정
// 3단계: Branch - 열 개수에 맞는 그리드 생성
class ResponsiveGrid extends StatelessWidget {
const ResponsiveGrid({super.key, required this.items});
final List<Widget> items;
@override
Widget build(BuildContext context) {
// Measure
final width = MediaQuery.sizeOf(context).width;
// Branch (Abstract 함수 활용)
final columns = getColumnCount(width);
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: columns,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
),
itemCount: items.length,
itemBuilder: (context, index) => items[index],
);
}
}

예제: 화면 크기 클래스 정의

// 1단계: Abstract - 화면 크기 클래스 정의
enum ScreenSize { compact, medium, expanded }
ScreenSize getScreenSize(double width) {
if (width < 600) return ScreenSize.compact;
if (width < 840) return ScreenSize.medium;
return ScreenSize.expanded;
}
// 2단계 & 3단계: Measure와 Branch
class AdaptiveScaffold extends StatelessWidget {
const AdaptiveScaffold({super.key, required this.body});
final Widget body;
@override
Widget build(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
final screenSize = getScreenSize(width);
return Scaffold(
// 큰 화면에서만 사이드 메뉴 표시
drawer: screenSize == ScreenSize.compact
? const AppDrawer()
: null,
body: Row(
children: [
// 중간 이상 화면에서 NavigationRail 표시
if (screenSize != ScreenSize.compact)
const NavigationRail(
destinations: [...],
selectedIndex: 0,
),
Expanded(child: body),
],
),
);
}
}

Watch out#

WARNING

중단점 값을 여러 곳에 하드코딩하면 일관성이 깨집니다.

// ❌ 중단점이 여러 곳에 흩어져 있음
if (width > 600) ... // 어떤 파일에서
if (width > 599) ... // 다른 파일에서 - 1픽셀 차이!
if (width >= 600) ... // 또 다른 파일에서
// ✅ 중단점을 한 곳에서 관리
abstract class Breakpoints {
static const double compact = 600;
static const double medium = 840;
static const double expanded = 1200;
}
// 사용
if (width >= Breakpoints.compact) ...

또한 너무 많은 중단점은 피하세요.
Material Design 3는 compact, medium, expanded 세 가지를 권장합니다.

결론: Abstract → Measure → Branch 패턴으로 반응형 코드를 체계적으로 구성합니다.


챕터 3: MediaQuery.sizeOf() 활용하기#

Why#

NOTE

화면 전체 크기를 기준으로 레이아웃을 결정해야 할 때가 있습니다.
”휴대폰인가 태블릿인가?”를 판단하려면 전체 화면 크기가 필요합니다.

// 화면 전체 크기에 따라 네비게이션 스타일 변경
final screenWidth = MediaQuery.sizeOf(context).width;
if (screenWidth > 600) {
return const TabletNavigation();
} else {
return const MobileNavigation();
}

What#

NOTE

MediaQuery.sizeOf(context)는 화면 전체 크기를 반환합니다.

final size = MediaQuery.sizeOf(context);
print(size.width); // 화면 너비 (픽셀)
print(size.height); // 화면 높이 (픽셀)

MediaQuery가 제공하는 주요 정보:

속성설명사용 예
size화면 크기레이아웃 분기
orientation가로/세로 방향방향별 레이아웃
padding시스템 UI 영역SafeArea 구현
viewInsets키보드 높이키보드 대응
textScaleFactor텍스트 크기 배율접근성 대응

How#

TIP

화면 방향에 따른 레이아웃 변경

class OrientationAwareLayout extends StatelessWidget {
const OrientationAwareLayout({super.key});
@override
Widget build(BuildContext context) {
final orientation = MediaQuery.orientationOf(context);
if (orientation == Orientation.landscape) {
// 가로 모드: 사이드바 + 콘텐츠
return Row(
children: [
const SizedBox(width: 200, child: Sidebar()),
const Expanded(child: MainContent()),
],
);
} else {
// 세로 모드: 콘텐츠만
return const MainContent();
}
}
}

텍스트 크기 배율 대응

class ScalableText extends StatelessWidget {
const ScalableText({super.key, required this.text});
final String text;
@override
Widget build(BuildContext context) {
final textScaler = MediaQuery.textScalerOf(context);
// 사용자가 시스템에서 텍스트 크기를 키웠을 때
// 레이아웃이 깨지지 않도록 최대 크기 제한
return Text(
text,
textScaler: textScaler.clamp(maxScaleFactor: 1.5),
);
}
}

of() vs sizeOf() 차이

// ❌ 전체 MediaQueryData를 구독 - 불필요한 리빌드 발생
final size = MediaQuery.of(context).size;
// ✅ size만 구독 - 다른 MediaQuery 값이 변해도 리빌드 안 함
final size = MediaQuery.sizeOf(context);

Watch out#

WARNING

MediaQuery.of(context)는 모든 MediaQuery 값이 변경될 때마다 위젯을 리빌드합니다.
키보드가 나타나거나 시스템 UI가 변경될 때도 리빌드됩니다.

// ❌ 키보드가 나타날 때마다 리빌드
Widget build(BuildContext context) {
final data = MediaQuery.of(context);
return Container(width: data.size.width * 0.5);
}
// ✅ 크기가 변할 때만 리빌드
Widget build(BuildContext context) {
final size = MediaQuery.sizeOf(context);
return Container(width: size.width * 0.5);
}

특정 속성만 필요하면 전용 메서드를 사용하세요:

  • MediaQuery.sizeOf(context) - 크기만
  • MediaQuery.orientationOf(context) - 방향만
  • MediaQuery.paddingOf(context) - 패딩만
  • MediaQuery.viewInsetsOf(context) - 키보드 영역만

결론: MediaQuery.sizeOf()로 화면 전체 크기를 효율적으로 측정합니다.


챕터 4: LayoutBuilder 활용하기#

Why#

NOTE

위젯이 실제로 사용할 수 있는 공간은 화면 크기와 다를 수 있습니다.
부모 위젯이 제한을 걸면 자식은 그 안에서만 레이아웃을 구성해야 합니다.

// MediaQuery는 화면 전체 크기를 반환
// 하지만 이 Container의 실제 공간은 부모가 결정
SizedBox(
width: 300, // 이 위젯 안에서
child: MyWidget(), // MyWidget이 쓸 수 있는 공간은 300px
)

LayoutBuilder3는 부모가 허용한 공간을 알려줍니다.

What#

NOTE

LayoutBuilder는 부모의 제약 조건6을 받아 자식을 빌드합니다.

LayoutBuilder(
builder: (context, constraints) {
// constraints.maxWidth: 사용 가능한 최대 너비
// constraints.maxHeight: 사용 가능한 최대 높이
// constraints.minWidth: 최소 너비 요구사항
// constraints.minHeight: 최소 높이 요구사항
return SomeWidget();
},
)

MediaQuery vs LayoutBuilder 비교

특성MediaQuery.sizeOf()LayoutBuilder
측정 대상화면 전체부모가 허용한 공간
업데이트 시점화면 크기 변경부모 제약 변경
사용 목적전역 레이아웃 결정위젯별 적응
중첩 영향없음부모에 따라 달라짐

How#

TIP

예제: 사용 가능한 공간에 맞는 카드 배치

class AdaptiveCardList extends StatelessWidget {
const AdaptiveCardList({super.key, required this.items});
final List<String> items;
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
// 부모가 허용한 너비에 따라 카드 크기 결정
final cardWidth = constraints.maxWidth > 600 ? 200.0 : 150.0;
final crossAxisCount = (constraints.maxWidth / cardWidth).floor();
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount.clamp(1, 4),
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
itemCount: items.length,
itemBuilder: (context, index) => Card(
child: Center(child: Text(items[index])),
),
);
},
);
}
}

예제: 재사용 가능한 반응형 위젯

// 이 위젯은 어디에 놓여도 자신의 공간에 맞게 적응
class ResponsivePanel extends StatelessWidget {
const ResponsivePanel({super.key, required this.child});
final Widget child;
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
// 좁은 공간에서는 세로 배치
if (constraints.maxWidth < 400) {
return Column(children: [const Header(), Expanded(child: child)]);
}
// 넓은 공간에서는 가로 배치
return Row(children: [const Sidebar(), Expanded(child: child)]);
},
);
}
}
// 어디에 놓여도 적응
// 1. 전체 화면에 놓으면 화면 크기에 맞게
// 2. Dialog 안에 놓으면 Dialog 크기에 맞게
// 3. 다른 위젯 안에 놓으면 그 위젯 크기에 맞게

Watch out#

WARNING

LayoutBuilder의 constraints가 무한대일 수 있습니다.
ListView나 SingleChildScrollView 안에서는 maxHeight가 double.infinity입니다.

// ❌ 무한 높이 문제
ListView(
children: [
LayoutBuilder(
builder: (context, constraints) {
// constraints.maxHeight == double.infinity!
// 높이 기반 계산이 불가능
return Container(height: constraints.maxHeight * 0.5); // 문제!
},
),
],
)
// ✅ 높이가 필요하면 ListView 밖에서 LayoutBuilder 사용
LayoutBuilder(
builder: (context, constraints) {
return ListView(
children: [
Container(height: constraints.maxHeight * 0.3),
// ...
],
);
},
)

또한 LayoutBuilder는 레이아웃 단계에서 실행됩니다.
성능에 민감한 작업은 builder 함수 안에서 피하세요.

결론: LayoutBuilder로 위젯이 실제 사용할 수 있는 공간에 맞게 적응합니다.


챕터 5: SafeArea로 안전 영역 확보하기#

Why#

NOTE

최신 기기에는 노치, 홈 인디케이터, 카메라 홀 등이 있습니다.
이런 영역에 콘텐츠가 배치되면 잘리거나 가려집니다.

// ❌ 노치가 있는 기기에서 텍스트가 가려짐
Scaffold(
body: Column(
children: [
Text('이 텍스트가 노치 뒤에 숨어요'),
// ...
],
),
)

What#

NOTE

SafeArea7는 시스템 UI를 피해 콘텐츠를 안전한 영역에 배치합니다.

SafeArea(
child: Text('이 텍스트는 항상 보여요'),
)

SafeArea가 피하는 영역:

영역설명예시
상단상태 바, 노치, 카메라 홀iPhone 노치
하단홈 인디케이터, 네비게이션 바iPhone 홈바
좌우둥근 모서리, 폴더블 힌지Galaxy Fold

SafeArea는 내부적으로 MediaQuery.paddingOf()를 사용합니다.

How#

TIP

기본 사용법

Scaffold(
body: SafeArea(
child: Column(
children: [
const Text('안전한 영역에 있어요'),
Expanded(child: ListView(...)),
],
),
),
)

특정 방향만 적용

SafeArea(
// 상단만 패딩 적용, 하단은 무시
top: true,
bottom: false,
left: true,
right: true,
child: Column(...),
)

최소 패딩 설정

SafeArea(
// 시스템 패딩이 없어도 최소 16px 보장
minimum: const EdgeInsets.all(16),
child: Column(...),
)

Scaffold와 함께 사용

Scaffold는 일부 SafeArea를 자동으로 처리합니다.

Scaffold(
appBar: AppBar(), // 상단 안전 영역 자동 처리
body: ListView(...), // body는 SafeArea 적용 안 됨
bottomNavigationBar: BottomNavigationBar(...), // 하단 자동 처리
)
// body에 SafeArea가 필요하면 직접 추가
Scaffold(
body: SafeArea(
child: ListView(...),
),
)

Watch out#

WARNING

SafeArea를 중첩하면 패딩이 중복 적용됩니다.

// ❌ 패딩이 두 번 적용됨
SafeArea(
child: Column(
children: [
SafeArea( // 불필요한 중첩
child: Text('패딩이 너무 많아요'),
),
],
),
)

또한 MediaQuery.removePadding()을 사용하면 하위 위젯에서 SafeArea가 작동하지 않을 수 있습니다.

// SafeArea 효과를 제거하고 직접 패딩 관리할 때
MediaQuery.removePadding(
context: context,
removeTop: true,
child: ListView(...), // 상단 패딩이 제거됨
)

스크롤 가능한 위젯에서는 SliverSafeArea를 고려하세요.

CustomScrollView(
slivers: [
SliverSafeArea(
sliver: SliverList(...),
),
],
)

결론: SafeArea로 시스템 UI와 겹치지 않는 안전한 콘텐츠 영역을 확보합니다.


챕터 6: 반응형 디자인 베스트 프랙티스#

Why#

NOTE

반응형 디자인을 잘못 구현하면 코드가 복잡해지고 성능이 떨어집니다.
검증된 패턴을 따르면 유지보수가 쉽고 일관된 사용자 경험을 제공할 수 있습니다.

// ❌ 조건문이 여기저기 흩어져 있음
if (MediaQuery.sizeOf(context).width > 600) ...
if (MediaQuery.sizeOf(context).width > 599) ... // 1픽셀 차이!

What#

NOTE

Flutter 공식 문서가 권장하는 핵심 원칙:

  1. Flex 위젯 우선: Row, Column, Wrap, Flex로 유연한 레이아웃
  2. ConstrainedBox 활용: 최소/최대 크기 제한으로 극단적 상황 대응
  3. 중단점 중앙화: 상수로 정의하여 일관성 유지
  4. 성능 고려: 불필요한 리빌드 방지

How#

TIP

1. Flex 위젯으로 유연한 레이아웃

// Wrap: 공간이 부족하면 자동 줄바꿈
Wrap(
spacing: 8,
runSpacing: 8,
children: [
for (final item in items)
Chip(label: Text(item)),
],
)
// Flexible: 남은 공간을 비율로 분배
Row(
children: [
Flexible(flex: 2, child: LeftPanel()),
Flexible(flex: 3, child: RightPanel()),
],
)

2. 최대/최소 크기 제한

// 너무 넓어지지 않도록 제한
Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 800),
child: Content(),
),
)
// FractionallySizedBox: 부모의 비율로 크기 지정
FractionallySizedBox(
widthFactor: 0.8, // 부모 너비의 80%
child: Card(),
)

3. 중단점 중앙 관리

breakpoints.dart
abstract class Breakpoints {
// Material Design 3 기준
static const double compact = 600;
static const double medium = 840;
static const double expanded = 1200;
static bool isCompact(BuildContext context) =>
MediaQuery.sizeOf(context).width < compact;
static bool isMedium(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
return width >= compact && width < medium;
}
static bool isExpanded(BuildContext context) =>
MediaQuery.sizeOf(context).width >= medium;
}
// 사용
if (Breakpoints.isCompact(context)) {
return const MobileLayout();
}

4. 반응형 위젯 래퍼 패턴

class ResponsiveBuilder extends StatelessWidget {
const ResponsiveBuilder({
super.key,
required this.compact,
this.medium,
this.expanded,
});
final Widget compact;
final Widget? medium;
final Widget? expanded;
@override
Widget build(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
if (width >= 840 && expanded != null) return expanded!;
if (width >= 600 && medium != null) return medium!;
return compact;
}
}
// 사용
ResponsiveBuilder(
compact: const MobileView(),
medium: const TabletView(),
expanded: const DesktopView(),
)

Watch out#

WARNING

성능 주의사항

  1. MediaQuery.of() 대신 특정 속성용 메서드 사용
// ❌ 모든 MediaQuery 변경에 반응
final width = MediaQuery.of(context).size.width;
// ✅ 크기 변경에만 반응
final width = MediaQuery.sizeOf(context).width;
  1. 불필요한 리빌드 방지
// ❌ 매 빌드마다 새 객체 생성
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(...), // 매번 새로 생성
);
}
// ✅ const 또는 캐싱 사용
static const _decoration = BoxDecoration(...);
Widget build(BuildContext context) {
return Container(decoration: _decoration);
}
  1. 중단점 변경 시 과도한 애니메이션 피하기
// 중단점에서 레이아웃이 갑자기 바뀌면 어색함
// AnimatedSwitcher로 부드러운 전환 고려
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: Breakpoints.isExpanded(context)
? const ExpandedLayout(key: ValueKey('expanded'))
: const CompactLayout(key: ValueKey('compact')),
)

결론: 검증된 패턴을 따라 유지보수가 쉽고 성능 좋은 반응형 UI를 만듭니다.


한계#

이 문서는 반응형 디자인의 기초를 다룹니다.
다음 주제는 별도로 학습해야 합니다.

  • 대형 화면 최적화: 태블릿, 데스크톱 전용 레이아웃 패턴
  • 폴더블 대응: 힌지 영역 처리, 화면 분할
  • 플랫폼별 적응: iOS/Android/Web 각각의 디자인 컨벤션
  • 접근성: 텍스트 크기 조정, 고대비 모드 대응

Footnotes#

  1. responsive(반응형): 화면 크기에 따라 콘텐츠가 연속적으로 조정되는 디자인 방식이다.

  2. MediaQuery(미디어쿼리): 화면 크기, 방향, 패딩 등 디바이스 정보를 제공하는 Flutter 위젯이다.

  3. LayoutBuilder(레이아웃빌더): 부모가 허용한 제약 조건을 받아 자식을 빌드하는 위젯이다. 2

  4. adaptive(적응형): 특정 중단점에서 완전히 다른 레이아웃으로 전환하는 디자인 방식이다.

  5. breakpoint(중단점): 레이아웃이 변경되는 화면 너비 기준점이다.

  6. constraints(제약 조건): 부모가 자식에게 전달하는 최소/최대 크기 정보다.

  7. SafeArea(세이프에리어): 노치, 상태 바 등 시스템 UI를 피해 콘텐츠를 배치하는 위젯이다.

공유

이 글이 도움이 되었다면 다른 사람과 공유해주세요!

Flutter 튜토리얼 34편: 반응형 디자인 기초
https://moodturnpost.net/posts/flutter/flutter-responsive-design-basics/
작성자
Moodturn
게시일
2026-01-08
Moodturn

목차