Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
Tags
- riverpod
- java
- ListView
- npm
- JS
- firebase
- 자바 출력 방식
- 단축키
- DART
- printf
- Flutter
- java 출력
- develop
- UI/UX
- scss
- 자바스크립트
- react
- lifecycle
- Clean Architecture
- println
- LLM
- 앱심사
- JQ
- abap
- nodejs
- unity
- 엡
- 배포
- java 콘솔 출력 차이
- 자바 포맷 출력
Archives
- Today
- Total
guricode
Flutter ThemeExtension 본문
Flutter에서 테마를 꾸밀 때, 기본적으로 제공하는 ThemeData만으로는 내가 원하는 모든 색상이나 스타일을 정의하기 어려운 경우가 많다. 그래서 Flutter는 ThemeExtension이라는 기능을 제공해서 직접 테마 속성을 확장할 수 있도록 해준다.
1. ThemeExtension 이란?
왜 필요한가?
ThemeData의 기본 속성만으로는 앱 전체에 걸쳐 쓰는 색상(main, sub 등)을 정의하기에 부족하다. 그래서 ThemeData.extensions 라는 확장 필드를 통해 직접 색상이나 스타일을 테마에 추가할 수 있다.
ThemeData(
extensions: <ThemeExtension<dynamic>>[
AppThemeExtension(
main: Colors.red,
mainLight: ..., // 사용자 정의 색상
...
)
]
);
어떻게 구현하는가?
ThemeExtension<T>를 상속한 뒤, 제네릭 인자로 자기 자신을 넘긴다:
class AppThemeExtension extends ThemeExtension<AppThemeExtension> {
...
}
이렇게 하면 아래 2가지 메서드를 반드시 재정의해야 한다:
copyWith()– 일부 값을 변경한 복사본 생성lerp()– 애니메이션 중간값 계산 (색상 부드럽게 전환)
2. 실습 코드 정리
1) custom_theme.dart
class AppThemeExtension extends ThemeExtension<AppThemeExtension> {
final Color main;
final Color mainLight;
final Color sub;
final Color background;
AppThemeExtension({
required this.main,
required this.mainLight,
required this.sub,
required this.background,
});
@override
AppThemeExtension copyWith({
Color? main,
Color? mainLight,
Color? sub,
Color? background,
}) {
return AppThemeExtension(
main: main ?? this.main,
mainLight: mainLight ?? this.mainLight,
sub: sub ?? this.sub,
background: background ?? this.background,
);
}
@override
AppThemeExtension lerp(AppThemeExtension? other, double t) {
if (other == null) return this;
return AppThemeExtension(
main: Color.lerp(main, other.main, t)!,
mainLight: Color.lerp(mainLight, other.mainLight, t)!,
sub: Color.lerp(sub, other.sub, t)!,
background: Color.lerp(background, other.background, t)!,
);
}
}
class LightTheme extends AppThemeExtension {
LightTheme()
: super(
main: Colors.red,
mainLight: Color(0xAAFF0000),
sub: Color(0xFFFF0000),
background: Colors.white,
);
}
class DarkTheme extends AppThemeExtension {
DarkTheme()
: super(
main: Color(0xFF0000FF),
mainLight: Color(0xAAAA0000),
sub: Color(0xFFFF00FF),
background: Colors.black,
);
}
2) theme.dart
final lightTheme = _theme(Brightness.light, LightTheme());
final darkTheme = _theme(Brightness.dark, DarkTheme());
ThemeData _theme(Brightness brightness, AppThemeExtension ext) {
return ThemeData(
brightness: brightness,
useMaterial3: true,
scaffoldBackgroundColor: ext.background,
colorScheme: ColorScheme.fromSeed(
brightness: brightness,
seedColor: ext.main,
),
extensions: [ext],
);
}
// BuildContext 확장
extension BuildContextExtention on BuildContext {
ThemeData get theme => Theme.of(this);
AppThemeExtension get appColor => theme.extension<AppThemeExtension>()!;
}
여기서 BuildContextExtention 에 대해 간략하게 알아보자
BuildContextExtension 이란?
BuildContextExtension은 Dart의 extension 기능을 활용해 BuildContext 클래스에 새로운 기능을 붙인 확장 클래스다.
기존 Flutter 프레임워크의 BuildContext를 그대로 두고, 별도의 기능을 덧붙일 수 있도록 해준다.
- context.theme → Theme.of(context)와 같다.
- context.appColor → Theme.of(context).extension<AppThemeExtension>()!와 같다.
사용하면 좋은 이유
- 매번 Theme.of(context) 혹은 Theme.of(context).extension<AppThemeExtension>()!를 호출하는 것이 코드가 길고 불편하기 때문이다.
- View 단에서 테마에 접근하는 코드를 간결하게 만들고, 유지보수성을 높이기 위해 사용하는 것이다.
- BuildContext는 Flutter 전역에서 거의 항상 접근 가능한 객체이기 때문에, 그에 기능을 덧붙이면 매우 강력한 효과를 준다.
각각의 기능 설명
1. ThemeData get theme => Theme.of(this);
- 현재 context에 연결된 테마 데이터를 반환하는 getter다.
- this는 BuildContext 자체를 가리키며, 결국 Theme.of(context)와 동일하다.
- 이 확장 메서드로 인해 context.theme.primaryColor 같은 표현이 가능해진다.
2. AppThemeExtension get appColor => theme.extension<AppThemeExtension>()!;
- ThemeData 내부에 정의된 ThemeExtension 객체를 꺼내서 반환한다.
- <AppThemeExtension>은 제네릭으로 내가 만든 테마 확장 클래스다.
- !는 null safety에서 null이 아님을 보장해주는 연산자다. 반드시 extensions에 이 타입이 포함돼 있어야 한다.
실사용 흐름
- ThemeData에 AppThemeExtension을 등록해 둔다.
- View에서는 context.appColor.main, context.appColor.sub 등으로 간편하게 접근한다.
- 테마가 바뀔 경우에도 lerp 메서드를 통해 자연스럽게 애니메이션으로 전환된다.
- 이 모든 과정이 BuildContextExtension을 통해 훨씬 가독성 좋게 표현된다.
3) main.dart
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
themeMode: ThemeMode.dark, // 다크모드 우선 적용
theme: lightTheme,
darkTheme: darkTheme,
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: ListView(
children: [
Container(height: 100, color: context.appColor.main),
Container(height: 100, color: context.appColor.mainLight),
Container(height: 100, color: context.appColor.sub),
],
),
);
}
}
3. 핵심 개념 요약
| 요소 | 설명 |
|---|---|
ThemeExtension<T> |
테마 확장을 위한 추상 클래스 (T는 자기 자신을 넣음) |
copyWith() |
테마 인스턴스를 복사할 때 사용 |
lerp() |
테마 전환 시 애니메이션 보간을 위해 사용 |
extensions: [...] |
ThemeData에 내가 만든 테마 확장 추가 |
context.extension<T>() |
내가 만든 확장에 접근하는 방법 |
extension BuildContextExtention |
context에서 theme, appColor 쉽게 접근 |
3줄요약
ThemeExtension을 사용하면 내가 원하는 색상이나 속성을 테마에 자유롭게 추가할 수 있음- 테마를 커스터마이징할 수 있어 디자인의 일관성과 유지보수성 모두 향상됨
lerp,copyWith를 활용하면 다크/라이트 테마 전환도 부드럽게 처리 가능
'앱 > Flutter&Dart' 카테고리의 다른 글
| MVVM 패턴 연습 (3) | 2025.08.25 |
|---|---|
| [TIL] 20250819 - GoRouter,Responsive UI (0) | 2025.08.19 |
| Flutter Firebase 시작 정리 - 설치부터 초기화까지 (3) | 2025.08.14 |
| 트러블슈팅: “프로필 이미지 선택만 했는데 바로 서버에 업로드됨” (2) | 2025.08.12 |
| Flutter iOS 시뮬레이터에서 위치 테스트하는 방법 (3) | 2025.08.11 |