Flutter 애플리케이션에 테마를 어떻게 적용하나요?

질문

Flutter 애플리케이션에 테마를 어떻게 적용하나요?

답변

Flutter에서 테마(Theme)는 애플리케이션 전체의 시각적 일관성을 유지하기 위한 중요한 요소입니다. 테마를 통해 색상, 글꼴, 모양 등의 스타일을 일괄적으로 관리할 수 있으며, 다크 모드와 라이트 모드 등 여러 테마를 쉽게 전환할 수 있습니다.

기본 테마 적용하기

Flutter에서 테마를 적용하는 가장 기본적인 방법은 MaterialApp 위젯의 theme 속성을 사용하는 것입니다.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter 테마 예제',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        backgroundColor: Colors.white,
        fontFamily: 'Roboto',
        textTheme: TextTheme(
          headline1: TextStyle(fontSize: 24.0, fontWeight: FontWeight.bold),
          bodyText1: TextStyle(fontSize: 16.0),
        ),
      ),
      home: HomePage(),
    );
  }
}

ThemeData 주요 속성

ThemeData 클래스는 테마의 다양한 측면을 정의하는 많은 속성을 가지고 있습니다:

1. 색상 관련 속성

ThemeData(
  // 기본 색상 - 앱의 주요 색상
  primarySwatch: Colors.blue, // Material 색상 팔레트
  primaryColor: Colors.blue, // 앱 바와 같은 주요 구성 요소의 색상
  accentColor: Colors.blueAccent, // 강조 색상 (버튼, 선택 컨트롤 등)

  // 배경 색상
  backgroundColor: Colors.white,
  scaffoldBackgroundColor: Colors.white, // Scaffold 위젯의 배경 색상

  // 특정 위젯 색상
  cardColor: Colors.white, // Card 위젯 배경 색상
  dividerColor: Colors.grey[300], // Divider 위젯 색상
  dialogBackgroundColor: Colors.white, // Dialog 배경 색상

  // 텍스트 색상
  textTheme: TextTheme(
    bodyText1: TextStyle(color: Colors.black87),
  ),
)

2. 텍스트 관련 속성

ThemeData(
  fontFamily: 'Pretendard', // 앱 전체 기본 폰트

  // 텍스트 테마
  textTheme: TextTheme(
    headline1: TextStyle(fontSize: 72.0, fontWeight: FontWeight.bold),
    headline2: TextStyle(fontSize: 36.0, fontStyle: FontStyle.italic),
    headline3: TextStyle(fontSize: 24.0),
    bodyText1: TextStyle(fontSize: 14.0, fontFamily: 'Hind'),
    bodyText2: TextStyle(fontSize: 14.0),
    button: TextStyle(fontSize: 16.0, letterSpacing: 1.5),
  ),
)

3. 위젯 스타일 속성

ThemeData(
  // 앱 바 테마
  appBarTheme: AppBarTheme(
    color: Colors.blue,
    elevation: 0,
    iconTheme: IconThemeData(color: Colors.white),
  ),

  // 버튼 테마
  buttonTheme: ButtonThemeData(
    buttonColor: Colors.blue,
    textTheme: ButtonTextTheme.primary,
    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
  ),

  // 카드 테마
  cardTheme: CardTheme(
    elevation: 2.0,
    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
  ),

  // 입력폼 테마
  inputDecorationTheme: InputDecorationTheme(
    border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.0)),
    focusedBorder: OutlineInputBorder(
      borderSide: BorderSide(color: Colors.blue, width: 2.0),
      borderRadius: BorderRadius.circular(8.0),
    ),
  ),
)

다크 모드 지원하기

Flutter에서는 라이트 모드와 다크 모드를 동시에 지원할 수 있습니다:

MaterialApp(
  theme: ThemeData.light().copyWith(
    // 라이트 모드 테마 커스터마이징
    primaryColor: Colors.blue,
  ),
  darkTheme: ThemeData.dark().copyWith(
    // 다크 모드 테마 커스터마이징
    primaryColor: Colors.indigo,
  ),
  themeMode: ThemeMode.system, // 시스템 설정 따르기
  // 또는 ThemeMode.light 또는 ThemeMode.dark
)

테마 데이터 상수화하기

테마 정의를 별도 파일로 분리하면 코드 관리가 용이해집니다:

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

class AppTheme {
  static final ThemeData lightTheme = ThemeData(
    brightness: Brightness.light,
    primaryColor: Colors.blue,
    // 추가적인 라이트 테마 설정...
  );

  static final ThemeData darkTheme = ThemeData(
    brightness: Brightness.dark,
    primaryColor: Colors.indigo,
    // 추가적인 다크 테마 설정...
  );
}

// main.dart
import 'themes.dart';

MaterialApp(
  theme: AppTheme.lightTheme,
  darkTheme: AppTheme.darkTheme,
  themeMode: ThemeMode.system,
)

커스텀 색상 팔레트 정의하기

Material Design 가이드라인에 맞는 커스텀 색상 팔레트를 정의할 수 있습니다:

const MaterialColor customBlue = MaterialColor(
  0xFF2196F3, // 기본 색상 값
  <int, Color>{
    50: Color(0xFFE3F2FD),
    100: Color(0xFFBBDEFB),
    200: Color(0xFF90CAF9),
    300: Color(0xFF64B5F6),
    400: Color(0xFF42A5F5),
    500: Color(0xFF2196F3), // 기본 색상
    600: Color(0xFF1E88E5),
    700: Color(0xFF1976D2),
    800: Color(0xFF1565C0),
    900: Color(0xFF0D47A1),
  },
);

ThemeData(
  primarySwatch: customBlue,
  // 기타 테마 설정...
)

테마 확장하기

