이제는 떠나보내야 할 플러터 primaryColor

오랫동안 플러터(Flutter) 개발자들에게 앱의 정체성을 부여하는 가장 첫 번째 관문은 ThemeDataprimaryColor를 설정하는 것이었습니다. 단 한 줄의 코드로 앱의 전반적인 분위기를 결정짓는 이 속성은 우리에게 매우 친숙하고 직관적이었습니다. 하지만 Flutter 프레임워크가 성숙하고, 구글의 머티리얼 디자인(Material Design) 가이드가 진화함에 따라, primaryColor는 서서히 역사의 뒤안길로 사라질 준비를 하고 있습니다. 이제는 'deprecated'라는 꼬리표가 붙어버렸죠.

이 글은 단순히 primaryColor가 왜 더 이상 권장되지 않는지를 설명하는 것을 넘어, 새로운 표준으로 자리 잡은 ColorScheme이 무엇이며, 왜 우리가 이 변화의 흐름에 적극적으로 올라타야 하는지를 풀스택 개발자의 관점에서 깊이 있게 파헤쳐 봅니다. 과거의 방식에 대한 이해부터 시작해, ColorScheme의 철학, 두 방식의 근본적인 차이, 그리고 실제 프로젝트에 적용할 수 있는 구체적인 마이그레이션 전략까지, 여러분의 플러터 테마 시스템을 한 단계 업그레이드할 모든 것을 담았습니다.

이 글의 목표: 이 글을 끝까지 읽으신다면, 여러분은 더 이상 ThemeData를 마주했을 때 primaryColoraccentColor를 기계적으로 찾지 않게 될 것입니다. 대신, 앱의 모든 색상이 조화롭게 상호작용하는 견고하고 유연한 색상 시스템, 즉 ColorScheme을 자신 있게 설계하고 구현할 수 있게 될 것입니다.

과거의 영광: primaryColor와 primarySwatch의 시대

우리가 primaryColor를 이야기할 때, 필연적으로 함께 등장하는 개념이 바로 primarySwatch입니다. 이 둘의 관계를 이해하는 것이 구시대 테마 방식의 핵심입니다. 과거의 코드를 복기해보며 그 시절의 테마 설정을 되짚어 보겠습니다.

초기 플러터 프로젝트의 main.dart 파일을 열면 흔히 볼 수 있었던 테마 설정 코드입니다.


// main.dart - 구시대의 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: 'Legacy Theme Demo',
      // ThemeData를 직접 설정하여 앱의 전반적인 테마를 정의합니다.
      theme: ThemeData(
        // primarySwatch: 앱의 기본 색상 '견본'을 정의합니다.
        // MaterialColor 객체를 받으며, 보통 Colors.blue, Colors.red처럼 미리 정의된 색상을 사용합니다.
        // 이것 하나만 설정해도 primaryColor, primaryColorLight, primaryColorDark 등이 자동으로 설정됩니다.
        primarySwatch: Colors.indigo,

        // primaryColor: 앱의 주요 색상을 '명시적으로' 지정할 수도 있습니다.
        // 만약 primarySwatch와 함께 사용되면, primaryColor가 우선권을 가질 수 있습니다.
        // 하지만 일반적으로는 primarySwatch를 통해 암묵적으로 설정되도록 두는 경우가 많았습니다.
        // primaryColor: Colors.indigo[500], // primarySwatch가 이 역할을 대신합니다.

        // accentColor: 보조 색상으로, 주로 FAB(FloatingActionButton)나 활성화된 스위치 등에 사용되었습니다.
        // 이 속성 역시 현재는 deprecated 되었습니다.
        accentColor: Colors.pinkAccent,

        // 기타 색상들을 개별적으로 설정해야 했습니다.
        scaffoldBackgroundColor: Colors.grey[100],
        buttonColor: Colors.indigo, // 이제는 사용되지 않는 속성
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        // AppBar의 배경색은 기본적으로 ThemeData.primaryColor를 따라갑니다.
        title: const Text('PrimaryColor 시대'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('이 텍스트는 기본 테마 색상을 따릅니다.'),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {},
              // 버튼의 배경색 또한 ThemeData의 영향을 받습니다.
              child: const Text('주요 버튼'),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        // FAB의 배경색은 기본적으로 ThemeData.accentColor를 따라갑니다.
        onPressed: () {},
        child: const Icon(Icons.add),
      ),
    );
  }
}

