티스토리 뷰

저는 기본 시스템컬러가 아니라 디자인 가이드에 따른 컬러를 사용할 예정이기 때문에

다크모드를 커스텀해서 사용하려고합니다.

 

이런식으로 컬러는 역할이 있고, 라이트모드와 다크모드가 대응되게 설계되어 있습니다.

 


 

 

먼저 색상을 정의하겠습니다.

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

 

Dark theme using GetX

Adding a Light and Dark theme for your app kind of becomes a mandatory thing in today’s app world....

dev.to

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
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
글 보관함