CupertinoTheme

Flutter中用于定义和应用iOS风格主题的组件

CupertinoTheme是Flutter中用于定义和应用iOS风格主题的组件。它提供了一种在应用程序中统一管理和传递主题数据(如颜色、字体、文本样式等)的方式,特别适用于遵循Apple iOS设计准则的 应用。通过CupertinoTheme,您可以为整个应用程序或其特定部分设置统一的视觉风格。

使用场景

  • 统一应用主题: 在整个iOS风格的Flutter应用中,为所有Cupertino风格的部件(如CupertinoNavigationBar, CupertinoButton, CupertinoTextField等)设置统一的颜色、字体和亮度(Brightness)。
  • 局部主题覆盖: 在应用的某个特定部分,需要与全局主题有所不同时,可以使用CupertinoTheme嵌套来覆盖或继承部分主题属性。
  • 深色/浅色模式切换: 配合CupertinoThemeDatabrightness属性,方便地实现应用的深色模式和浅色模式切换。
  • 响应系统主题: 结合MediaQuery监听系统的主题设置,动态调整CupertinoTheme

示例

基础应用主题设置

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; // 仅用于 Material scaffold

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

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

  
  Widget build(BuildContext context) {
    return CupertinoApp(
      // 使用 CupertinoTheme 包裹整个应用
      home: CupertinoTheme(
        data: const CupertinoThemeData(
          primaryColor: CupertinoColors.systemGreen, // 设置应用的iOS主色调为绿色
          scaffoldBackgroundColor: CupertinoColors.extraLightBackgroundGray, // 设置页面背景色
          brightness: Brightness.light, // 明确指定为浅色模式
        ),
        child: const MyHomePage(),
      ),
    );
  }
}

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

  
  Widget build(BuildContext context) {
    // 获取当前应用的主题数据
    final theme = CupertinoTheme.of(context);

    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: Text(
          'Cupertino Theme Demo',
          style: theme.textTheme.navTitleTextStyle, // 使用主题文本样式
        ),
        backgroundColor: CupertinoColors.white.withOpacity(0.9), // 导航栏背景透明度
      ),
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            // primaryColor 会影响 CupertinoButton 的文字颜色
            CupertinoButton(
              onPressed: () {},
              child: const Text('Cupertino Button'),
            ),
            const SizedBox(height: 20),
            // 背景色保持默认或由 scaffoldBackgroundColor 控制
            CupertinoActivityIndicator(
              radius: 15.0,
              color: theme.primaryColor, // 使用主题的primaryColor
            ),
            const SizedBox(height: 20),
            Text(
              '当前主题 PrimaryColor: ${theme.primaryColor}',
              style: theme.textTheme.textStyle, // 使用主题文本样式
            ),
          ],
        ),
      ),
    );
  }
}

局部主题覆盖与深色模式切换

import 'package:flutter/cupertino.dart';

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

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

  
  State<ThemeSwitchApp> createState() => _ThemeSwitchAppState();
}

class _ThemeSwitchAppState extends State<ThemeSwitchApp> {
  Brightness _brightness = Brightness.light;

