테마 및 스타일 적용, 커스텀 위젯 제작 기초
학습 목표
- 앱 전체 테마 설정: ThemeData로 색상·글꼴·전반적 스타일 정의
- 텍스트 스타일 관리: TextStyle로 주요 텍스트 속성 통일
- 커스텀 위젯 제작: Stateless/Stateful 위젯 조합해 재사용 가능한 버튼·리스트 아이템 만들기
학습전 둘러보기
카테고리 | 위젯·클래스 | 주요 속성·메서드·이벤트 | 사용 패키지 |
권한 관리 | SwitchListTile | value, onChanged → Permission.request(), Permission.status | permission_handler |
Permission | Permission.photos, Permission.location, Permission.calendar | ||
테마 & 언어 | DropdownButton<ThemeMode> | value, items, onChanged → ThemeMode.light/dark/system | provider |
DropdownButton<Locale> | value, items, onChanged → Locale('ko'), Locale('en') | flutter_localizations, intl | |
ThemeData | primaryColor, accentColor, fontFamily, textTheme, cardTheme, inputDecorationTheme | ||
알림 설정 | FlutterLocalNotificationsPlugin | initialize(), zonedSchedule(), show() | flutter_local_notifications |
SwitchListTile | value, onChanged | ||
showTimePicker() | 시간 선택 다이얼로그 | ||
달력 연동 | TableCalendar | firstDay, lastDay, focusedDay, onDaySelected, calendarStyle, headerStyle | table_calendar |
일기예보 | FutureBuilder | future, builder | http |
http.get(), jsonDecode() | OpenWeatherMap API 호출 | ||
CircularProgressIndicator | 로딩 인디케이터 | ||
지도 연동 | GoogleMap | initialCameraPosition, markers, onMapCreated | google_maps_flutter |
Marker | markerId, position, infoWindow | ||
저장·동기화 | ListTile + IconButton | onTap, trailing | shared_preferences, hive, OAuth/Firebase |
캐시·데이터 관리 | ListTile + AlertDialog | showDialog(), AlertDialog(actions: [...]) | 기본 Flutter |
폰트 & 텍스트 | ThemeData(fontFamily) | 전역 폰트 설정 | |
Slider 또는 DropdownButton | 텍스트 크기 조절 | 기본 Flutter | |
계정 관리 | ListTile, CircleAvatar | 프로필 사진, onTap → 화면 전환 | Firebase Auth, REST API |
일기 보안 | SwitchListTile | value, onChanged → flutter_secure_storage, local_auth | flutter_secure_storage, local_auth |
- 이벤트 처리: 대부분 위젯의 onChanged, onTap, onPressed 콜백
- 상태 관리: provider 또는 riverpod 와 ChangeNotifier/StateNotifier 조합
- 저장소: shared_preferences·hive 등으로 사용자 설정값 로컬 저장
🔷 앱 전체 테마 설정: ThemeData로 색상·글꼴·전반적 스타일 정의
1. ThemeData 기본 이해
ThemeData
- 앱 전역 색상(primary, accent 등), 글꼴(family), 버튼·앱바·스낵바 테마 등을 한 곳에서 관리
- 참고: 패키지 폰트 등록 방법
- 예시 ( Flutter에서 앱 전체의 디자인 스타일을 정의하는 ThemeData 객체를 만들기 )
final ThemeData appTheme = ThemeData(
primaryColor: Colors.indigo,
accentColor: Colors.amber,
fontFamily: 'NanumGothic', // pubspec.yaml에 추가 필요 (패키지 폰트)
textTheme: TextTheme(
headline6: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
bodyText2: TextStyle(fontSize: 16),
),
buttonTheme: ButtonThemeData(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
buttonColor: Colors.indigo,
),
snackBarTheme: SnackBarThemeData(
backgroundColor: Colors.grey[800],
contentTextStyle: TextStyle(color: Colors.white),
),
tabBarTheme: TabBarTheme(
labelColor: Colors.amber,
unselectedLabelColor: Colors.white70,
),
);
- primaryColor: 앱의 주 색상으로 indigo(남색)를 사용합니다.
- accentColor: 강조 색상으로 amber(호박색)를 지정합니다.
- fontFamily: 'NanumGothic' 폰트를 사용하며, pubspec.yaml에 추가해야 합니다.
- textTheme: 텍스트의 기본 스타일을 설정합니다.
- headline6: 크기 20, 볼드체로 제목 스타일.
- bodyText2: 크기 16의 일반 텍스트.
- buttonTheme: 버튼의 모양과 색상.
- 폼은 둥근 모서리(반지름 8).
- 버튼 색상은 indigo.
- snackBarTheme: 스낵바(알림창)의 배경과 텍스트 스타일.
- 배경은 짙은 회색(Colors.grey[800]).
- 텍스트 색상은 흰색.
- tabBarTheme: 탭바의 활성/비활성 레이블 색상.
- 선택된 탭은 amber.
- 비활성 탭은 연한 흰색(Colors.white70).
이렇게 하면 앱의 전체 디자인을 일관되게 꾸밀 수 있어요!
2. 글꼴(Font) 적용
- pubspec.yaml에 TTF/OTF 파일 등록
- flutter > fonts: Flutter 앱에서 사용할 폰트 목록입니다.
- family: NanumGothic: 폰트 이름을 "NanumGothic"으로 지정합니다.
- fonts > asset: 폰트 파일이 저장된 위치를 지정합니다. 여기서는 assets/fonts/NanumGothic-Regular.ttf라는 폰트 파일을 사용합니다.
flutter: fonts: - family: NanumGothic fonts: - asset: assets/fonts/NanumGothic-Regular.ttf
- ThemeData(fontFamily: 'NanumGothic') 로 전역 설정
3. Theme 접근·사용
https://docs.flutter.dev/cookbook/design/themes
Theme.of(context) 로 현재 테마 정보 가져오기
Text(
'안녕하세요',
style: Theme.of(context).textTheme.headline6,
);
다크/라이트 모드
1. 프로젝트 기본 설정
MaterialApp(
theme: ThemeData.light(), // 라이트 모드 테마
darkTheme: ThemeData.dark(), // 다크 모드 테마
themeMode: ThemeMode.system, // 시스템 설정을 따름
);
- MaterialApp 위젯만 잘 활용하면 앱 전체 테마 관리를 한눈에 할 수 있습니다.
- theme: 라이트 모드일 때 적용될 디자인
- darkTheme: 다크 모드일 때 적용될 디자인
- themeMode
- system : 기기 설정과 동기화
- light : 항상 라이트 모드
- dark : 항상 다크 모드
2. 테마 상태 관리 클래스 만들기
- 사용자 선택을 반영하려면 상태 관리가 필요합니다. 여기서는 ChangeNotifier 기반의 간단한 클래스를 사용합니다.
import 'package:flutter/material.dart'; class ThemeNotifier with ChangeNotifier { bool _isDarkMode = false; // 초기 상태: 라이트 bool get isDarkMode => _isDarkMode; void toggleTheme() { _isDarkMode = !_isDarkMode; notifyListeners(); } }
- _isDarkMode : 현재 테마 상태 저장
- toggleTheme() : 값을 반전시키고 구독 위젯에 변경 알림
3. Provider로 연결하기
- pubspec.yaml에 provider 추가 후, 최상위에 ChangeNotifierProvider를 감싸 줍니다.
dependencies: flutter: sdk: flutter provider: ^6.0.0
void main() { runApp( ChangeNotifierProvider( create: (_) => ThemeNotifier(), child: MyApp(), ), ); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { final themeNotifier = Provider.of<ThemeNotifier>(context); return MaterialApp( theme: ThemeData.light(), darkTheme: ThemeData.dark(), themeMode: themeNotifier.isDarkMode ? ThemeMode.dark : ThemeMode.light, home: MyHomePage(), ); } }
- Provider.of<ThemeNotifier>(context)로 상태 읽기
- themeMode에 반영해 전체 테마 즉시 변경
4. 사용자 인터페이스에 스위치 추가하기
- 설정 화면이나 앱바에 토글 스위치를 두면 사용자가 쉽게 전환할 수 있습니다.
class MyHomePage extends StatelessWidget { @override Widget build(BuildContext context) { final notifier = Provider.of<ThemeNotifier>(context); return Scaffold( appBar: AppBar(title: Text('테마 설정')), body: Center( child: SwitchListTile( title: Text('다크 모드'), value: notifier.isDarkMode, onChanged: (_) { Provider.of<ThemeNotifier>(context, listen: false) .toggleTheme(); }, ), ), ); } }
- SwitchListTile으로 직관적인 토글 UI
- listen: false 옵션으로 불필요한 리빌드를 방지
+
-
- 초기값 저장 : shared_preferences에 사용자의 마지막 선택을 저장해 재시작해도 유지
- 부드러운 전환 : AnimatedTheme 위젯을 감싸 테마 변경 시 페이드 효과 주기
- 커스텀 테마 : 색상·폰트를 직접 정의해 브랜드 아이덴티티 반영
- 테마별 리소스 교체 : 이미지, 아이콘을 다크/라이트 버전으로 나눠 적절히 로드
🔷 텍스트 스타일 관리: TextStyle로 주요 텍스트 속성 통일
Flutter 앱에서 일관된 텍스트 디자인을 유지하려면 TextStyle을 활용해 주요 텍스트 속성을 통일해야 합니다. 이를 통해 유지보수성과 가독성을 모두 높일 수 있습니다.
1.TextStyle 주요 속성
- fontSize: 글자 크기 (e.g. 14.0)
- fontWeight: 글자 굵기 (e.g. FontWeight.bold)
- fontStyle: 기울임체 여부 (FontStyle.italic)
- color: 글자 색상
- letterSpacing: 자간 조절
- wordSpacing: 단어 간격
- height: 줄 높이(행간)
- fontFamily: 폰트 패밀리
2. 개별 위젯에 적용하기
Text(
'안녕하세요',
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.w500,
color: Colors.black87,
),
);
3. Theme에 통합해 전역 관리하기
- ThemeData의 textTheme 설정
final appTheme = ThemeData(
textTheme: TextTheme(
headline1: TextStyle(fontSize: 96, fontWeight: FontWeight.light),
headline6: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
bodyText2: TextStyle(fontSize: 16),
),
);
- MaterialApp에 적용
MaterialApp( theme: appTheme, home: MyHomePage(), );
- Theme.of(context).textTheme 사용
Text( '제목 텍스트', style: Theme.of(context).textTheme.headline6, );
4. 커스텀 TextStyle 묶음 정의
- Styles 클래스 생성
class Styles {
static const header = TextStyle(
fontSize: 24, fontWeight: FontWeight.bold, color: Colors.blue);
static const body = TextStyle(
fontSize: 16, fontWeight: FontWeight.normal);
static const caption = TextStyle(
fontSize: 12, color: Colors.grey);
}
- 위젯 적용
Text('본문', style: Styles.body);
Text('설명', style: Styles.caption);
다음은 요청하신 내용을 공부용 블로그 포맷에 맞춰 보기 좋고 실용적으로 정리한 예시입니다. 각 항목은 주제별로 묶고, 커스텀 위젯 제작 설명도 추가했습니다.
🔷 Flutter 실전 위젯 커스터마이징 가이드
Flutter 앱을 제작하면서 재사용 가능한 UI 컴포넌트를 만들고, 테마·애니메이션·권한 등 기능을 확장하는 방법을 정리했다.
📌 1. 커스텀 위젯 제작 – 구조화 & 재사용
✅ Stateless 위젯으로 기본 버튼 만들기
class PrimaryButton extends StatelessWidget {
final String label;
final VoidCallback onTap;
const PrimaryButton({ required this.label, required this.onTap });
@override
Widget build(BuildContext context) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Theme.of(context).primaryColor,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
onPressed: onTap,
child: Text(label, style: TextStyle(color: Colors.white)),
);
}
}
- label: 버튼에 표시할 텍스트
- onTap: 버튼 클릭 시 실행할 콜백
- UI 스타일은 테마 기반으로 통일감 유지
✅ Stateful 위젯을 활용한 할 일 리스트 항목
class TodoItem extends StatefulWidget {
final String text;
final VoidCallback onDelete;
const TodoItem({ required this.text, required this.onDelete });
@override
_TodoItemState createState() => _TodoItemState();
}
class _TodoItemState extends State<TodoItem> {
bool _checked = false;
@override
Widget build(BuildContext context) {
return ListTile(
leading: Checkbox(
value: _checked,
onChanged: (v) => setState(() => _checked = v!),
),
title: Text(widget.text),
trailing: IconButton(
icon: Icon(Icons.delete, color: Colors.redAccent),
onPressed: widget.onDelete,
),
);
}
}
- 체크박스 상태 _checked는 내부에서 관리
- 삭제 버튼은 외부로 콜백 전달
- ListTile 구성으로 레이아웃 일관성 확보
🎨 2. 테마 & 다국어 설정 – Provider 패턴
Provider 설정
dependencies:
provider: ^6.0.0
flutter_localizations:
flutter:
uses-material-design: true
generate: true
테마·언어 설정 모델
class SettingsNotifier with ChangeNotifier {
ThemeMode themeMode = ThemeMode.system;
Locale locale = Locale('ko');
void setTheme(ThemeMode m) {
themeMode = m;
notifyListeners();
}
void setLocale(Locale l) {
locale = l;
notifyListeners();
}
}
UI 예시
final settings = context.watch<SettingsNotifier>();
DropdownButton<ThemeMode>(
value: settings.themeMode,
items: ThemeMode.values.map((m) =>
DropdownMenuItem(value: m, child: Text(m.name))).toList(),
onChanged: settings.setTheme,
);
- Provider로 상태 공유
- UI 전역 설정을 컨트롤 가능
✨ 3. 애니메이션 적용
✅ Implicit 애니메이션
AnimatedContainer(
duration: Duration(milliseconds: 300),
width: _expanded ? 200 : 100,
height: 50,
child: ElevatedButton(...),
)
- 간단한 크기·색상 전환에 유용
✅ Explicit 애니메이션
AnimationController + Tween + AnimatedBuilder
- 복잡한 트랜지션에 사용
- Hero 위젯으로 화면 전환 강조 가능
📦 4. 카드 · 리스트 · 폼 스타일 커스터마이징
ThemeData 설정
ThemeData(
cardTheme: CardTheme(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
elevation: 4,
),
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(),
filled: true,
fillColor: Colors.grey[100],
),
)
- CardTheme, FormFieldTheme 활용으로 일관된 스타일링
- ListView.separated 사용 시 dividerColor로 간격 조절
🔐 5. 권한 설정
permission_handler 설정
dependencies:
permission_handler: ^10.2.0
Future<void> _checkPermission(Permission perm) async {
final status = await perm.request();
}
SwitchListTile(
title: Text('위치 권한'),
value: false,
onChanged: (_) => _checkPermission(Permission.location),
)
- 권한 요청 후 상태에 따라 로직 처리 가능
🔔 6. 알림 설정
flutter_local_notifications 설정
dependencies:
flutter_local_notifications: ^12.0.0
await _plugin.zonedSchedule(
0,
'일기 쓰기 알림',
'오늘 하루를 기록해보세요!',
tz.TZDateTime.local(...),
const NotificationDetails(
android: AndroidNotificationDetails('diary', 'Diary Reminders'),
),
matchDateTimeComponents: DateTimeComponents.time,
);
- 특정 시간대 반복 알림
- 야간 모드 off 스위치 등 커스터마이징 가능
🗓 7. 달력 연동 (table_calendar)
dependencies:
table_calendar: ^3.0.9
TableCalendar(
firstDay: DateTime.utc(2020, 1, 1),
lastDay: DateTime.utc(2030, 12, 31),
focusedDay: DateTime.now(),
calendarStyle: CalendarStyle(
todayDecoration: BoxDecoration(color: Colors.blueAccent, shape: BoxShape.circle),
),
onDaySelected: (d, f) {
// 스케줄 상세 페이지로 이동
},
)
- 커스텀 데코레이션 가능
- 선택일, 오늘 강조 스타일 분리
🌤 8. 날씨 위젯 – OpenWeatherMap API 연동
dependencies:
http: ^0.13.4
flutter_svg: ^1.0.3
final res = await http.get(Uri.parse('https://api.openweathermap.org/data/2.5/weather?q=Seoul&appid=YOUR_KEY'));
final data = jsonDecode(res.body);
Text('${data['main']['temp']}°C');
Text(data['weather'][0]['description']);
- 날씨 아이콘은 flutter_svg로 표현 가능
- 날씨 정보 시각화 시 LineChart 적용 가능
🗺 9. 지도 연동 – Google Maps
dependencies:
google_maps_flutter: ^2.2.0
GoogleMap(
initialCameraPosition: CameraPosition(target: LatLng(37.5665, 126.9780), zoom: 12),
markers: {
Marker(markerId: MarkerId('here'), position: LatLng(37.5665, 126.9780)),
},
)
- 커스텀 마커, 클러스터링, 경로 표시 가능
- Theme.of(context) 활용으로 색상 통일 가능
🖼 10. 블로그 형식 레이아웃 팁
- 비율 유지: AspectRatio, ClipRRect
- 텍스트 흐름: Padding, RichText
- 문서화: flutter_markdown 패키지 활용
✅ TIP
- 커스텀 위젯으로 반복 UI 관리
- 테마·언어·애니메이션은 앱 완성도에 직결
- Flutter는 위젯 조합력과 상태 관리가 핵심
실습 과제
- PrimaryButton, SecondaryButton 커스텀 위젯 제작
- TodoItem 위젯 구현 및 삭제·완료 토글 기능 추가
- 전역 폰트, 스낵바, 탭바 스타일을 적용한 간단한 샘플 페이지 완성
- 앱 전역에 사용할 textTheme 커스터마이징
- Styles 클래스에 최소 5가지 스타일 정의 및 적용
- 다크 모드에서 텍스트 색상이 잘 보이도록 별도 스타일 설정
참고
- Drawer 테마 적용 - DrawerThemeData 로 배경색·아이템 패딩 설정
- 스낵바 디자인 - ScaffoldMessenger.of(context).showSnackBar(...) 활용해 Toast 형태의 안내 메시지 띄우기
- 탭바 스타일 - TabBar 와 TabBarView 로 탭 전환 UI 만들기; TabBarTheme 적용
- 가로/세로 방향 전환 대응 - OrientationBuilder 로 레이아웃 분기 처리
https://docs.flutter.dev/cookbook/design/themes
https://docs.flutter.dev/cookbook/design/drawer
https://docs.flutter.dev/cookbook/design/package-fonts
https://docs.flutter.dev/cookbook/design/themes
https://docs.flutter.dev/cookbook/design/fonts
https://docs.flutter.dev/cookbook/design/snackbars
https://docs.flutter.dev/cookbook/design/tabs
https://docs.flutter.dev/cookbook/design/orientation
https://docs.flutter.dev/cookbook/design/fonts
https://docs.flutter.dev/cookbook/design/themes
https://docs.flutter.dev/cookbook/design/themes