위 코드에서 핵심은 primarySwatch입니다. primaryColor는 단순히 하나의 Color 값이지만, primarySwatchMaterialColor 타입의 객체입니다. MaterialColor는 하나의 기본 색상과 그 색상의 밝고 어두운 10가지 버전(shade)을 포함하는 하나의 '색상 견본'입니다. 예를 들어 Colors.blueColors.blue[50]부터 Colors.blue[900]까지의 음영을 모두 가지고 있습니다.

primarySwatch를 설정하면 플러터는 다음과 같은 일들을 내부적으로 처리했습니다.

  • primaryColor: primarySwatch[500] (가장 기본적인 중간 값)으로 설정됩니다.
  • primaryColorLight: 더 밝은 음영(예: primarySwatch[100])으로 설정됩니다.
  • primaryColorDark: 더 어두운 음영(예: primarySwatch[700])으로 설정됩니다.
  • AppBar, TabBar 등 주요 위젯들의 기본 색상이 primaryColor를 기반으로 자동으로 결정됩니다.

이 방식은 매우 편리했습니다. 단 하나의 primarySwatch 설정만으로 앱의 전반적인 톤앤매너를 빠르게 잡을 수 있었기 때문입니다. 하지만 프로젝트의 규모가 커지고 디자인 시스템이 정교해지면서 여러 한계점이 드러나기 시작했습니다.

primaryColor 방식의 한계점

  1. 제한적인 유연성: primarySwatchMaterialColor 객체만 받을 수 있습니다. 만약 디자이너가 #FF5733와 같이 Material 기본 색상표에 없는 특정 색상을 기본 색으로 요구한다면, 이 색상에 대한 10가지 음영을 직접 만들어 MaterialColor 객체를 생성해야 하는 번거로움이 있었습니다.
  2. 파편화된 색상 관리: primaryColor는 앱의 '주요' 색상만 정의할 뿐, 그 색상 위에 올라갈 텍스트나 아이콘의 색상(소위 'onPrimary' 색상)에 대한 표준이 없었습니다. 개발자는 primaryColor가 밝은 색이면 어두운 텍스트를, 어두운 색이면 밝은 텍스트를 사용하도록 직접 조건문을 추가해야 했습니다. 보조 색상(accentColor), 배경색(scaffoldBackgroundColor), 카드색(cardColor) 등도 모두 개별적으로 관리해야 해서 일관성을 유지하기 어려웠습니다.
  3. 머티리얼 디자인과의 괴리: 구글의 머티리얼 디자인 시스템이 발전하면서, 색상은 단순히 '주요색', '보조색'으로 나뉘는 것을 넘어 '표면(Surface)', '배경(Background)', '오류(Error)' 등 의미론적 역할에 따라 정의되기 시작했습니다. primaryColor 기반의 시스템은 이러한 정교한 디자인 시스템을 제대로 담아내지 못했습니다.

이러한 문제점들을 해결하기 위해, 플러터 팀은 더 체계적이고 확장 가능한 새로운 색상 시스템을 도입하기로 결정합니다. 그것이 바로 ColorScheme입니다.

새로운 표준의 등장: ColorScheme의 철학

ColorScheme은 단일 색상이나 색상 견본이 아닌, 앱을 구성하는 데 필요한 의미론적 색상들의 집합(a set of semantic colors)입니다. "이 버튼은 파란색이야"가 아니라, "이 버튼은 우리 앱의 '주요 액션(primary)'을 나타내는 색이야"라고 정의하는 방식입니다. 이 철학의 변화는 매우 중요합니다. 색상에 역할을 부여함으로써 디자인의 일관성을 강제하고, 다크 모드 전환이나 브랜드 색상 변경 시 훨씬 유연하게 대처할 수 있게 만듭니다.

