flutter-theming-apps

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Implementing Flutter Theming and Adaptive Design

实现Flutter主题与自适应设计

Contents

目录

Core Theming Concepts

核心主题概念

Flutter applies styling in a strict hierarchy: styles applied to the specific widget -> themes that override the immediate parent theme -> the main app theme.
  • Define app-wide themes using the
    theme
    property of
    MaterialApp
    with a
    ThemeData
    instance.
  • Override themes for specific widget subtrees by wrapping them in a
    Theme
    widget and using
    Theme.of(context).copyWith(...)
    .
  • Do not use deprecated
    ThemeData
    properties:
    • Replace
      accentColor
      with
      colorScheme.secondary
      .
    • Replace
      accentTextTheme
      with
      textTheme
      (using
      colorScheme.onSecondary
      for contrast).
    • Replace
      AppBarTheme.color
      with
      AppBarTheme.backgroundColor
      .
Flutter 采用严格的样式层级:应用到特定Widget的样式 -> 覆盖父级主题的主题 -> 应用主主题。
  • 使用
    MaterialApp
    theme
    属性搭配
    ThemeData
    实例定义全局应用主题。
  • 通过将Widget子树包裹在
    Theme
    组件中,并使用
    Theme.of(context).copyWith(...)
    来覆盖特定子树的主题。
  • 请勿使用已废弃的
    ThemeData
    属性:
    • colorScheme.secondary
      替代
      accentColor
    • textTheme
      (搭配
      colorScheme.onSecondary
      保证对比度)替代
      accentTextTheme
    • AppBarTheme.backgroundColor
      替代
      AppBarTheme.color

Material 3 Guidelines

Material 3 指南

Material 3 is the default theme as of Flutter 3.16.
  • Colors: Generate color schemes using
    ColorScheme.fromSeed(seedColor: Colors.blue)
    . This ensures accessible contrast ratios.
  • Elevation: Material 3 uses
    ColorScheme.surfaceTint
    to indicate elevation instead of just drop shadows. To revert to M2 shadow behavior, set
    surfaceTint: Colors.transparent
    and define a
    shadowColor
    .
  • Typography: Material 3 updates font sizes, weights, and line heights. If text wrapping breaks legacy layouts, adjust
    letterSpacing
    on the specific
    TextStyle
    .
  • Modern Components:
    • Replace
      BottomNavigationBar
      with
      NavigationBar
      .
    • Replace
      Drawer
      with
      NavigationDrawer
      .
    • Replace
      ToggleButtons
      with
      SegmentedButton
      .
    • Use
      FilledButton
      for a high-emphasis button without the elevation of
      ElevatedButton
      .
从Flutter 3.16开始,Material 3成为默认主题。
  • 颜色:使用
    ColorScheme.fromSeed(seedColor: Colors.blue)
    生成配色方案,确保符合可访问性对比度标准。
  • 阴影层级:Material 3 使用
    ColorScheme.surfaceTint
    来标识阴影层级,而非仅依赖阴影效果。若要恢复M2的阴影行为,设置
    surfaceTint: Colors.transparent
    并定义
    shadowColor
  • 排版:Material 3 更新了字体大小、字重和行高。如果文本换行破坏了原有布局,可调整特定
    TextStyle
    letterSpacing
  • 现代组件
    • NavigationBar
      替代
      BottomNavigationBar
    • NavigationDrawer
      替代
      Drawer
    • SegmentedButton
      替代
      ToggleButtons
    • 对于无需阴影层级的高优先级按钮,使用
      FilledButton
      而非
      ElevatedButton

Component Theme Normalization

组件主题规范化