  void _toggleBrightness() {
    setState(() {
      _brightness = _brightness == Brightness.light ? Brightness.dark : Brightness.light;
    });
  }

  
  Widget build(BuildContext context) {
    return CupertinoApp(
      home: CupertinoTheme(
        data: CupertinoThemeData(
          brightness: _brightness, // 根据状态切换深色/浅色模式
          primaryColor: _brightness == Brightness.light
              ? CupertinoColors.systemBlue
              : CupertinoColors.systemOrange, // 不同模式下主色不同
          scaffoldBackgroundColor: _brightness == Brightness.light
              ? CupertinoColors.extraLightBackgroundGray
              : CupertinoColors.darkBackgroundGray,
        ),
        child: CupertinoPageScaffold(
          navigationBar: CupertinoNavigationBar(
            middle: const Text('Theme Switch Demo'),
            trailing: CupertinoButton(
              padding: EdgeInsets.zero,
              onPressed: _toggleBrightness,
              child: Icon(_brightness == Brightness.light ? CupertinoIcons.moon_fill : CupertinoIcons.sun_max_fill),
            ),
          ),
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                CupertinoButton.filled(
                  onPressed: () {},
                  child: const Text('Filled Button'),
                ),
                const SizedBox(height: 20),
                // 局部子主题,PrimaryColor 覆盖了父主题
                CupertinoTheme(
                  data: CupertinoTheme.of(context).copyWith(
                    primaryColor: _brightness == Brightness.light
                        ? CupertinoColors.systemRed
                        : CupertinoColors.systemTeal,
                  ),
                  child: CupertinoButton(
                    onPressed: () {},
                    child: const Text('Locally Themed Button'),
                  ),
                ),
                const SizedBox(height: 20),
                Text(
                  '当前应用模式: ${_brightness == Brightness.light ? "浅色" : "深色"}',
                  style: CupertinoTheme.of(context).textTheme.textStyle,
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

动态响应系统主题

import 'package:flutter/cupertino.dart';

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

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

  
  Widget build(BuildContext context) {
    return CupertinoApp(
      home: Builder(
        builder: (context) {
          // 获取当前系统的 brightness
          final platformBrightness = MediaQuery.of(context).platformBrightness;
          final isDarkMode = platformBrightness == Brightness.dark;

          return CupertinoTheme(
            data: CupertinoThemeData(
              brightness: platformBrightness,
              primaryColor: isDarkMode ? CupertinoColors.systemGreen : CupertinoColors.systemBlue,
              scaffoldBackgroundColor: isDarkMode
                  ? CupertinoColors.darkBackgroundGray
                  : CupertinoColors.extraLightBackgroundGray,
            ),
            child: CupertinoPageScaffold(
              navigationBar: CupertinoNavigationBar(
                middle: const Text('System Theme Demo'),
                backgroundColor: isDarkMode
                    ? CupertinoColors.darkBackgroundGray.withOpacity(0.9)
                    : CupertinoColors.white.withOpacity(0.9),
              ),
              child: Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    Text(
                      '当前系统模式: ${isDarkMode ? "深色" : "浅色"}',
                      style: CupertinoTheme.of(context).textTheme.textStyle,
                    ),
                    const SizedBox(height: 20),
                    CupertinoButton(
                      onPressed: () {
                        // 演示一个按钮,其颜色会随主题变化
                      },
                      child: const Text('Hello Cupertino!'),
                    ),
                    const SizedBox(height: 20),
                    // 用于检查主题是否正确应用的指示器
                    CupertinoActivityIndicator(
                      color: CupertinoTheme.of(context).primaryColor,
                      radius: 15.0,
                    ),
                  ],
                ),
              ),
            ),
          );
        },
      ),
    );
  }
}

注意事项

  • 主题查找顺序: CupertinoTheme.of(context)会从最近的CupertinoTheme祖先节点查找主题数据。如果没有找到CupertinoTheme,它会退回到默认的iOS风格主题数据。
  • CupertinoThemeData的重要性: CupertinoTheme接收的data属性是CupertinoThemeData类型,所有主题的定制都是通过配置CupertinoThemeData的属性来实现的。
  • MaterialTheme的区别: CupertinoTheme仅影响Cupertino风格的组件。如果您的应用混合使用了Material和Cupertino组件,您可能需要同时使用ThemeCupertinoTheme
  • 性能考虑: 大范围频繁地切换CupertinoTheme数据可能会导致组件重建,但对于应用程序整体主题切换(如深色模式)通常不是性能瓶颈。
  • 文本主题(textTheme): CupertinoThemeData中的textTheme属性非常强大,可以定义各种文本样式,如navTitleTextStylenavLargeTitleTextStyleactionTextStyle等。使用这些预定义样式可以更好地保持iOS的视觉一致性。
  • primaryColor的作用: primaryColor是最重要的颜色之一,它影响到许多Cupertino控件(如按钮文本、进度指示器等)的默认颜色。

构造函数

CupertinoTheme的构造函数相对简单,主要接受datachild两个参数。

const CupertinoTheme({
  Key? key,
  required CupertinoThemeData data, // 必需,定义主题数据
  required Widget child,             // 必需,应用主题的子Widget
})
  • key: 可选,用于在Widget树中唯一标识此Widget
  • data: 类型为CupertinoThemeData。这是定义iOS风格主题数据的地方,包括颜色、文本样式、亮度等。
  • child: 类型为Widget。当前CupertinoTheme的子Widget树将继承并使用此处定义的主题数据。

属性

属性名称属性类型说明
brightnessBrightness?设置主题的亮度(lightdark)。它会影响某些 Cupertino 组件的默认颜色,例如 CupertinoActivityIndicator 的颜色。
primaryColorColor?用于整个应用的主要颜色。此颜色一般用于显示强调交互元素,如按钮文本、选中状态的图标等。如果未指定,将根据 brightness 默认值为 CupertinoColors.systemBlueCupertinoColors.white (深色模式)。
primaryContrastingColorColor?primaryColor 形成强烈对比的颜色。常用于在 primaryColor 背景上显示文本或图标。例如,如果 primaryColor 是深色,此颜色应为浅色。
scaffoldBackgroundColorColor?CupertinoPageScaffold 的主题背景颜色。它用于普通页面背景,如果未指定,将根据 brightness 默认值为 CupertinoColors.systemBackgroundCupertinoColors.darkBackgroundGray (+800亮度)。
barBackgroundColorColor?CupertinoNavigationBarCupertinoTabBar 的主题背景颜色。如果未指定,将根据 brightness 默认值为 CupertinoColors.tertiarySystemFillCupertinoColors.darkBackgroundGray (-100亮度)。
textThemeCupertinoTextThemeData?定义 Cupertino 风格的文本主题,包含各种文本样式,如导航栏标题、大标题、按钮文本等。这是一个非常重要的属性,用于统一应用内的字体和文本表现。
activeColorColor?用于表示活跃状态或选中状态的颜色,例如 CupertinoSwitch 被选中时的颜色。默认为 primaryColor
inactiveColorColor?用于表示非活跃或未选中状态的颜色。例如 CupertinoSwitch 未选中时的颜色。
splashFactoryInteractiveInkFeatureFactory?(此属性在 CupertinoThemeData 中通常不直接使用,更常用于 MaterialTheme)。在 Cupertino 风格中通常通过 CupertinoButton 等组件的自身行为来处理触摸反馈,而不是 Material 的 InkWell
separatorColorColor?用于绘制分隔线的颜色,例如 CupertinoListTile 之间的分隔线。
textSelectionColorColor?当用户选择文本时,用于文本高亮的颜色。
textSelectionHandleColorColor?文本选择手柄(拖动选择范围的圆点)的颜色。
is NoPaddingbool?(较少用) 通常用于控制某些布局的默认内边距。

关键属性解释

  • brightness: 这个属性是Cupertino主题实现深色/浅色模式切换的关键。它会影响多个默认颜色,使得整个应用能够适应用户的系统偏好。建议总是在CupertinoThemeData中明确设置此属性。
  • primaryColor: 它是Fuchsia应用的主色调,会影响到很多交互元素的视觉表现。例如,CupertinoButton的文本颜色如果没有被明确覆盖,会继承primaryColor。合理选择primaryColor对应用的视觉统一性至关重要。
  • scaffoldBackgroundColorbarBackgroundColor: 这两个颜色分别控制CupertinoPageScaffold的主体背景和导航栏/标签栏的背景。它们对于定义应用的整体背景层次结构非常重要。
  • textTheme: 这是一个高度可定制的属性,类型是CupertinoTextThemeData。它允许您为应用中的不同文本类型(如导航栏标题、大标题、正文等)定义统一的TextStyle。例如,CupertinoTextThemeData.navTitleTextStyle可以在导航栏中使用,以确保标题具有一致的iOS风格。通过CupertinoTheme.of(context).textTheme.yourTextStyle获取并使用这些样式,可以大大提高UI的一致性。