點燈坊

失くすものさえない今が強くなるチャンスよ

Material Design 使用自訂顏色變數

Sam Xiao's Avatar 2024-11-03

雖然理論上 Material Design 要使用內建的顏色變數,如 primarysecondary…,但實務上 Designer 可能並不使用這些變數,我們也可以在 Material Design 使用 Figma 的變數。

Version

Flutter 3.24

Flutter

custom01

  • Android 與 iOS 都成功使用 Material Design 的自訂顏色變數

Custom Colors

utils/custom_colors.dart

import 'package:flutter/material.dart';

import '../constants/dark_custom_colors.dart';
import '../constants/light_custom_colors.dart';

ThemeData getThemeData(Brightness brightness) {
  return ThemeData(
    brightness: brightness,
    useMaterial3: true,
    extensions: <ThemeExtension<dynamic>>[
      brightness == Brightness.light ? lightCustomColors : darkCustomColors,
    ],
  );
}

class CustomColors extends ThemeExtension<CustomColors> {
  final Color grey100;
  final Color darkRed;

  const CustomColors({
    required this.grey100,
    required this.darkRed,
  });

  
  CustomColors copyWith({Color? grey100, Color? darkRed}) {
    return CustomColors(
      grey100: grey100 ?? this.grey100,
      darkRed: darkRed ?? this.darkRed,
    );
  }

  
  CustomColors lerp(ThemeExtension<CustomColors>? other, double t) {
    if (other is! CustomColors) return this;

    return CustomColors(
      grey100: Color.lerp(grey100, other.grey100, t)!,
      darkRed: Color.lerp(darkRed, other.darkRed, t)!,
    );
  }
}
  • 要使用自訂顏色變數,必須先遵循 Material Design 先建立 CustomColors class

Line 17

final Color grey100;
final Color darkRed;
  • CustomColors class 將自訂顏色變數定義成 property,有幾個顏色就要定義幾個 property

Line 20

const CustomColors({
  required this.grey100,
  required this.darkRed,
});
  • CustomColors class 的 constructor 定義自訂顏色變數,有幾個顏色就要定義幾個

Line 25


CustomColors copyWith({Color? grey100, Color? darkRed}) {
  return CustomColors(
    grey100: grey100 ?? this.grey100,
    darkRed: darkRed ?? this.darkRed,
  );
}
  • CustomColors class override copyWith(),有幾個顏色就要定義幾個

Line 33


CustomColors lerp(ThemeExtension<CustomColors>? other, double t) {
  if (other is! CustomColors) return this;

  return CustomColors(
    grey100: Color.lerp(grey100, other.grey100, t)!,
    darkRed: Color.lerp(darkRed, other.darkRed, t)!,
  );
}
  • CustomColors class override lerp(),有幾個顏色就要定義幾個

Line 6

ThemeData getThemeData(Brightness brightness) {
  return ThemeData(
    brightness: brightness,
    useMaterial3: true,
    extensions: <ThemeExtension<dynamic>>[
      brightness == Brightness.light ? lightCustomColors : darkCustomColors,
    ],
  );
}
  • 使用 Extension Method 將自訂顏色變數加到 ThemeData

Light Custom Colors

constants/light_custom_colors.dart

import 'package:flutter/material.dart';

import '../utils/custom_colors.dart';

var lightCustomColors = const CustomColors(
  grey100: Color(0xFFF8F8F8),
  darkRed: Color(0xFFFF0000),
);
  • 定義 Light Mode 的自訂顏色變數

Dark Custom Colors

constants/dark_custom_color.dart

import 'package:flutter/material.dart';

import '../utils/custom_colors.dart';

var darkCustomColors = const CustomColors(
  grey100: Color(0xFF212529),
  darkRed: Color(0xFFFFFF00),
);
  • 定義 Dark Mode 的自訂顏色變數

Main

main.dart

import 'package:flutter/material.dart';
import 'package:flutter_lab/utils/custom_colors.dart';

import 'home.dart';

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

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
        theme: getThemeData(Brightness.light),
        darkTheme: getThemeData(Brightness.dark),
        themeMode: ThemeMode.system,
        home: const Home());
  }
}
  • main.dart 使用自訂 theme

Line 13


Widget build(BuildContext context) {
  return MaterialApp(
    theme: getThemeData(Brightness.light),
    darkTheme: getThemeData(Brightness.dark),
    themeMode: ThemeMode.system,
    home: const Home());
}
  • theme:由 getThemeData() 定義 Light Mode 的 theme
  • darkTheme:由 getThemeData() 定義 Dark Mode 的 theme
  • themeMode:預設使用系統所定義的 theme

Home

home.dart

import 'package:flutter/material.dart';

import 'utils/custom_colors.dart';

class Home extends StatefulWidget {
  const Home({super.key});

  
  State<Home> createState() => _Home();
}

class _Home extends State<Home> {
  late CustomColors _customColors;
  var _count = 0;

  
  Widget build(BuildContext context) {
    _customColors = Theme.of(context).extension<CustomColors>()!;

    var appBar = AppBar(
      title: const Text('Material Design Custom Color'),
    );

    var floatingActionButton = FloatingActionButton(
      onPressed: () => setState(() => _count++),
      backgroundColor: _customColors.grey100,
      foregroundColor: _customColors.darkRed,
      child: const Icon(
        Icons.add,
      ),
    );

    var body = Center(
      child: Text('$_count'),
    );

    return Scaffold(
      appBar: appBar,
      floatingActionButton: floatingActionButton,
      body: body,
    );
  }
}
  • 經典的 Counter

Line 12

class _Home extends State<Home> {
  late CustomColors _customColors;
}
  • _customColors:定義 _customColors 為 field,且為 late,將在稍後建立

Line 17

Widget build(BuildContext context) {
  _customColors = Theme.of(context).extension<CustomColors>()!;
}
  • _customColors:在 build() 內由 context 根據 dark mode 或 light mode 建立

Line 24

var floatingActionButton = FloatingActionButton(
  onPressed: () => setState(() => _count++),
  backgroundColor: _customColors.grey100,
  foregroundColor: _customColors.darkRed,
  child: const Icon(
    Icons.add,
  ),
);
  • backgrondColorforegroundColor 使用自訂的 gray100darkRed 變數

Conclusion

  • 之所以能使用 Material Design 又可使用自訂顏色變數,關鍵在於 Dart 支援 Extension Method
  • 可將 customColor 定義成 field,如此所有地方都可直接存取