ColorScheme 객체는 다음과 같은 주요 속성들을 가집니다. 각 속성은 쌍으로 존재하며, 하나는 배경색 역할을, 다른 하나('on' 접두사)는 그 배경 위에 올라갈 콘텐츠(텍스트, 아이콘)의 색상 역할을 합니다.

속성 (Property) 역할 및 설명 사용 예시
primary 앱의 가장 주요한 브랜드 색상입니다. 화면의 핵심적인 부분에 사용됩니다. (과거 primaryColor의 역할) 앱 바, 주요 버튼(ElevatedButton), 활성화된 탭
onPrimary primary 색상 위에 표시되는 텍스트나 아이콘의 색상입니다. 가독성을 보장합니다. 앱 바의 제목 텍스트, 주요 버튼의 텍스트
primaryContainer primary보다 덜 강조되지만 여전히 주요 콘텐츠를 담는 컨테이너의 색상입니다. (Material 3에서 중요) 주요 정보 카드, 선택된 항목의 배경
onPrimaryContainer primaryContainer 색상 위에 표시되는 콘텐츠의 색상입니다. 주요 정보 카드의 텍스트
secondary 앱의 보조적인 액션을 나타내는 색상입니다. (과거 accentColor의 역할) FAB(FloatingActionButton), 필터 칩, 슬라이더
onSecondary secondary 색상 위에 표시되는 콘텐츠의 색상입니다. FAB의 아이콘
secondaryContainer secondary보다 덜 강조되는 보조 컨테이너의 색상입니다. 보조 정보 영역, 태그
onSecondaryContainer secondaryContainer 색상 위에 표시되는 콘텐츠의 색상입니다. 보조 정보 영역의 텍스트
tertiary 대조적인 강조나 보조적인 요소를 표현하는 제3의 색상입니다. (Material 3) 하이라이트, 보상 표시
onTertiary tertiary 색상 위에 표시되는 콘텐츠의 색상입니다. 하이라이트 영역의 아이콘
error 오류 상태를 나타내는 데 사용되는 색상입니다. 잘못된 입력 필드(TextField)의 테두리, 오류 메시지
onError error 색상 위에 표시되는 콘텐츠의 색상입니다. 오류 스낵바의 텍스트
background 앱 콘텐츠의 가장 뒤에 위치하는 배경 스크롤 가능한 콘텐츠의 배경색입니다. 앱의 전체적인 배경
onBackground background 색상 위에 표시되는 콘텐츠의 색상입니다. 일반적인 본문 텍스트
surface 카드, 시트, 메뉴 등 컴포넌트의 '표면' 색상입니다. background보다 한 단계 위에 있습니다. 카드(Card), 다이얼로그(Dialog), 바텀 시트(BottomSheet)
onSurface surface 색상 위에 표시되는 콘텐츠의 색상입니다. 카드 내부의 텍스트, 다이얼로그의 제목
surfaceVariant surface와 미묘한 대비를 주는 변형된 표면 색상입니다. 구분선(Divider), 비활성화된 컴포넌트
onSurfaceVariant surfaceVariant 위에 표시되는 텍스트 색상입니다. 주로 보조적인 텍스트에 사용됩니다. 입력 필드의 레이블 텍스트, 보조 설명
outline 컴포넌트의 경계선을 그리는 데 사용되는 색상입니다. OutlinedButton의 테두리, TextField의 테두리
shadow 그림자 효과에 사용되는 색상입니다. 컴포넌트의 Elevation 그림자

표만 봐도 알 수 있듯이, ColorSchemeprimaryColoraccentColor만으로는 표현할 수 없었던 훨씬 더 정교하고 완전한 색상 체계를 제공합니다. 이제 ThemeDataColorScheme으로 정의하는 현대적인 방법을 살펴보겠습니다.


