티스토리 뷰
저는 기본 시스템컬러가 아니라 디자인 가이드에 따른 컬러를 사용할 예정이기 때문에
다크모드를 커스텀해서 사용하려고합니다.
먼저 색상을 정의하겠습니다.
Colors.dart라는 파일을 하나 생성해서 LightColors 클래스와 DarkColors 클래스를 만들어주었습니다.
상수값이고 전역적으로 접근해야할 클래스는 멤버를 static const로 작성해줍니다.
ㄴ static으로 선언했기 때문에 인스턴스화없이 접근할 수 있고 const로 정적바인딩 되므로 런타임에서 값을 변경할 수 없어 안전합니다.
이 작업은 디자인 가이드에 붙은 컬러 이름을 사용하기 위해 작성한 것이고 건너뛰어도 무방합니다.
추가적으로 색상값을 지정할땐 알파값까지 지정하는 것을 추천드립니다.
// Colors.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class LightColors {
// color define
static const Color basic = Color(0xFFFFFFFF);
static const Color orange1 = Color(0xFFEB8C1C);
static const Color orange2 = Color(0xFFFFA842);
static const Color blue = Color(0xFF3485FF);
static const Color gray1 = Color(0xFFFFFFFF);
static const Color gray2 = Color(0xFFF5F5F5);
static const Color gray3 = Color(0xFFEEEEEF);
static const Color gray4 = Color(0xFFDFE1E4);
static const Color gray5 = Color(0xFF767676);
static const Color gray6 = Color(0xFF4D4D4D);
static const Color important = Color(0xFF2D2D2D);
}
class DarkColors {
// color define
static const Color basic = Color(0xFF2D2D2D);
static const Color orange1 = Color(0xFFEF8E1D);
static const Color orange2 = Color(0xFFFFB55E);
static const Color blue = Color(0xFF5196FF);
static const Color gray1 = Color(0xFF1A1A1A);
static const Color gray2 = Color(0xFF2B2B2B);
static const Color gray3 = Color(0XFF3C3C3C);
static const Color gray4 = Color(0xFF4D4D4D);
static const Color gray5 = Color(0xFFAFAFAF);
static const Color gray6 = Color(0xFFDCDCDC);
static const Color important = Color(0xFFFFFFFF);
}
// 테마에 따라 바뀌지 않는 컬러
class CommonColors {
// color define
static const Color red = Color(0xFFEC7272);
static const Color yellow = Color(0xFFFAD113);
static const Color green = Color(0xFF8ED9AB);
static const Color onWhite = Color(0xFFFFFFFF);
static const Color onBlack = Color(0xFF2D2D2D);
}
다시 main.dart에 가서 ThemeData를 설정해주겠습니다.
ThemeData 공식문서 를 보면 다양한 속성을 통해 색상을 커스텀할 수 있습니다.
예를 들어 appBar속성을 사용하면 자동으로 모드마다 색상이 바뀌어 나옵니다.
하지만 커스텀 컴포넌트같은 경우는 일일이 색상을 지정해주어야겠죠,
그래서 저는 colorScheme라는 속성을 통해 모드별 색을 지정하고 이를 이용해 색을 설정해보려고 합니다.
// main.dart
ThemeData _lightTheme = ThemeData(
appBarTheme: const AppBarTheme(
backgroundColor: LightColors.gray1,
),
colorScheme: const ColorScheme(
onPrimary: CommonColors.onWhite, //required
onSecondary:CommonColors.onWhite, //required
primary: LightColors.orange1, // point color1
primaryContainer: LightColors.orange2, // point color2
secondary: LightColors.blue, // point color3
background: LightColors.gray1, // app backgound
surface: LightColors.gray2, // card background
outline: LightColors.gray3, // card line or divider
surfaceVariant: LightColors.gray4, // disabled
onSurface: LightColors.gray5, // text3
onSurfaceVariant: LightColors.gray6, //text2
onBackground: LightColors.important, //text1
error: CommonColors.red, // danger
tertiary: CommonColors.yellow, // normal
tertiaryContainer: CommonColors.green, // safe
onError: LightColors.basic, //no use
brightness: Brightness.light,
),
);
ThemeData _darkTheme = ThemeData(
appBarTheme: const AppBarTheme(
backgroundColor: DarkColors.gray1,
),
colorScheme: const ColorScheme(
onPrimary: CommonColors.onWhite, //required
onSecondary:CommonColors.onWhite, //required
primary: DarkColors.orange1, // point color1
primaryContainer: DarkColors.orange2, // point color2
secondary: DarkColors.blue, // point color3
background: DarkColors.gray1, // app backgound
surface: DarkColors.gray2, // card background
outline: DarkColors.gray3, // card line or divider
surfaceVariant: DarkColors.gray4, // disabled
onSurface: DarkColors.important, //text3
onSurfaceVariant: DarkColors.gray6, // text2
onBackground: DarkColors.important, //text1
error: CommonColors.red, // danger
tertiary: CommonColors.yellow, // normal
tertiaryContainer: CommonColors.green, // safe
onError: DarkColors.basic, // no use
brightness: Brightness.light,
),
);
ColorSchme 공식문서를 보고 사용하고자하는 의미가 비슷한 속성들에 색상을 부여했습니다.
예를 들면, surface는 card 컴포넌트의 배경을 의미한다고합니다.
이때 on이 붙은 속성들이 있는데
예를 들어 primary, onPrimary가 있다면 primary 색상 위에 올라갔을 때 잘 보이는 색이 onPrimary 색상이 됩니다.
아이콘이나 텍스트는 배경색과 대비비가 4.5:1이 넘어야 잘 인식된다고 보는데
이를 잘 감안하여 onPrimary 색을 지정하면 좋을 것 같습니다.
추가적으로 ColorScheme는 다음 속성들을 필수로 채워야합니다. (작성하지 않으면 오류가 뜹니다)
[primary, onPrimary, secondary, onSecondary, error, onError, background, onBackground, surface, onSurface]
이제 만든 테마를 적용시켜줍니다.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GetMaterialApp(
home: MyHome(),
theme: _lightTheme,
darkTheme: _darkTheme,
themeMode: ThemeMode.system,
);
}
}
GetMaterialApp에 theme와 darkTheme를 추가하고 방금 만든 ThemeData(_lightTheme, _darkTheme)를 넣어주세요.
위젯에서 컬러를 쓰고싶을 때는 이렇게 쓰면 됩니다.
context.theme.colorScheme.background
이제 모드를 변경해주는 toggle 역할의 버튼을 하나 만들겠습니다.
// return 문 전에 정의 (클래스 멤버변수로)
bool isLightTheme = true;
// return 문
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(context.theme.colorScheme.secondary),
),
onPressed: (){
if (isLightTheme) {
Get.changeThemeMode(ThemeMode.dark);
isLightTheme = false;
}
else {
Get.changeThemeMode(ThemeMode.light);
isLightTheme = true;
}
}
참고로 커스텀한 테마를 사용하려면 이제 Get.changeTheme(ThemeData.light()); 는 사용하면 안됩니다.
몇가지 샘플 위젯을 넣은 결과물입니다.
극적인 효과를 위해 버튼을 파란색에서 주황색으로 반전시켜보았습니다.
+후기
colorScheme에 정의되어있는 속성만 써야하는데 surface처럼 자주사용하지 않는 용어로 키워드가 지정되어 있다보니
코딩할 때 많이 헷갈리네요..
커스텀할 수 있는 색상도 제한적이고, 사용할 컬러가 정말 많아질 경우엔 다른 방안을 찾아야할 것 같습니다.
gray1, gray2같은 용어를 사용하고 싶은데... 아직 방법은 못찾았습니다 😅
MaterialColor 클래스를 이용해서 할 수 있을까요? 더 공부해보겠습니다.
다음번엔 앱을 종료하고 재실행해도 테마 모드를 유지시키는 방법을 알아보겠습니다.
+ 추가적으로
단순히 앱 테마컬러만 변경하고 싶으신 분들은 primarySwatch를 이용하면 좋을 것 같습니다.
또, 머티리얼 컴포넌트들을 적극 활용하고 싶으신 분은 ThemeData 공식문서에서
buttonTheme, cardTheme등의 속성을 찾아 쓰시면 좋을 것 같습니다.
전체코드
//// Colors.dart
class LightColors {
// color define
static const Color basic = Color(0xFFFFFFFF);
static const Color orange1 = Color(0xFFEB8C1C);
static const Color orange2 = Color(0xFFFFA842);
static const Color blue = Color(0xFF3485FF);
static const Color gray1 = Color(0xFFFFFFFF);
static const Color gray2 = Color(0xFFFFFFFF);
static const Color gray3 = Color(0xFFEEEEEF);
static const Color gray4 = Color(0xFFDFE1E4);
static const Color gray5 = Color(0xFF767676);
static const Color gray6 = Color(0xFF4D4D4D);
static const Color important = Color(0xFF000000);
}
class DarkColors {
// color define
static const Color basic = Color(0xFF000000);
static const Color orange1 = Color(0xFFEF8E1D);
static const Color orange2 = Color(0xFFFFB55E);
static const Color blue = Color(0xFF5196FF);
static const Color gray1 = Color(0xFF1A1A1A);
static const Color gray2 = Color(0xFF2B2B2B);
static const Color gray3 = Color(0XFF3C3C3C);
static const Color gray4 = Color(0xFF4D4D4D);
static const Color gray5 = Color(0xFFAFAFAF);
static const Color gray6 = Color(0xFFDCDCDC);
static const Color important = Color(0xFFFFFFFF);
}
class CommonColors {
// color define
static const Color red = Color(0xFFEC7272);
static const Color yellow = Color(0xFFFAD113);
static const Color green = Color(0xFF8ED9AB);
}
//// main.dart
void main() {
runApp(const MyApp());
}
ThemeData _lightTheme = ThemeData(
appBarTheme: const AppBarTheme(
backgroundColor: LightColors.gray1,
),
colorScheme: const ColorScheme(
onSurface: LightColors.basic,
primary: LightColors.orange1, // point color1
primaryContainer: LightColors.orange2, // point color2
secondary: LightColors.blue, // point color3
background: LightColors.gray1, // app backgound
surface: LightColors.gray2, // card background
outline: LightColors.gray3, // card line or divider
surfaceVariant: LightColors.gray4, // disabled
onPrimary: LightColors.gray5, // text3
onSecondary:LightColors.gray6, //text2
onBackground: LightColors.important, //text1
error: CommonColors.red, // danger
tertiary: CommonColors.yellow, // normal
tertiaryContainer: CommonColors.green, // safe
onError: LightColors.basic,
brightness: Brightness.light,
),
);
ThemeData _darkTheme = ThemeData(
appBarTheme: const AppBarTheme(
backgroundColor: DarkColors.gray1,
),
colorScheme: const ColorScheme(
onSurface: DarkColors.basic,
primary: DarkColors.orange1, // point color1
primaryContainer: DarkColors.orange2, // point color2
secondary: DarkColors.blue, // point color3
background: DarkColors.gray1, // app backgound
surface: DarkColors.gray2, // card background
outline: DarkColors.gray3, // card line or divider
surfaceVariant: DarkColors.gray4, // disabled
onPrimary: DarkColors.gray5, //text3
onSecondary:DarkColors.gray6, // text2
onBackground: DarkColors.important, //text1
error: CommonColors.red, // danger
tertiary: CommonColors.yellow, // normal
tertiaryContainer: CommonColors.green, // safe
onError: DarkColors.basic,
brightness: Brightness.light,
),
);
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GetMaterialApp(
home: MyHome(),
theme: _lightTheme,
darkTheme: _darkTheme,
themeMode: ThemeMode.system,
);
}
}
//// MyHome.dart
class MyHome extends StatelessWidget {
// const MyHome({Key? key}) : super(key: key);
bool isLightTheme = true;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: context.theme.colorScheme.background,
appBar: AppBar(),
body: Column(children: [
SampleCard(),
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(context.theme.colorScheme.secondary),
),
onPressed: (){
if (isLightTheme) {
Get.changeThemeMode(ThemeMode.dark);
isLightTheme = false;
}
else {
Get.changeThemeMode(ThemeMode.light);
isLightTheme = true;
}
},
child: Text(
"light/dark",
style: TextStyle(color: Colors.white)
)
),
]),
bottomNavigationBar: BottomAppBar(),
);
}
}
//// SampleCard.dart
class SampleCard extends StatelessWidget {
const SampleCard({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// 데이터
var dataValue = "data";
var dataDetailValue = "detail data";
// 박스 내의 텍스트 위젯
var title = Text(
"Sample",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: context.theme.colorScheme.onPrimary,
),
);
var body = Text(
"${dataValue}",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: context.theme.colorScheme.onBackground,
),
);
var bodyDetail = Text(
" / ${dataDetailValue}",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: context.theme.colorScheme.onSecondary,
),
);
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
border: Border.all(color: context.theme.colorScheme.outline, width: 1),
color: context.theme.colorScheme.surface,
),
width: double.infinity,
padding: EdgeInsets.all(25),
margin: EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
title,
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
body,
bodyDetail,
],
)
],
)
);
}
}
Ref.
https://dev.to/dhiwise/dark-theme-using-getx-5hn7
'Flutter' 카테고리의 다른 글
[Flutter] BottomNavigationBar 커스텀하기 (0) | 2022.08.16 |
---|---|
[Flutter] 하단탭바, BottomNavigationBar 만들기 (0) | 2022.08.10 |
[Flutter][GetX] Conditions must have a static type of 'bool'.Try changing the condition. (0) | 2022.08.07 |
[Flutter] 전체 폰트 변경하기 (0) | 2022.08.03 |
[Flutter][GetX] GetX로 다크모드 만들기 (1) (0) | 2022.07.28 |
- Total
- Today
- Yesterday
- SwiftUI
- MVC
- coordinator pattern
- 리액티브 프로그래밍
- notion
- Bloking/Non-bloking
- SWM
- programmers
- 프로그래머스
- 아키텍쳐 패턴
- MVVM
- DocC
- 소프트웨어마에스트로
- Swift Concurrency
- 노션
- GetX
- 비동기/동기
- combine
- RX
- 코디네이터 패턴
- reactive programming
- MVI
- ios
- Flutter
- healthkit
- Architecture Pattern
- swift
- Flux
- TestCode
- design pattern
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |