[Flutter] 15. 디자인 - 테마, 스타일, 반응형
앱의 전체 디자인을 통일하는 테마 설정과 반응형 레이아웃을 배웁니다.
ThemeData (앱 전체 테마)
테마 설정
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
theme: ThemeData(
// 색상 스킴
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
// AppBar 테마
appBarTheme: const AppBarTheme(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
elevation: 0,
),
// 버튼 테마
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
// 텍스트 테마
textTheme: const TextTheme(
headlineLarge: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
bodyLarge: TextStyle(fontSize: 16),
bodyMedium: TextStyle(fontSize: 14),
),
// 입력 필드 테마
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
),
useMaterial3: true,
),
home: const HomePage(),
);
}
}
다크 모드 지원
MaterialApp(
theme: ThemeData(
brightness: Brightness.light,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
),
darkTheme: ThemeData(
brightness: Brightness.dark,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.dark,
),
),
themeMode: ThemeMode.system, // system / light / dark
home: const HomePage(),
)
테마 값 사용
@override
Widget build(BuildContext context) {
// 현재 테마 가져오기
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return Column(
children: [
Text(
'제목',
style: theme.textTheme.headlineLarge,
),
Container(
color: colorScheme.primaryContainer,
child: Text(
'본문',
style: theme.textTheme.bodyLarge?.copyWith(
color: colorScheme.onPrimaryContainer,
),
),
),
],
);
}
텍스트 스타일
Text(
'스타일 적용 텍스트',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold, // 굵기
color: Colors.blue,
letterSpacing: 1.5, // 자간
height: 1.5, // 줄 간격
decoration: TextDecoration.underline, // 밑줄
fontStyle: FontStyle.italic, // 기울임
),
textAlign: TextAlign.center, // 정렬
maxLines: 2, // 최대 줄 수
overflow: TextOverflow.ellipsis, // 넘침 처리 (...)
)
반응형 레이아웃
MediaQuery (화면 크기 확인)
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
final screenHeight = MediaQuery.of(context).size.height;
return Scaffold(
body: screenWidth > 600
? _buildTabletLayout() // 태블릿
: _buildPhoneLayout(), // 폰
);
}
LayoutBuilder
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 800) {
// 넓은 화면: 2열 레이아웃
return Row(
children: [
Expanded(flex: 1, child: _buildSidebar()),
Expanded(flex: 3, child: _buildContent()),
],
);
} else {
// 좁은 화면: 1열 레이아웃
return _buildContent();
}
},
)
반응형 그리드
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: _getCrossAxisCount(context),
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
itemCount: 20,
itemBuilder: (context, index) => Card(child: Center(child: Text('$index'))),
)
int _getCrossAxisCount(BuildContext context) {
double width = MediaQuery.of(context).size.width;
if (width > 1200) return 4;
if (width > 800) return 3;
if (width > 600) return 2;
return 1;
}
실습 예제: 테마 전환 앱
import 'package:flutter/material.dart';
class ThemeSwitchApp extends StatefulWidget {
const ThemeSwitchApp({super.key});
@override
State<ThemeSwitchApp> createState() => _ThemeSwitchAppState();
}
class _ThemeSwitchAppState extends State<ThemeSwitchApp> {
ThemeMode _themeMode = ThemeMode.light;
void _toggleTheme() {
setState(() {
_themeMode = _themeMode == ThemeMode.light
? ThemeMode.dark
: ThemeMode.light;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
useMaterial3: true,
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.indigo,
brightness: Brightness.dark,
),
useMaterial3: true,
),
themeMode: _themeMode,
home: Scaffold(
appBar: AppBar(
title: const Text('테마 전환'),
actions: [
IconButton(
icon: Icon(
_themeMode == ThemeMode.light
? Icons.dark_mode
: Icons.light_mode,
),
onPressed: _toggleTheme,
),
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'현재 테마: ${_themeMode == ThemeMode.light ? "라이트" : "다크"}',
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _toggleTheme,
child: const Text('테마 변경'),
),
],
),
),
),
);
}
}
- [Flutter] 18. 빌드와 배포 - APK, App Store
- [Flutter] 17. 실전 프로젝트 - Todo 앱 만들기
- [Flutter] 16. 패키지 활용 - 유용한 패키지 소개
- [Flutter] 15. 디자인 - 테마, 스타일, 반응형
- [Flutter] 14. 로컬 저장소 - SharedPreferences, SQLite
- [Flutter] 13. HTTP 통신 - REST API 연동
- [Flutter] 12. 상태관리 - setState, Provider
- [Flutter] 11. 사용자 입력 - Form, TextField, 버튼
- [Flutter] 10. 화면 이동 - Navigation, Route
- [Flutter] 09. 리스트와 스크롤 - ListView, GridView
- [Flutter] 08. 레이아웃 - Row, Column, Stack
- [Flutter] 07. 위젯 기초 - StatelessWidget, StatefulWidget
- [Flutter] 06. Flutter 소개 및 개발환경 설치
- [Flutter] 05. Dart 비동기 - Future, async/await, Stream
- [Flutter] 04. Dart 클래스 - OOP 기초
- [Flutter] 03. Dart 함수 - 선언, 매개변수, 람다
- [Flutter] 02. Dart 제어문 - 조건문, 반복문
- [Flutter] 01. Dart 언어 기초 - 변수, 타입, 연산자