Component themes in
ThemeData
have been normalized to use
*ThemeData
classes rather than
*Theme
widgets.
When defining
ThemeData
, strictly use the
*ThemeData
suffix for the following properties:
  • cardTheme
    : Use
    CardThemeData
    (Not
    CardTheme
    )
  • dialogTheme
    : Use
    DialogThemeData
    (Not
    DialogTheme
    )
  • tabBarTheme
    : Use
    TabBarThemeData
    (Not
    TabBarTheme
    )
  • appBarTheme
    : Use
    AppBarThemeData
    (Not
    AppBarTheme
    )
  • bottomAppBarTheme
    : Use
    BottomAppBarThemeData
    (Not
    BottomAppBarTheme
    )
  • inputDecorationTheme
    : Use
    InputDecorationThemeData
    (Not
    InputDecorationTheme
    )
ThemeData
中的组件主题已规范化为使用
*ThemeData
类,而非
*Theme
组件。
定义
ThemeData
时,以下属性必须严格使用
*ThemeData
后缀:
  • cardTheme
    :使用
    CardThemeData
    (而非
    CardTheme
  • dialogTheme
    :使用
    DialogThemeData
    (而非
    DialogTheme
  • tabBarTheme
    :使用
    TabBarThemeData
    (而非
    TabBarTheme
  • appBarTheme
    :使用
    AppBarThemeData
    (而非
    AppBarTheme
  • bottomAppBarTheme
    :使用
    BottomAppBarThemeData
    (而非
    BottomAppBarTheme
  • inputDecorationTheme
    :使用
    InputDecorationThemeData
    (而非
    InputDecorationTheme

Button Styling

按钮样式

Legacy button classes (
FlatButton
,
RaisedButton
,
OutlineButton
) are obsolete.
  • Use
    TextButton
    ,
    ElevatedButton
    , and
    OutlinedButton
    .
  • Configure button appearance using a
    ButtonStyle
    object.
  • For simple overrides based on the theme's color scheme, use the static utility method:
    TextButton.styleFrom(foregroundColor: Colors.blue)
    .
  • For state-dependent styling (hovered, focused, pressed, disabled), use
    MaterialStateProperty.resolveWith
    .
旧版按钮类(
FlatButton
RaisedButton
OutlineButton
)已废弃。
  • 使用
    TextButton
    ElevatedButton
    OutlinedButton
  • 使用
    ButtonStyle
    对象配置按钮外观。
  • 若要基于主题配色方案进行简单覆盖,可使用静态工具方法:
    TextButton.styleFrom(foregroundColor: Colors.blue)
  • 对于依赖状态的样式(悬停、聚焦、按下、禁用),使用
    MaterialStateProperty.resolveWith

Platform Idioms & Adaptive Design

平台规范与自适应设计

When building adaptive apps, respect platform-specific norms to reduce cognitive load and build user trust.
  • Scrollbars: Desktop users expect omnipresent scrollbars; mobile users expect them only during scrolling. Toggle
    thumbVisibility
    on the
    Scrollbar
    widget based on the platform.
  • Selectable Text: Web and desktop users expect text to be selectable. Wrap text in
    SelectableText
    or
    SelectableText.rich
    .
  • Horizontal Button Order: Windows places confirmation buttons on the left; macOS/Linux place them on the right. Use a
    Row
    with
    TextDirection.rtl
    for Windows and
    TextDirection.ltr
    for others.
  • Context Menus & Tooltips: Desktop users expect hover and right-click interactions. Implement
    Tooltip
    for hover states and use context menu packages for right-click actions.
构建自适应应用时,需遵循平台特定规范,以降低用户认知负荷并建立信任。
  • 滚动条:桌面用户期望滚动条始终可见;移动用户仅希望滚动时显示。根据平台切换
    Scrollbar
    组件的
    thumbVisibility
    属性。
  • 可选中文本:Web和桌面用户期望文本可选中。将文本包裹在
    SelectableText
    SelectableText.rich
    中。
  • 按钮水平顺序:Windows系统将确认按钮放在左侧;macOS/Linux放在右侧。使用
    Row
    组件,Windows系统设置
    TextDirection.rtl
    ,其他系统设置
    TextDirection.ltr
  • 上下文菜单与工具提示:桌面用户期望悬停和右键交互。为悬停状态实现
    Tooltip
    ,并使用上下文菜单包处理右键操作。

Workflows

工作流程

Workflow: Migrating Legacy Themes to Material 3

工作流程:将旧版主题迁移至Material 3

Use this workflow when updating an older Flutter codebase.
Task Progress:
  • 1. Remove
    useMaterial3: false
    from
    ThemeData
    (it is true by default).
  • 2. Replace manual
    ColorScheme
    definitions with
    ColorScheme.fromSeed()
    .
  • 3. Run validator -> review errors -> fix: Search for and replace deprecated
    accentColor
    ,
    accentColorBrightness
    ,
    accentIconTheme
    , and
    accentTextTheme
    .
  • 4. Run validator -> review errors -> fix: Search for
    AppBarTheme(color: ...)
    and replace with
    backgroundColor
    .
  • 5. Update
    ThemeData
    component properties to use
    *ThemeData
    classes (e.g.,
    cardTheme: CardThemeData()
    ).
  • 6. Replace legacy buttons (
    FlatButton
    ->
    TextButton
    ,
    RaisedButton
    ->
    ElevatedButton
    ,
    OutlineButton
    ->
    OutlinedButton
    ).
  • 7. Replace legacy navigation components (
    BottomNavigationBar
    ->
    NavigationBar
    ,
    Drawer
    ->
    NavigationDrawer
    ).
当更新旧版Flutter代码库时,可使用此工作流程。
任务进度:
  • 1. 从
    ThemeData
    中移除
    useMaterial3: false
    (默认值为true)。
  • 2. 用
    ColorScheme.fromSeed()
    替代手动定义的
    ColorScheme
  • 3. 运行验证器 -> 查看错误 -> 修复:搜索并替换已废弃的
    accentColor
    accentColorBrightness
    accentIconTheme
    accentTextTheme
  • 4. 运行验证器 -> 查看错误 -> 修复:搜索
    AppBarTheme(color: ...)
    并替换为
    backgroundColor
  • 5. 将
    ThemeData
    的组件属性更新为使用
    *ThemeData
    类(例如
    cardTheme: CardThemeData()
    )。
  • 6. 替换旧版按钮(
    FlatButton
    ->
    TextButton
    RaisedButton
    ->
    ElevatedButton
    OutlineButton
    ->
    OutlinedButton
    )。
  • 7. 替换旧版导航组件(
    BottomNavigationBar
    ->
    NavigationBar
    Drawer
    ->
    NavigationDrawer
    )。

Workflow: Implementing Adaptive UI Components

工作流程:实现自适应UI组件

Use this workflow when building a widget intended for both mobile and desktop/web.
Task Progress:
  • 1. If displaying a list/grid, wrap it in a
    Scrollbar
    and set
    thumbVisibility: DeviceType.isDesktop
    .
  • 2. If displaying read-only data, use
    SelectableText
    instead of
    Text
    .
  • 3. If implementing a dialog with action buttons, check the platform. If Windows, set
    TextDirection.rtl
    on the button
    Row
    .
  • 4. If implementing interactive elements, wrap them in
    Tooltip
    widgets to support mouse hover states.
当构建同时面向移动、桌面和Web的Widget时,可使用此工作流程。
任务进度:
  • 1. 若显示列表/网格,将其包裹在
    Scrollbar
    中,并根据平台设置
    thumbVisibility: DeviceType.isDesktop
  • 2. 若显示只读数据,使用
    SelectableText
    而非
    Text
  • 3. 若实现带操作按钮的对话框,检查平台。如果是Windows系统,在按钮
    Row
    中设置
    TextDirection.rtl
  • 4. 若实现交互元素,将其包裹在
    Tooltip
    组件中以支持鼠标悬停状态。

Examples

示例

Example: Modern Material 3 ThemeData Setup

示例:现代Material 3 ThemeData配置

dart
MaterialApp(
  title: 'Adaptive App',
  theme: ThemeData(
    colorScheme: ColorScheme.fromSeed(
      seedColor: Colors.deepPurple,
      brightness: Brightness.light,
    ),
    // Use *ThemeData classes for component normalization
    appBarTheme: const AppBarThemeData(
      backgroundColor: Colors.deepPurple, // Do not use 'color'
      elevation: 0,
    ),
    cardTheme: const CardThemeData(
      elevation: 2,
    ),
    textTheme: const TextTheme(
      bodyMedium: TextStyle(letterSpacing: 0.2),
    ),
  ),
  home: const MyHomePage(),
);
dart
MaterialApp(
  title: 'Adaptive App',
  theme: ThemeData(
    colorScheme: ColorScheme.fromSeed(
      seedColor: Colors.deepPurple,
      brightness: Brightness.light,
    ),
    // 使用*ThemeData类进行组件规范化
    appBarTheme: const AppBarThemeData(
      backgroundColor: Colors.deepPurple, // 请勿使用'color'
      elevation: 0,
    ),
    cardTheme: const CardThemeData(
      elevation: 2,
    ),
    textTheme: const TextTheme(
      bodyMedium: TextStyle(letterSpacing: 0.2),
    ),
  ),
  home: const MyHomePage(),
);

Example: State-Dependent ButtonStyle

示例:依赖状态的ButtonStyle

dart
TextButton(
  style: ButtonStyle(
    // Default color
    foregroundColor: MaterialStateProperty.all<Color>(Colors.blue),
    // State-dependent overlay color
    overlayColor: MaterialStateProperty.resolveWith<Color?>(
      (Set<MaterialState> states) {
        if (states.contains(MaterialState.hovered)) {
          return Colors.blue.withOpacity(0.04);
        }
        if (states.contains(MaterialState.focused) || states.contains(MaterialState.pressed)) {
          return Colors.blue.withOpacity(0.12);
        }
        return null; // Defer to the widget's default.
      },
    ),
  ),
  onPressed: () {},
  child: const Text('Adaptive Button'),
)
dart
TextButton(
  style: ButtonStyle(
    // 默认颜色
    foregroundColor: MaterialStateProperty.all<Color>(Colors.blue),
    // 依赖状态的覆盖色
    overlayColor: MaterialStateProperty.resolveWith<Color?>(
      (Set<MaterialState> states) {
        if (states.contains(MaterialState.hovered)) {
          return Colors.blue.withOpacity(0.04);
        }
        if (states.contains(MaterialState.focused) || states.contains(MaterialState.pressed)) {
          return Colors.blue.withOpacity(0.12);
        }
        return null; // 遵循组件默认值
      },
    ),
  ),
  onPressed: () {},
  child: const Text('Adaptive Button'),
)

Example: Adaptive Dialog Button Order

示例:自适应对话框按钮顺序

dart
Row(
  // Windows expects confirmation on the left (RTL reverses the standard LTR Row)
  textDirection: Platform.isWindows ? TextDirection.rtl : TextDirection.ltr,
  mainAxisAlignment: MainAxisAlignment.end,
  children: [
    TextButton(
      onPressed: () => Navigator.pop(context, false),
      child: const Text('Cancel'),
    ),
    FilledButton(
      onPressed: () => Navigator.pop(context, true),
      child: const Text('Confirm'),
    ),
  ],
)
dart
Row(
  // Windows系统期望确认按钮在左侧(RTL会反转标准LTR的Row顺序)
  textDirection: Platform.isWindows ? TextDirection.rtl : TextDirection.ltr,
  mainAxisAlignment: MainAxisAlignment.end,
  children: [
    TextButton(
      onPressed: () => Navigator.pop(context, false),
      child: const Text('Cancel'),
    ),
    FilledButton(
      onPressed: () => Navigator.pop(context, true),
      child: const Text('Confirm'),
    ),
  ],
)