// main.dart - ColorScheme을 사용한 현대적인 ThemeData 설정 방식
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    // ColorScheme 객체를 먼저 정의합니다.
    final ColorScheme colorScheme = ColorScheme.fromSeed(
      seedColor: Colors.indigo,
      // (선택사항) 다크 모드에서의 밝기를 조절할 수 있습니다.
      // brightness: Brightness.dark, 
    );

    return MaterialApp(
      title: 'ColorScheme Theme Demo',
      // ThemeData.from 생성자를 사용하여 ColorScheme으로부터 전체 테마를 생성합니다.
      theme: ThemeData.from(
        colorScheme: colorScheme,
        useMaterial3: true, // Material 3 디자인을 활성화합니다.
      ).copyWith(
        // ThemeData.from으로 생성된 테마에서 추가적으로 커스터마이징이 필요한 부분은
        // copyWith를 사용하여 수정합니다.
        appBarTheme: AppBarTheme(
          backgroundColor: colorScheme.primary,
          foregroundColor: colorScheme.onPrimary, // AppBar의 아이콘 및 텍스트 색상
          elevation: 4.0,
        ),
        // 다른 위젯 테마들도 여기서 커스터마이징 가능
        // floatingActionButtonTheme: FloatingActionButtonThemeData(...)
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});
  @override
  Widget build(BuildContext context) {
    // 위젯 내에서 테마 색상을 사용할 때는 ColorScheme을 통해 접근합니다.
    final colorScheme = Theme.of(context).colorScheme;

    return Scaffold(
      appBar: AppBar(
        // AppBar의 색상은 이제 ThemeData.appBarTheme에 의해 중앙에서 관리됩니다.
        title: const Text('ColorScheme 시대'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // Text 위젯은 기본적으로 onSurface 또는 onBackground 색상을 사용합니다.
            Text(
              '이 텍스트는 onSurface 색상을 따릅니다.',
              style: TextStyle(color: colorScheme.onSurface),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {},
              // ElevatedButton은 내부적으로 primary와 onPrimary 색상을 사용하도록 설계되어 있습니다.
              child: const Text('주요 버튼'),
            ),
            const SizedBox(height: 10),
            OutlinedButton(
              onPressed: () {},
              // OutlinedButton의 테두리는 outline, 텍스트는 primary 색상을 사용합니다.
              child: const Text('보조 버튼'),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        // FAB는 기본적으로 secondary와 onSecondary 색상을 사용합니다.
        onPressed: () {},
        child: const Icon(Icons.add),
      ),
    );
  }
}

가장 주목할 점은 ColorScheme.fromSeed입니다. 단 하나의 `seedColor`만 제공하면, 머티리얼 디자인의 색상 알고리즘에 따라 조화로운 `primary`, `secondary`, `tertiary` 및 기타 모든 `ColorScheme` 색상들이 자동으로 생성됩니다. 이는 과거 `primarySwatch`의 편리함을 계승하면서도, 훨씬 더 풍부하고 완성도 높은 색상 팔레트를 제공하는 강력한 기능입니다. 더 이상 디자이너가 제공한 특정 브랜드 색상 하나를 적용하기 위해 수많은 색상 코드를 직접 만들 필요가 없습니다.

primaryColor vs. ColorScheme: 직접 비교 분석

이제 두 방식의 차이점을 명확하게 이해하기 위해 여러 관점에서 직접적으로 비교해 보겠습니다. 이것이야말로 플러터 colorScheme과 primaryColor 차이의 핵심입니다.

비교 항목 primaryColor / primarySwatch 방식 ColorScheme 방식
철학적 모델 색상 중심(Color-centric). '파란색', '빨간색' 등 특정 색상 값을 직접 지정하는 데 초점을 맞춥니다. 위젯들은 이 지정된 색상을 가져다 쓰는 방식입니다. 역할 중심(Role-centric). '주요 액션 색상', '표면 색상' 등 색상의 역할을 정의합니다. 위젯들은 자신의 역할에 맞는 색상을 시스템에서 받아옵니다. 색상 자체가 아닌 색상의 의미가 중요합니다.
일관성 및 유지보수 낮음. primaryColor 외의 색상(배경, 텍스트, 오류 등)은 개별적으로 관리해야 하므로 일관성을 해치기 쉽습니다. 브랜드 색상 변경 시 수십 개의 속성을 수정해야 할 수 있습니다. 매우 높음. ColorScheme 하나만 정의하면 모든 관련 색상(onPrimary, surface, background 등)이 조화롭게 결정됩니다. seedColor만 변경하면 앱 전체의 톤앤매너가 일관되게 변경됩니다.
유연성 및 확장성 낮음. primarySwatch는 MaterialColor만 허용하여 커스텀 색상 적용이 번거롭습니다. 다크 모드 지원을 위해 별도의 ThemeData를 거의 처음부터 다시 정의해야 합니다. 매우 높음. ColorScheme.fromSeed로 어떤 색상이든 시드 색상으로 사용할 수 있습니다. 또한 ColorScheme의 각 속성을 개별적으로 오버라이드하여 세밀한 조정이 가능합니다. 다크 모드는 brightness 속성 변경만으로 쉽게 생성됩니다.
Material Design 준수 Material 1, 2 초기 버전에 부합합니다. 최신 Material 3(Material You)의 정교한 색상 시스템(Container, Variant 등)을 완벽하게 지원하지 못합니다. Material 2 및 Material 3를 완벽하게 지원합니다. primaryContainer, tertiary, surfaceVariant 등 M3의 모든 색상 역할을 포함하고 있어 최신 디자인 트렌드를 따르기에 최적입니다.
코드 가독성 및 의도 Theme.of(context).primaryColor 코드는 '주요 색상을 가져온다'는 의미만 전달합니다. 이 색상이 배경에 쓰일지, 텍스트에 쓰일지는 알 수 없습니다. Theme.of(context).colorScheme.primary, .onPrimary, .surface 등 코드를 읽는 것만으로도 해당 색상이 어떤 역할과 의도로 사용되는지 명확하게 파악할 수 있습니다.
미래 지향성 Deprecated. 더 이상 적극적으로 개발되거나 권장되지 않습니다. 새로운 위젯들은 ColorScheme 기반으로 색상을 결정하므로, 구 방식 사용 시 예상치 못한 UI 깨짐이 발생할 수 있습니다. Flutter 테마의 표준이자 미래입니다. 동적 색상(Dynamic Color) 등 앞으로 나올 모든 테마 관련 기능은 ColorScheme을 기반으로 합니다. 지금 전환하는 것이 장기적으로 이득입니다.

왜 primaryColor가 deprecated 되었는가?

위 비교표를 보면 플러터 primaryColor deprecated 이유가 명확해집니다. 요약하자면, primaryColor는 더 이상 현대적인 UI/UX 디자인 시스템의 복잡성과 요구사항을 감당할 수 없기 때문입니다. ColorSchemeprimaryColor가 하던 모든 것을 포함하면서, 동시에 다음과 같은 압도적인 장점을 제공합니다.

  • 관계의 정의: ColorScheme은 색상 간의 관계(배경과 전경, 주요와 보조)를 시스템 수준에서 정의해줍니다.
  • 중앙 집중 관리: 색상 관련 모든 정의가 ColorScheme 객체 하나로 모여 관리가 용이합니다.
  • 자동화와 지능: fromSeed와 같은 생성자는 색상 이론에 기반하여 조화로운 팔레트를 자동으로 생성해줍니다.

결국 Flutter 프레임워크는 개발자들이 더 견고하고, 일관되며, 유지보수하기 쉬운 앱을 만들도록 유도하기 위해 ColorScheme을 표준으로 채택하고 primaryColor를 과거의 유산으로 남기기로 결정한 것입니다.

실전! primaryColor 기반 프로젝트를 ColorScheme으로 마이그레이션하기

이론은 충분합니다. 이제 기존 프로젝트에 어떻게 ColorScheme을 적용할지 단계별로 알아보겠습니다. 실제 코드를 통해 변화를 체감해 보세요.

1단계: 기존 테마 분석 및 시드 색상 결정

가장 먼저 할 일은 기존 ThemeData를 살펴보는 것입니다.


// AS-IS: 기존 ThemeData
theme: ThemeData(
  brightness: Brightness.light,
  primarySwatch: Colors.teal,
  accentColor: Colors.orangeAccent,
  scaffoldBackgroundColor: const Color(0xFFF5F5F5),
  // ... 기타 등등
),

여기서 핵심 색상은 primarySwatch로 사용된 Colors.teal입니다. 이 색상을 새로운 ColorScheme의 시드 색상(seed color)으로 사용하기로 결정합니다.

2단계: ColorScheme 기반의 새로운 ThemeData 생성

이제 ColorScheme.fromSeed를 사용하여 새로운 테마를 정의합니다. useMaterial3: true를 설정하여 최신 디자인을 적용하는 것을 잊지 마세요.


// TO-BE: 새로운 ThemeData
final lightColorScheme = ColorScheme.fromSeed(
  seedColor: Colors.teal,
  brightness: Brightness.light,
);

theme: ThemeData(
  colorScheme: lightColorScheme,
  useMaterial3: true,
  // (선택사항) ColorScheme으로 자동 생성된 값 외에 추가로 커스터마이징
  // scaffoldBackgroundColor: lightColorScheme.background, // 보통은 자동으로 잘 설정됨
),

단 몇 줄만으로 훨씬 더 정교한 테마가 완성되었습니다. 다크 모드도 매우 간단하게 만들 수 있습니다.


// TO-BE: 다크 모드 ThemeData
final darkColorScheme = ColorScheme.fromSeed(
  seedColor: Colors.teal,
  brightness: Brightness.dark,
);

darkTheme: ThemeData(
  colorScheme: darkColorScheme,
  useMaterial3: true,
),

이제 MaterialAppthemedarkTheme를 모두 제공하고 themeMode를 설정하면, 사용자의 시스템 설정에 따라 라이트/다크 모드가 자동으로 전환됩니다.

3단계: 위젯에서 색상 사용하는 코드 리팩토링

가장 중요한 단계입니다. 프로젝트 전체에서 Theme.of(context).primaryColor와 같이 오래된 방식으로 색상을 사용하던 코드를 모두 찾아서 ColorScheme을 사용하도록 변경해야 합니다.

팁: IDE의 '전체 검색(Find in Files)' 기능을 사용하여 .primaryColor, .accentColor, .scaffoldBackgroundColor 등의 키워드를 검색하면 수정해야 할 부분을 쉽게 찾을 수 있습니다.

예시 1: 커스텀 컨테이너


// AS-IS
Container(
  color: Theme.of(context).primaryColor,
  child: Text(
    'Hello',
    style: TextStyle(color: Colors.white), // 직접 흰색으로 지정
  ),
)

// TO-BE
Container(
  color: Theme.of(context).colorScheme.primary,
  child: Text(
    'Hello',
    // onPrimary 색상을 사용하여 가독성을 시스템에 위임
    style: TextStyle(color: Theme.of(context).colorScheme.onPrimary),
  ),
)

예시 2: 아이콘 색상


// AS-IS
Icon(
  Icons.check_circle,
  color: Theme.of(context).accentColor,
)

// TO-BE
// accentColor는 보통 secondary 역할에 해당합니다.
Icon(
  Icons.check_circle,
  color: Theme.of(context).colorScheme.secondary,
)

예시 3: 텍스트 색상


// AS-IS
Text(
  '중요하지 않은 부가 설명',
  style: TextStyle(color: Colors.grey[600]), // 색상을 하드코딩
)

// TO-BE
// onSurfaceVariant는 보조 텍스트에 적합한 역할입니다.
Text(
  '중요하지 않은 부가 설명',
  style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
)

이러한 변경 작업은 처음에는 번거롭게 느껴질 수 있습니다. 하지만 한번 ColorScheme 기반으로 코드를 정리하고 나면, 추후 디자인 변경이나 기능 추가 시에 엄청난 생산성 향상을 경험하게 될 것입니다. 모든 색상이 예측 가능하고 일관된 방식으로 동작하기 때문입니다.

ColorScheme을 넘어서: Material 3와 동적 색상(Dynamic Color)

ColorScheme으로의 전환은 단순히 코드를 현대화하는 것을 넘어, 플러터의 미래 기술을 맞이할 준비를 하는 것입니다. 그 대표적인 예가 바로 Material 3(Material You)의 핵심 기능인 동적 색상(Dynamic Color)입니다.

동적 색상은 안드로이드 12 이상 기기에서 사용자의 배경화면(Wallpaper)에서 주요 색상을 추출하여, 앱의 전체적인 ColorScheme을 동적으로 생성하는 기술입니다. 즉, 사용자가 배경화면을 바꾸면 앱의 테마 색상도 그에 맞춰 실시간으로 변경되는 놀라운 사용자 경험을 제공합니다.

이 기능을 구현하는 것은 놀라울 정도로 간단합니다. 바로 우리 앱의 테마 시스템이 ColorScheme을 기반으로 구축되어 있기 때문입니다.

dynamic_color 패키지를 사용하여 동적 색상을 적용하는 방법입니다.


import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

// 기본 fallback ColorScheme 정의
const _defaultLightColorScheme = ColorScheme.fromSeed(seedColor: Colors.blue);
const _defaultDarkColorScheme = ColorScheme.fromSeed(seedColor: Colors.blue, brightness: Brightness.dark);

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    // DynamicColorBuilder 위젯으로 앱을 감싸줍니다.
    return DynamicColorBuilder(
      builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
        
        // 동적으로 생성된 ColorScheme이 있다면 그것을 사용하고,
        // 없다면(지원하지 않는 기기) 미리 정의해둔 기본 값을 사용합니다.
        ColorScheme lightColorScheme = lightDynamic ?? _defaultLightColorScheme;
        ColorScheme darkColorScheme = darkDynamic ?? _defaultDarkColorScheme;

        return MaterialApp(
          title: 'Dynamic Color Demo',
          theme: ThemeData(
            colorScheme: lightColorScheme,
            useMaterial3: true,
          ),
          darkTheme: ThemeData(
            colorScheme: darkColorScheme,
            useMaterial3: true,
          ),
          themeMode: ThemeMode.system,
          home: const MyHomePage(),
        );
      },
    );
  }
}