기존 테마를 기반으로 일부만 수정하려면 copyWith 메서드를 사용합니다:

final ThemeData baseTheme = ThemeData.light();

final ThemeData customTheme = baseTheme.copyWith(
  primaryColor: Colors.purple,
  textTheme: baseTheme.textTheme.copyWith(
    headline1: baseTheme.textTheme.headline1!.copyWith(
      fontSize: 32.0,
      color: Colors.purple,
    ),
  ),
);

동적 테마 전환 구현하기

사용자가 앱 내에서 테마를 전환할 수 있도록 하려면 상태 관리가 필요합니다:

// 상태 관리 클래스 (Provider 사용)
class ThemeProvider with ChangeNotifier {
  ThemeMode _themeMode = ThemeMode.light;

  ThemeMode get themeMode => _themeMode;

  void setLightMode() {
    _themeMode = ThemeMode.light;
    notifyListeners();
  }

  void setDarkMode() {
    _themeMode = ThemeMode.dark;
    notifyListeners();
  }

  void toggleTheme() {
    _themeMode = _themeMode == ThemeMode.light
        ? ThemeMode.dark
        : ThemeMode.light;
    notifyListeners();
  }
}

// main.dart
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => ThemeProvider(),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final themeProvider = Provider.of<ThemeProvider>(context);

    return MaterialApp(
      theme: AppTheme.lightTheme,
      darkTheme: AppTheme.darkTheme,
      themeMode: themeProvider.themeMode,
      home: HomePage(),
    );
  }
}

// 테마 전환 버튼
ElevatedButton(
  onPressed: () {
    Provider.of<ThemeProvider>(context, listen: false).toggleTheme();
  },
  child: Text('테마 전환'),
)

테마 데이터 사용하기

위젯 내에서 현재 테마 데이터에 접근하려면 Theme.of(context)를 사용합니다:

Widget build(BuildContext context) {
  final theme = Theme.of(context);

  return Scaffold(
    appBar: AppBar(
      title: Text('테마 예제'),
    ),
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(
            '제목 텍스트',
            style: theme.textTheme.headline4,
          ),
          SizedBox(height: 16),
          Text(
            '본문 텍스트',
            style: theme.textTheme.bodyText1,
          ),
          SizedBox(height: 16),
          ElevatedButton(
            style: ElevatedButton.styleFrom(
              primary: theme.primaryColor,
            ),
            onPressed: () {},
            child: Text('테마 색상 버튼'),
          ),
        ],
      ),
    ),
  );
}

테마에 사용자 정의 속성 추가하기

ThemeExtension을 사용하여 사용자 정의 테마 속성을 추가할 수 있습니다 (Flutter 2.10 이상):

// 커스텀 테마 확장
class CustomColors extends ThemeExtension<CustomColors> {
  final Color? brandColor;
  final Color? secondaryBrandColor;

  CustomColors({this.brandColor, this.secondaryBrandColor});

  @override
  CustomColors copyWith({Color? brandColor, Color? secondaryBrandColor}) {
    return CustomColors(
      brandColor: brandColor ?? this.brandColor,
      secondaryBrandColor: secondaryBrandColor ?? this.secondaryBrandColor,
    );
  }

  @override
  CustomColors lerp(CustomColors? other, double t) {
    if (other is! CustomColors) {
      return this;
    }
    return CustomColors(
      brandColor: Color.lerp(brandColor, other.brandColor, t),
      secondaryBrandColor: Color.lerp(secondaryBrandColor, other.secondaryBrandColor, t),
    );
  }
}

// 테마 데이터에 확장 추가
final theme = ThemeData.light().copyWith(
  extensions: [
    CustomColors(
      brandColor: Colors.purple,
      secondaryBrandColor: Colors.amber,
    ),
  ],
);

// 확장 데이터 사용
Widget build(BuildContext context) {
  final customColors = Theme.of(context).extension<CustomColors>()!;

  return Container(
    color: customColors.brandColor,
    child: Text('브랜드 색상 컨테이너'),
  );
}

일반적인 테마 적용 팁

  1. 일관성 유지: 앱 전체에서 일관된 색상과 스타일을 사용하여 사용자 경험을 향상시킵니다.

  2. 접근성 고려: 충분한 대비를 제공하고 텍스트 크기를 적절히 설정하여 모든 사용자가 앱을 사용할 수 있도록 합니다.

  3. 모듈화: 테마 정의를 별도 파일로 분리하여 코드 관리를 용이하게 합니다.

  4. 의미 있는 색상 변수: 하드코딩된 색상 대신 의미 있는 이름의 변수를 사용합니다.

  5. 테마 미리보기: 라이트 모드와 다크 모드 모두에서 앱을 테스트하여 일관된 사용자 경험을 보장합니다.

요약

Flutter에서 테마를 적용하는 방법은 다음과 같습니다:

  1. MaterialApp 위젯의 theme 속성에 ThemeData 객체를 제공합니다.
  2. ThemeData에서 색상, 글꼴, 위젯 스타일 등을 정의합니다.
  3. 다크 모드를 지원하려면 darkTheme 속성과 함께 themeMode를 설정합니다.
  4. 위젯 내에서 Theme.of(context)를 통해 현재 테마에 접근합니다.
  5. 동적 테마 전환을 위해 상태 관리 솔루션을 사용합니다.
  6. 필요한 경우 사용자 정의 테마 확장을 정의하여 추가 속성을 사용합니다.

테마는 Flutter 앱의 시각적 일관성을 보장하고 다양한 환경에 적응하는 데 중요한 역할을 합니다. 잘 설계된 테마는 유지보수성을 향상시키고 브랜딩을 강화하며 사용자 경험을 개선합니다.

results matching ""

    No results matching ""