티스토리 뷰

Flutter로 애플리케이션을 개발할 때, 일관된 디자인을 유지하기 위해 테마(Theme)를 사용하는 것이 중요합니다. 이 블로그 포스트에서는 Flutter에서 테마를 정의하고 사용하는 방법, 테마를 적용하고 확장하는 방법, 그리고 특정 부분에서 테마를 덮어쓰는 방법을 살펴봅니다. 이를 통해 앱의 색상과 글꼴 스타일을 효율적으로 공유하고 관리할 수 있는 방법을 익힐 수 있습니다.

Flutter에서 테마란 무엇인가?

테마는 Flutter 애플리케이션 전반에 걸쳐 색상, 글꼴 스타일, 버튼 스타일 등의 디자인 요소를 통일된 방식으로 적용하는 시스템입니다. Flutter는 기본적으로 Material 3 테마를 지원하며, 이를 통해 사용자 인터페이스(UI) 전반에 일관된 스타일을 부여할 수 있습니다.

테마를 정의하고 적용하는 방법

1. 앱 전체에 테마 설정하기

Flutter에서는 MaterialApp 생성자에서 theme 속성을 사용하여 애플리케이션의 기본 테마를 설정할 수 있습니다. 이 속성은 ThemeData 인스턴스를 받으며, 여기서 색상, 글꼴 스타일, 밝기 등을 정의할 수 있습니다. 예를 들어, 다음 코드는 앱 전체에 Material 3 테마를 적용하는 방법을 보여줍니다.

MaterialApp(
  title: 'Flutter Demo',
  theme: ThemeData(
    useMaterial3: true,
    colorScheme: ColorScheme.fromSeed(seedColor: Colors.purple),
    textTheme: TextTheme(
      displayLarge: TextStyle(fontSize: 72, fontWeight: FontWeight.bold),
      titleLarge: GoogleFonts.oswald(fontSize: 30, fontStyle: FontStyle.italic),
      bodyMedium: GoogleFonts.merriweather(),
    ),
  ),
  home: MyHomePage(title: 'Flutter Home'),
);

위 코드에서는 ColorScheme을 사용해 기본 색상 팔레트를 설정하고, TextTheme을 통해 텍스트 스타일을 정의했습니다. 이로 인해 앱 전반에 걸쳐 일관된 색상과 글꼴이 적용됩니다.

2. 테마를 위젯에 적용하기

위젯의 스타일 속성에 테마를 적용하려면 Theme.of(context) 메서드를 사용합니다. 이 메서드는 위젯 트리에서 가장 가까운 테마를 찾아 그 스타일을 적용합니다. 예를 들어, Container 위젯에 테마 색상을 적용하는 코드는 다음과 같습니다.

Container(
  padding: EdgeInsets.all(12),
  color: Theme.of(context).colorScheme.primary,
  child: Text(
    '테마 색상이 적용된 텍스트',
    style: Theme.of(context).textTheme.bodyMedium!.copyWith(
      color: Theme.of(context).colorScheme.onPrimary,
    ),
  ),
);

3. 특정 부분에서 테마 덮어쓰기

애플리케이션 전체에 적용된 테마를 특정 부분에서 덮어쓰고 싶다면, 해당 부분을 Theme 위젯으로 감싸고 ThemeData를 설정할 수 있습니다. 예를 들어, 특정 버튼에 다른 색상 테마를 적용하는 코드는 다음과 같습니다.

Theme(
  data: ThemeData(
    colorScheme: ColorScheme.fromSeed(seedColor: Colors.pink),
  ),
  child: FloatingActionButton(
    onPressed: () {},
    child: Icon(Icons.add),
  ),
);

테마 확장하기

전체 테마를 덮어쓰는 대신, 부모 테마를 확장해 일부 속성만 변경할 수도 있습니다. 이를 위해 copyWith() 메서드를 사용합니다. 이 방법은 기존 테마 스타일을 유지하면서 특정 속성만 수정할 때 유용합니다.

Theme(
  data: Theme.of(context).copyWith(
    colorScheme: ColorScheme.fromSeed(seedColor: Colors.pink),
  ),
  child: FloatingActionButton(
    onPressed: null,
    child: Icon(Icons.add),
  ),
);