만약 우리 앱이 여전히 primaryColor 기반이었다면, 이처럼 아름답고 사용자 친화적인 기능을 구현하는 것은 거의 불가능했을 것입니다. ColorScheme은 이처럼 플러터가 제공하는 최신 UI/UX 혁신을 받아들이기 위한 필수적인 기반입니다.

결론: 변화를 수용하고 앞으로 나아가기

우리는 primaryColor와 함께 플러터 여정을 시작했습니다. 그것은 간단했고, 많은 경우에 충분히 좋은 결과를 보여주었습니다. 하지만 기술은 발전하고, 사용자들의 기대 수준은 높아지며, 디자인 시스템은 더욱 정교해졌습니다. 이러한 변화의 흐름 속에서 primaryColor는 그 한계를 드러냈고, ColorScheme이라는 훨씬 더 강력하고 체계적인 대안에게 자리를 내주게 되었습니다.

primaryColor를 떠나보낸다는 것은 단순히 오래된 API를 새로운 API로 바꾸는 작업이 아닙니다. 그것은 색상을 파편적으로 관리하던 방식에서 벗어나, 앱 전체의 색상을 하나의 일관된 시스템 안에서 역할 기반으로 관리하는 철학적 전환을 의미합니다. 이 전환을 통해 우리는 다음과 같은 가치를 얻게 됩니다.

  • 견고함: 예측 가능하고 일관된 색상 적용으로 UI 버그가 줄어듭니다.
  • 효율성: 테마 변경 및 유지보수 비용이 극적으로 감소합니다.
  • 미래지향성: Material 3, 동적 색상 등 최신 기능을 손쉽게 도입할 수 있습니다.

아직 primaryColor를 사용하고 있는 프로젝트가 있다면, 더 이상 주저하지 마세요. 오늘 당장 ColorScheme으로의 마이그레이션을 시작하십시오. 처음에는 약간의 학습 곡선과 리팩토링 노력이 필요하겠지만, 그 보상은 장기적으로 여러분의 프로젝트를 더욱 건강하고 경쟁력 있게 만들어 줄 것입니다. 이제는 익숙했던 primaryColor에게 작별을 고하고, ColorScheme과 함께 더 넓고 다채로운 플러터의 세계로 나아갈 때입니다.

Flutter 공식 ColorScheme 문서 확인하기

Post a Comment