테마를 사용한 디자인 최적화 팁

  1. 일관된 디자인 유지: 테마를 사용하면 앱 전반에 걸쳐 일관된 디자인을 유지할 수 있습니다. 이는 사용자 경험(UX)을 향상시키고, 코드의 유지 보수성을 높여줍니다.
  2. 색상 팔레트 최적화: ColorScheme을 사용해 앱의 색상 팔레트를 중앙에서 관리하면 디자인 변경 시 효율적으로 대응할 수 있습니다.
  3. 글꼴 스타일 관리: TextTheme을 통해 다양한 텍스트 스타일을 미리 정의해 두면, 텍스트의 크기나 두께 등을 일관되게 적용할 수 있습니다.

Flutter 테마 적용 시 주의사항

  • 기본 테마 설정: Flutter는 테마를 지정하지 않으면 기본 Material 테마를 사용합니다. 따라서 테마를 명시적으로 설정하는 것이 좋습니다.
  • 테마 중첩 관리: 여러 테마가 중첩될 경우, 가장 가까운 상위 테마가 적용되므로 테마의 적용 범위를 명확히 관리해야 합니다.

결론

Flutter에서 테마를 사용하면 애플리케이션의 색상과 글꼴 스타일을 일관되게 적용할 수 있어 디자인 품질을 높일 수 있습니다. 또한 테마를 확장하거나 덮어쓰는 방법을 사용해 세부적인 스타일 변경도 유연하게 처리할 수 있습니다. 이러한 테마 시스템은 앱의 유지 보수성과 사용자 경험을 모두 향상시키는 강력한 도구입니다.

 

import 'package:flutter/material.dart';
// Include the Google Fonts package to provide more text format options
// https://pub.dev/packages/google_fonts
import 'package:google_fonts/google_fonts.dart';

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

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

  @override
  Widget build(BuildContext context) {
    const appName = 'Custom Themes';

    return MaterialApp(
      title: appName,
      theme: ThemeData(
        useMaterial3: true,

        // Define the default brightness and colors.
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.purple,
          // TRY THIS: Change to "Brightness.light"
          //           and see that all colors change
          //           to better contrast a light background.
          brightness: Brightness.dark,
        ),

        // Define the default `TextTheme`. Use this to specify the default
        // text styling for headlines, titles, bodies of text, and more.
        textTheme: TextTheme(
          displayLarge: const TextStyle(
            fontSize: 72,
            fontWeight: FontWeight.bold,
          ),
          // TRY THIS: Change one of the GoogleFonts
          //           to "lato", "poppins", or "lora".
          //           The title uses "titleLarge"
          //           and the middle text uses "bodyMedium".
          titleLarge: GoogleFonts.oswald(
            fontSize: 30,
            fontStyle: FontStyle.italic,
          ),
          bodyMedium: GoogleFonts.merriweather(),
          displaySmall: GoogleFonts.pacifico(),
        ),
      ),
      home: const MyHomePage(
        title: appName,
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final String title;

  const MyHomePage({super.key, required this.title});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title,
            style: Theme.of(context).textTheme.titleLarge!.copyWith(
                  color: Theme.of(context).colorScheme.onSecondary,
                )),
        backgroundColor: Theme.of(context).colorScheme.secondary,
      ),
      body: Center(
        child: Container(
          padding: const EdgeInsets.symmetric(
            horizontal: 12,
            vertical: 12,
          ),
          color: Theme.of(context).colorScheme.primary,
          child: Text(
            'Text with a background color',
            // TRY THIS: Change the Text value
            //           or change the Theme.of(context).textTheme
            //           to "displayLarge" or "displaySmall".
            style: Theme.of(context).textTheme.bodyMedium!.copyWith(
                  color: Theme.of(context).colorScheme.onPrimary,
                ),
          ),
        ),
      ),
      floatingActionButton: Theme(
        data: Theme.of(context).copyWith(
          // TRY THIS: Change the seedColor to "Colors.red" or
          //           "Colors.blue".
          colorScheme: ColorScheme.fromSeed(
            seedColor: Colors.pink,
            brightness: Brightness.dark,
          ),
        ),
        child: FloatingActionButton(
          onPressed: () {},
          child: const Icon(Icons.add),
        ),
      ),
    );
  }
}