主入口是Flutter应用的起点,它负责初始化应用、配置主题、设置路由等。一个好的主入口配置可以为整个应用奠定良好的基础。今天我们来详细讲解数独游戏的主入口与应用配置。
请添加图片描述

在设计主入口之前,我们需要考虑几个关键问题。首先是应用初始化,包括状态管理、屏幕适配等。其次是主题配置,定义应用的视觉风格。最后是路由设置,管理页面导航。

让我们从main.dart开始。

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'pages/main_page.dart';

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

main函数是应用的入口点,调用runApp启动应用。我们导入了Flutter的Material库,它提供了Material Design风格的组件。GetX是一个轻量级的状态管理库,集成了路由、依赖注入等功能。ScreenUtil是屏幕适配库,可以根据设计稿尺寸自动适配不同屏幕。main_page.dart是我们的主页面组件。使用const构造函数可以让Flutter在编译时创建对象,提升运行时性能。

MyApp类的实现。

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

  
  Widget build(BuildContext context) {
    return ScreenUtilInit(
      designSize: const Size(375, 812),
      minTextAdapt: true,
      builder: (context, child) {
        return GetMaterialApp(
          title: '数独游戏',
          debugShowCheckedModeBanner: false,
          theme: ThemeData(
            primarySwatch: Colors.blue,
            primaryColor: const Color(0xFF2196F3),
            scaffoldBackgroundColor: const Color(0xFFF5F5F5),
            appBarTheme: const AppBarTheme(
              backgroundColor: Colors.white,
              foregroundColor: Colors.black,
              elevation: 0.5,
              centerTitle: true,
            ),
          ),
          home: const MainPage(),
        );
      },
    );
  }
}

ScreenUtilInit是屏幕适配的初始化组件,必须包裹在最外层。designSize设置设计稿尺寸为375x812,这是iPhone X的屏幕尺寸,也是目前最常用的设计稿尺寸。minTextAdapt设为true表示文字大小也会根据屏幕适配。builder回调函数返回实际的应用组件。GetMaterialApp是GetX提供的MaterialApp替代品,它在MaterialApp的基础上集成了GetX的状态管理、路由管理和依赖注入功能。title设置应用标题,会显示在任务管理器中。debugShowCheckedModeBanner设为false可以隐藏右上角的调试标签。home指定应用启动后显示的第一个页面。

主题配置详解。

theme: ThemeData(
  primarySwatch: Colors.blue,
  primaryColor: const Color(0xFF2196F3),
  scaffoldBackgroundColor: const Color(0xFFF5F5F5),
  appBarTheme: const AppBarTheme(
    backgroundColor: Colors.white,
    foregroundColor: Colors.black,
    elevation: 0.5,
    centerTitle: true,
  ),
),

ThemeData定义了应用的整体视觉风格。primarySwatch是主色调,设为蓝色后会自动生成一系列深浅不同的蓝色供组件使用。primaryColor是主色,使用十六进制颜色值0xFF2196F3,这是Material Design的标准蓝色。scaffoldBackgroundColor设置Scaffold组件的背景色为浅灰色,让页面看起来更加柔和。appBarTheme配置应用栏的样式,backgroundColor设为白色,foregroundColor设为黑色让标题和图标清晰可见。elevation设为0.5添加轻微阴影,centerTitle设为true让标题居中显示。

深色主题配置。

darkTheme: ThemeData(
  brightness: Brightness.dark,
  primarySwatch: Colors.blue,
  primaryColor: const Color(0xFF2196F3),
  scaffoldBackgroundColor: const Color(0xFF121212),
  appBarTheme: const AppBarTheme(
    backgroundColor: Color(0xFF1E1E1E),
    foregroundColor: Colors.white,
    elevation: 0.5,
    centerTitle: true,
  ),
  cardColor: const Color(0xFF1E1E1E),
),
themeMode: ThemeMode.system,

darkTheme定义深色主题,当系统切换到深色模式时会自动应用。brightness设为Brightness.dark告诉Flutter这是深色主题,系统会自动调整文字颜色等。scaffoldBackgroundColor使用0xFF121212这个深灰色而不是纯黑色,这是Material Design推荐的深色背景色,看起来更加柔和不刺眼。appBarTheme的backgroundColor使用稍浅的深灰色0xFF1E1E1E,与背景形成层次感。foregroundColor设为白色确保标题清晰可见。cardColor设置卡片背景色,与应用栏颜色保持一致。themeMode设为ThemeMode.system让应用跟随系统主题自动切换,用户不需要手动设置。

应用初始化的完整流程。

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  await initStorage();
  
  Get.put(GameController());
  Get.put(StatsController());
  Get.put(SettingsController());
  
  runApp(const MyApp());
}

当main函数需要执行异步操作时,必须添加async关键字。WidgetsFlutterBinding.ensureInitialized()确保Flutter的Widget绑定已经初始化完成,这在调用任何Flutter相关的异步方法之前是必需的。如果不调用这个方法,直接使用SharedPreferences等插件可能会报错。initStorage是我们自定义的初始化函数,用于加载本地存储的数据。Get.put是GetX的依赖注入方法,它会创建控制器实例并注册到GetX的容器中。这样在应用的任何地方都可以通过Get.find获取这些控制器。我们注册了游戏控制器、统计控制器和设置控制器,它们会在整个应用生命周期中保持存在。

本地存储初始化。

Future<void> initStorage() async {
  final prefs = await SharedPreferences.getInstance();
  Get.put(StorageService(prefs));
}

initStorage函数负责初始化本地存储服务。SharedPreferences是Flutter常用的本地存储插件,可以存储简单的键值对数据。getInstance是异步方法,返回SharedPreferences的单例实例。我们将获取到的实例传递给StorageService,然后通过Get.put注册到GetX容器中。这样其他控制器就可以通过依赖注入获取StorageService来读写本地数据。这种设计模式让代码更加解耦,便于测试和维护。

错误处理配置。

void main() {
  FlutterError.onError = (FlutterErrorDetails details) {
    FlutterError.presentError(details);
  };
  
  runZonedGuarded(() {
    runApp(const MyApp());
  }, (error, stackTrace) {
    debugPrint('Uncaught error: $error');
  });
}

FlutterError.onError是Flutter框架的错误处理回调,当Widget构建过程中发生错误时会被调用。FlutterErrorDetails包含了错误的详细信息,包括异常对象、堆栈跟踪和出错的Widget信息。presentError方法会将错误信息显示在控制台,方便开发调试。runZonedGuarded创建一个错误处理区域,可以捕获区域内所有未处理的异步错误。第一个参数是要执行的代码,第二个参数是错误处理回调。这两种错误处理机制结合使用,可以捕获应用中几乎所有的错误,防止应用崩溃并方便问题排查。

屏幕方向配置。

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  await SystemChrome.setPreferredOrientations([
    DeviceOrientation.portraitUp,
    DeviceOrientation.portraitDown,
  ]);
  
  runApp(const MyApp());
}

SystemChrome是Flutter提供的系统UI控制类,可以配置状态栏、导航栏和屏幕方向等。setPreferredOrientations方法设置应用支持的屏幕方向,参数是一个方向列表。DeviceOrientation.portraitUp表示正常竖屏,portraitDown表示倒置竖屏。数独游戏的棋盘是正方形的,竖屏布局可以让棋盘和数字键盘都有足够的显示空间。如果允许横屏,棋盘可能会被压缩或者需要滚动才能看到完整界面。这个设置在应用启动时生效,用户无法通过旋转设备来改变方向。

状态栏配置。

void main() {
  SystemChrome.setSystemUIOverlayStyle(
    const SystemUiOverlayStyle(
      statusBarColor: Colors.transparent,
      statusBarIconBrightness: Brightness.dark,
      systemNavigationBarColor: Colors.white,
      systemNavigationBarIconBrightness: Brightness.dark,
    ),
  );
  
  runApp(const MyApp());
}

setSystemUIOverlayStyle方法配置系统UI的样式,包括状态栏和底部导航栏。SystemUiOverlayStyle是一个不可变的配置类,使用const构造函数创建。statusBarColor设为透明可以让应用内容延伸到状态栏区域,实现沉浸式效果。statusBarIconBrightness设为dark表示状态栏图标使用深色,适合浅色背景。systemNavigationBarColor设置底部导航栏的背景色为白色,与应用整体风格一致。systemNavigationBarIconBrightness设为dark让导航栏按钮使用深色,在白色背景上清晰可见。

路由配置。

GetMaterialApp(
  initialRoute: '/',
  getPages: [
    GetPage(name: '/', page: () => const MainPage()),
    GetPage(name: '/game', page: () => const GamePage()),
    GetPage(name: '/stats', page: () => const StatsPage()),
    GetPage(name: '/daily', page: () => const DailyPage()),
    GetPage(name: '/settings', page: () => const SettingsPage()),
  ],
)

GetX的路由系统比Flutter原生路由更加简洁易用。initialRoute设置应用启动时的初始路由,这里设为根路由’/‘。getPages是路由表,定义了所有可导航的页面。每个GetPage包含name和page两个必需参数,name是路由名称,page是返回页面Widget的函数。使用命名路由的好处是可以在应用任何地方通过Get.toNamed(’/game’)来导航,不需要导入目标页面的文件。GetX还支持路由参数、路由中间件、路由转场动画等高级功能。这种集中式的路由配置让页面导航更加清晰可控。

国际化配置。

GetMaterialApp(
  translations: AppTranslations(),
  locale: const Locale('zh', 'CN'),
  fallbackLocale: const Locale('en', 'US'),
)

GetX内置了国际化支持,配置非常简单。translations参数接收一个Translations子类的实例,里面定义了所有的翻译文本。locale设置应用的默认语言,Locale构造函数接收语言代码和国家代码两个参数。fallbackLocale是备用语言,当找不到当前语言的翻译时会使用备用语言。这种设计确保应用在任何情况下都能显示文本,不会出现空白或报错。

翻译类的实现。

class AppTranslations extends Translations {
  
  Map<String, Map<String, String>> get keys => {
    'zh_CN': {
      'app_name': '数独游戏',
      'new_game': '新游戏',
      'settings': '设置',
    },
    'en_US': {
      'app_name': 'Sudoku',
      'new_game': 'New Game',
      'settings': 'Settings',
    },
  };
}

AppTranslations继承自GetX的Translations类,需要重写keys属性。keys是一个嵌套的Map,外层的key是语言代码(格式为语言_国家),内层是翻译键值对。在代码中使用’app_name’.tr就可以获取当前语言对应的翻译文本。如果当前语言是中文,返回’数独游戏’;如果是英文,返回’Sudoku’。这种方式比Flutter原生的国际化方案更加简洁,不需要生成额外的代码文件。添加新语言只需要在keys中增加一个新的语言Map即可。

总结一下主入口与应用配置的关键设计要点。首先是初始化顺序,确保依赖项在使用前已初始化。其次是主题配置,定义一致的视觉风格。然后是错误处理,捕获和处理异常。最后是系统配置,包括屏幕方向、状态栏等。

主入口是应用的基础,良好的配置可以为整个应用提供一致的体验和可靠的运行环境。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

在实际开发中,主入口与应用配置还需要考虑更多的细节。下面我们来探讨一些高级配置和最佳实践。

首先是应用启动优化。Flutter应用的启动时间对用户体验至关重要,我们可以通过并行初始化来优化。

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  await Future.wait([
    _initStorage(),
    _initControllers(),
    _preloadAssets(),
  ]);
  
  runApp(const MyApp());
}

Future.wait是Dart提供的并行执行多个Future的方法,它会等待所有Future完成后才继续执行。与顺序执行相比,并行执行可以显著减少总的初始化时间。假设三个初始化任务各需要100毫秒,顺序执行需要300毫秒,而并行执行只需要100毫秒左右。每个初始化任务都是独立的,没有依赖关系,所以可以同时进行。这种优化在初始化任务较多或者某些任务耗时较长时效果特别明显。

各个初始化函数的实现。

Future<void> _initStorage() async {
  final prefs = await SharedPreferences.getInstance();
  Get.put(StorageService(prefs));
}

Future<void> _initControllers() async {
  Get.put(GameController());
  Get.put(ThemeController());
  Get.put(SettingsController());
}

Future<void> _preloadAssets() async {
  // 预加载常用图片资源
}

_initStorage负责初始化本地存储,获取SharedPreferences实例并注册StorageService。_initControllers注册应用需要的各种控制器,这些控制器会在整个应用生命周期中保持存在。_preloadAssets可以预加载常用的图片资源,避免首次显示时出现闪烁。将初始化逻辑拆分成多个小函数,不仅可以并行执行,还让代码更加清晰易维护。每个函数职责单一,出问题时也容易定位。

启动页面的配置。

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

  
  Widget build(BuildContext context) {
    return ScreenUtilInit(
      designSize: const Size(375, 812),
      minTextAdapt: true,
      builder: (context, child) {
        return GetMaterialApp(
          title: '数独游戏',
          debugShowCheckedModeBanner: false,
          theme: AppTheme.lightTheme,
          darkTheme: AppTheme.darkTheme,
          themeMode: ThemeMode.system,
          home: const SplashPage(),
        );
      },
    );
  }
}

这里我们将home改为SplashPage启动页面,而不是直接显示MainPage。AppTheme.lightTheme和AppTheme.darkTheme是我们抽取出来的主题配置,放在单独的文件中管理更加清晰。SplashPage作为启动页面,可以显示应用Logo和加载动画,给用户一个良好的第一印象。在启动页面中可以完成一些额外的初始化工作,比如检查登录状态、加载用户数据等。这种设计让用户感知到应用正在加载,而不是卡住了。

启动页面的实现。

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

  
  State<SplashPage> createState() => _SplashPageState();
}

class _SplashPageState extends State<SplashPage> {
  
  void initState() {
    super.initState();
    _navigateToMain();
  }

  Future<void> _navigateToMain() async {
    await Future.delayed(const Duration(milliseconds: 1500));
    if (mounted) {
      Get.offAll(() => const MainPage());
    }
  }
}

SplashPage使用StatefulWidget是因为我们需要在initState中启动导航逻辑。_navigateToMain方法使用Future.delayed延迟1.5秒,这个时间可以根据实际需要调整。如果有真正的初始化任务,可以用await等待任务完成而不是固定延迟。mounted检查确保Widget还在树中,避免在页面已销毁后调用setState或导航。Get.offAll会清除整个导航栈并跳转到新页面,这样用户按返回键不会回到启动页面。这是启动页面的标准处理方式。

启动页面的UI构建。


Widget build(BuildContext context) {
  return Scaffold(
    backgroundColor: Colors.blue,
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.grid_on, size: 80.sp, color: Colors.white),
          SizedBox(height: 24.h),
          Text(
            '数独游戏',
            style: TextStyle(
              fontSize: 32.sp,
              fontWeight: FontWeight.bold,
              color: Colors.white,
            ),
          ),
        ],
      ),
    ),
  );
}

启动页面的UI设计要简洁大方。Scaffold的backgroundColor设为蓝色作为品牌色。Center和Column组合实现垂直居中布局。Icon使用grid_on图标代表数独的网格,size使用80.sp确保在不同屏幕上大小合适。SizedBox添加24.h的间距,h后缀表示根据屏幕高度适配。Text显示应用名称,使用32.sp的大字号和粗体让标题醒目。整体配色使用蓝底白字,简洁清爽,符合数独游戏的风格定位。

添加加载指示器。

children: [
  Icon(Icons.grid_on, size: 80.sp, color: Colors.white),
  SizedBox(height: 24.h),
  Text(
    '数独游戏',
    style: TextStyle(
      fontSize: 32.sp,
      fontWeight: FontWeight.bold,
      color: Colors.white,
    ),
  ),
  SizedBox(height: 48.h),
  const CircularProgressIndicator(
    valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
  ),
],

在应用名称下方添加CircularProgressIndicator加载指示器,让用户知道应用正在加载中。SizedBox添加48.h的间距,让指示器与文字保持适当距离。valueColor使用AlwaysStoppedAnimation包装白色,这样指示器的颜色会保持白色不变。如果直接设置color属性,在某些情况下可能不生效。加载指示器是启动页面的标准元素,可以有效减少用户的等待焦虑感。

应用生命周期管理。

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

  
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }
}

要监听应用生命周期,需要将MyApp改为StatefulWidget并混入WidgetsBindingObserver。在initState中调用addObserver注册观察者,在dispose中调用removeObserver取消注册,这是标准的观察者模式用法。WidgetsBinding.instance是Flutter的Widget绑定单例,管理着Widget树的生命周期。通过注册观察者,我们可以在应用进入后台、恢复前台等时机执行相应的逻辑。这对于游戏应用特别重要,可以在切换应用时自动暂停游戏。

生命周期回调处理。


void didChangeAppLifecycleState(AppLifecycleState state) {
  switch (state) {
    case AppLifecycleState.resumed:
      _onAppResumed();
      break;
    case AppLifecycleState.paused:
      _onAppPaused();
      break;
    case AppLifecycleState.inactive:
      _onAppInactive();
      break;
    case AppLifecycleState.detached:
      _onAppDetached();
      break;
    case AppLifecycleState.hidden:
      break;
  }
}

didChangeAppLifecycleState是WidgetsBindingObserver的回调方法,当应用生命周期状态改变时被调用。AppLifecycleState有五种状态:resumed表示应用在前台可见且可交互;paused表示应用在后台不可见;inactive表示应用可见但不可交互,比如来电话时;detached表示应用即将被销毁;hidden是新增的状态,表示应用被其他内容遮挡。使用switch语句分别处理不同状态,调用对应的处理方法。这种设计让代码结构清晰,每种状态的处理逻辑独立。

前台和后台的处理逻辑。

void _onAppResumed() {
  final gameController = Get.find<GameController>();
  if (!gameController.isPaused && !gameController.isComplete) {
    gameController.resumeTimer();
  }
}

void _onAppPaused() {
  final gameController = Get.find<GameController>();
  gameController.pauseGame();
  gameController.saveGameState();
}

_onAppResumed在应用恢复前台时调用。通过Get.find获取GameController实例,检查游戏是否处于暂停或完成状态。如果游戏正在进行中,调用resumeTimer恢复计时器。_onAppPaused在应用进入后台时调用,首先暂停游戏,然后保存游戏状态到本地存储。这样即使应用被系统杀死,用户下次打开时也能恢复之前的游戏进度。这种处理确保用户不会因为接电话或切换应用而丢失游戏进度,提升了用户体验。

内存警告处理。


void didHaveMemoryPressure() {
  imageCache.clear();
  imageCache.clearLiveImages();
}

didHaveMemoryPressure是WidgetsBindingObserver的另一个回调方法,当系统内存不足时被调用。imageCache是Flutter的全局图片缓存,clear方法清除所有缓存的图片,clearLiveImages清除正在使用的图片缓存。在内存紧张时释放这些资源可以避免应用被系统杀死。对于数独游戏来说,图片资源不多,这个处理主要是预防性的。如果应用有大量图片,这个处理就非常重要了。

字体配置。

theme: ThemeData(
  fontFamily: 'Roboto',
  textTheme: TextTheme(
    displayLarge: TextStyle(fontSize: 32.sp, fontWeight: FontWeight.bold),
    displayMedium: TextStyle(fontSize: 28.sp, fontWeight: FontWeight.bold),
    displaySmall: TextStyle(fontSize: 24.sp, fontWeight: FontWeight.bold),
    headlineMedium: TextStyle(fontSize: 20.sp, fontWeight: FontWeight.w600),
    titleLarge: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.w600),
  ),
)

fontFamily设置应用的默认字体为Roboto,这是Android的默认字体,在各平台上都有良好的显示效果。textTheme定义了不同级别的文字样式,遵循Material Design的排版规范。displayLarge用于最大的标题,32.sp配合粗体非常醒目。displayMedium和displaySmall依次递减,用于次级标题。headlineMedium使用w600中粗字重,适合小标题。titleLarge用于列表项标题等场景。使用sp单位确保字体大小会根据屏幕适配。在代码中通过Theme.of(context).textTheme.displayLarge访问这些样式。

正文字体样式。

textTheme: TextTheme(
  titleMedium: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w500),
  bodyLarge: TextStyle(fontSize: 16.sp),
  bodyMedium: TextStyle(fontSize: 14.sp),
  bodySmall: TextStyle(fontSize: 12.sp),
  labelLarge: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w500),
),

titleMedium用于中等大小的标题,16.sp配合w500字重。bodyLarge是大号正文,16.sp是常用的正文大小。bodyMedium是标准正文,14.sp适合大段文字阅读。bodySmall是小号正文,12.sp用于辅助说明文字。labelLarge用于按钮文字等标签,14.sp配合w500字重让按钮文字清晰可读。统一的字体配置确保应用中的文字样式一致,避免每个Text组件单独设置样式导致的混乱。修改主题配置就能影响整个应用的文字样式。

ElevatedButton样式配置。

elevatedButtonTheme: ElevatedButtonThemeData(
  style: ElevatedButton.styleFrom(
    backgroundColor: Colors.blue,
    foregroundColor: Colors.white,
    padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 12.h),
    shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(8.r),
    ),
    textStyle: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w500),
  ),
),

ElevatedButtonThemeData定义凸起按钮的默认样式。styleFrom是一个便捷方法,可以快速创建ButtonStyle。backgroundColor设置按钮背景色为蓝色,foregroundColor设置文字和图标颜色为白色。padding使用symmetric方法设置水平24.w、垂直12.h的内边距,w和h后缀分别表示宽度和高度适配。shape使用RoundedRectangleBorder设置圆角,8.r的圆角大小适中。textStyle设置按钮文字样式。这样应用中所有ElevatedButton都会使用这个样式,保持一致的外观。

OutlinedButton样式配置。

outlinedButtonTheme: OutlinedButtonThemeData(
  style: OutlinedButton.styleFrom(
    foregroundColor: Colors.blue,
    side: const BorderSide(color: Colors.blue),
    padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 12.h),
    shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(8.r),
    ),
    textStyle: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w500),
  ),
),

OutlinedButton是带边框的按钮,通常用于次要操作。foregroundColor设置文字颜色为蓝色,与ElevatedButton的背景色呼应。side设置边框样式,使用蓝色1像素边框。padding和shape与ElevatedButton保持一致,确保两种按钮大小相同,可以并排放置。textStyle也保持一致。OutlinedButton没有背景色,视觉上比ElevatedButton轻量,适合放在ElevatedButton旁边作为取消按钮使用。统一的样式让按钮组合看起来协调美观。

TextButton样式配置。

textButtonTheme: TextButtonThemeData(
  style: TextButton.styleFrom(
    foregroundColor: Colors.blue,
    textStyle: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w500),
  ),
),

TextButton是最轻量的按钮类型,没有背景也没有边框,只有文字。foregroundColor设置文字颜色为蓝色,让按钮可点击的特性明显。textStyle与其他按钮保持一致。TextButton通常用于对话框中的操作按钮,或者作为链接使用。因为没有背景和边框,TextButton不需要设置padding和shape。三种按钮类型各有用途:ElevatedButton用于主要操作,OutlinedButton用于次要操作,TextButton用于最轻量的操作。统一配置后,开发时只需要选择合适的按钮类型,不需要关心样式细节。

输入框样式配置。

inputDecorationTheme: InputDecorationTheme(
  filled: true,
  fillColor: Colors.grey.shade100,
  border: OutlineInputBorder(
    borderRadius: BorderRadius.circular(8.r),
    borderSide: BorderSide.none,
  ),
  focusedBorder: OutlineInputBorder(
    borderRadius: BorderRadius.circular(8.r),
    borderSide: const BorderSide(color: Colors.blue, width: 2),
  ),
  contentPadding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
),

InputDecorationTheme定义TextField和TextFormField的默认装饰样式。filled设为true启用填充背景,fillColor使用浅灰色让输入框与页面背景区分开。border定义默认边框,使用OutlineInputBorder设置8.r的圆角,borderSide设为none隐藏边框线。focusedBorder定义获得焦点时的边框,显示2像素宽的蓝色边框,让用户知道当前正在输入哪个框。contentPadding设置内容区域的内边距。这种设计让输入框看起来简洁现代,获得焦点时有明显的视觉反馈。

错误状态的输入框样式。

errorBorder: OutlineInputBorder(
  borderRadius: BorderRadius.circular(8.r),
  borderSide: const BorderSide(color: Colors.red, width: 2),
),
focusedErrorBorder: OutlineInputBorder(
  borderRadius: BorderRadius.circular(8.r),
  borderSide: const BorderSide(color: Colors.red, width: 2),
),
errorStyle: TextStyle(fontSize: 12.sp, color: Colors.red),

errorBorder定义验证失败时的边框样式,使用红色边框提示用户输入有误。focusedErrorBorder定义错误状态下获得焦点时的边框,保持红色让错误提示持续可见。errorStyle定义错误提示文字的样式,12.sp的小字号不会太突兀,红色与边框颜色一致。输入验证是表单的重要功能,清晰的错误提示可以帮助用户快速修正输入。虽然数独游戏中输入框使用不多,但统一配置这些样式可以保证需要时有一致的体验。

对话框样式配置。

dialogTheme: DialogTheme(
  backgroundColor: Colors.white,
  shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(16.r),
  ),
  titleTextStyle: TextStyle(
    fontSize: 20.sp,
    fontWeight: FontWeight.bold,
    color: Colors.black,
  ),
  contentTextStyle: TextStyle(
    fontSize: 16.sp,
    color: Colors.grey.shade700,
  ),
),

DialogTheme定义AlertDialog和SimpleDialog的默认样式。backgroundColor设为白色,在浅色和深色背景上都清晰可见。shape使用16.r的大圆角,让对话框看起来更加柔和友好。titleTextStyle定义标题样式,20.sp的大字号配合粗体让标题醒目。contentTextStyle定义内容文字样式,16.sp的正常字号,使用深灰色而不是纯黑色,阅读起来更舒适。数独游戏中会用到新游戏确认、胜利提示等对话框,统一的样式让这些对话框看起来协调一致。

底部弹出框样式配置。

bottomSheetTheme: BottomSheetThemeData(
  backgroundColor: Colors.white,
  shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.vertical(top: Radius.circular(16.r)),
  ),
  modalBackgroundColor: Colors.white,
  modalElevation: 8,
),

BottomSheetThemeData定义底部弹出框的样式。backgroundColor设置普通BottomSheet的背景色。shape使用BorderRadius.vertical只设置顶部圆角,底部保持直角贴合屏幕边缘。16.r的圆角与对话框保持一致。modalBackgroundColor设置模态BottomSheet的背景色,模态弹出框会有半透明遮罩层。modalElevation设置阴影高度为8,让弹出框有明显的层次感。底部弹出框常用于显示选项列表、设置面板等,数独游戏中可以用来选择难度级别或显示游戏设置。

Snackbar样式配置。

snackBarTheme: SnackBarThemeData(
  backgroundColor: Colors.grey.shade800,
  contentTextStyle: TextStyle(fontSize: 14.sp, color: Colors.white),
  shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(8.r),
  ),
  behavior: SnackBarBehavior.floating,
),

SnackBarThemeData定义Snackbar提示条的样式。backgroundColor使用深灰色,在浅色背景上醒目但不刺眼。contentTextStyle设置提示文字为14.sp白色,确保清晰可读。shape设置8.r的圆角,与按钮等组件保持一致。behavior设为floating让Snackbar悬浮显示,与底部保持一定距离,看起来更加现代。默认的fixed行为会让Snackbar贴在底部,可能被底部导航栏遮挡。Snackbar用于显示简短的操作反馈,比如"游戏已保存"、"已撤销上一步"等提示。

进度指示器样式配置。

progressIndicatorTheme: ProgressIndicatorThemeData(
  color: Colors.blue,
  linearTrackColor: Colors.grey.shade200,
  circularTrackColor: Colors.grey.shade200,
),

ProgressIndicatorThemeData定义进度指示器的颜色。color是进度条的颜色,使用蓝色与应用主色调一致。linearTrackColor是线性进度条的轨道颜色,使用浅灰色作为背景。circularTrackColor是圆形进度指示器的轨道颜色,同样使用浅灰色。轨道颜色比进度颜色浅,可以清晰地显示进度比例。数独游戏中可能在加载谜题、保存数据时显示进度指示器,统一的颜色配置让这些指示器与应用风格协调。

滑块样式配置。

sliderTheme: SliderThemeData(
  activeTrackColor: Colors.blue,
  inactiveTrackColor: Colors.grey.shade300,
  thumbColor: Colors.blue,
  overlayColor: Colors.blue.withOpacity(0.2),
  valueIndicatorColor: Colors.blue,
  valueIndicatorTextStyle: TextStyle(fontSize: 14.sp, color: Colors.white),
),

SliderThemeData定义Slider滑块的样式。activeTrackColor是已选择部分的轨道颜色,使用蓝色表示当前值。inactiveTrackColor是未选择部分的轨道颜色,使用浅灰色。thumbColor是滑块圆点的颜色,与activeTrackColor保持一致。overlayColor是拖动时滑块周围的光晕颜色,使用20%透明度的蓝色,提供触摸反馈但不会太突兀。valueIndicatorColor是数值指示器的背景色,valueIndicatorTextStyle定义指示器中数字的样式。滑块可以用于调节音量、难度等连续值的设置。

开关样式配置。

switchTheme: SwitchThemeData(
  thumbColor: WidgetStateProperty.resolveWith((states) {
    if (states.contains(WidgetState.selected)) {
      return Colors.blue;
    }
    return Colors.grey;
  }),
  trackColor: WidgetStateProperty.resolveWith((states) {
    if (states.contains(WidgetState.selected)) {
      return Colors.blue.withOpacity(0.5);
    }
    return Colors.grey.withOpacity(0.5);
  }),
),

SwitchThemeData使用WidgetStateProperty来定义不同状态下的颜色。thumbColor是开关圆点的颜色,resolveWith方法根据当前状态返回不同的颜色。states.contains(WidgetState.selected)检查开关是否处于打开状态,打开时返回蓝色,关闭时返回灰色。trackColor是开关轨道的颜色,使用50%透明度让轨道颜色比圆点浅。这种状态响应式的配置让开关在不同状态下有明显的视觉区分。数独游戏的设置页面会用到开关来控制音效、震动等选项的开关状态。

复选框样式配置。

checkboxTheme: CheckboxThemeData(
  fillColor: WidgetStateProperty.resolveWith((states) {
    if (states.contains(WidgetState.selected)) {
      return Colors.blue;
    }
    return Colors.transparent;
  }),
  checkColor: WidgetStateProperty.all(Colors.white),
  shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(4.r),
  ),
),

CheckboxThemeData定义复选框的样式。fillColor是复选框的填充颜色,选中时使用蓝色填充,未选中时透明只显示边框。checkColor是勾选标记的颜色,使用WidgetStateProperty.all表示所有状态下都是白色。shape设置复选框的形状,4.r的小圆角让复选框看起来更加现代,而不是传统的直角方框。复选框可以用于多选设置,比如选择要显示的统计项目。统一的样式配置让所有复选框外观一致。

单选按钮样式配置。

radioTheme: RadioThemeData(
  fillColor: WidgetStateProperty.resolveWith((states) {
    if (states.contains(WidgetState.selected)) {
      return Colors.blue;
    }
    return Colors.grey;
  }),
),

RadioThemeData定义单选按钮的样式。fillColor是单选按钮的填充颜色,选中时使用蓝色,未选中时使用灰色。单选按钮的形状是固定的圆形,不能自定义。单选按钮用于在多个选项中选择一个,比如选择游戏难度级别。与复选框不同,单选按钮组中只能有一个被选中。统一的蓝色选中状态让单选按钮与其他组件风格一致。

分割线样式配置。

dividerTheme: DividerThemeData(
  color: Colors.grey.shade200,
  thickness: 1,
  space: 1,
),

DividerThemeData定义Divider分割线的样式。color设置分割线颜色为浅灰色,不会太突兀但能起到分隔作用。thickness设置线条粗细为1像素,这是最常用的分割线粗细。space设置分割线占用的总空间,包括线条本身和上下的空白。设为1表示分割线紧凑显示,不占用额外空间。分割线用于分隔列表项或内容区块,数独游戏的设置页面会用到分割线来分隔不同的设置组。

列表瓦片样式配置。

listTileTheme: ListTileThemeData(
  contentPadding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
  minLeadingWidth: 24.w,
  iconColor: Colors.grey.shade600,
  textColor: Colors.black87,
),

ListTileThemeData定义ListTile列表项的样式。contentPadding设置内容区域的内边距,水平16.w、垂直8.h是常用的列表项间距。minLeadingWidth设置leading图标的最小宽度,确保多个列表项的图标对齐。iconColor设置图标颜色为深灰色,不会太突兀。textColor设置文字颜色为87%透明度的黑色,这是Material Design推荐的正文颜色。ListTile是构建设置页面的常用组件,统一的样式让设置列表看起来整齐美观。

通过以上这些主题配置,我们可以确保应用中所有组件的外观一致,同时也方便后续的样式调整。只需要修改主题配置,就可以影响整个应用的外观,不需要逐个修改每个组件的样式。

主入口与应用配置是Flutter应用开发的基础。良好的配置可以提升应用的性能、用户体验和可维护性。希望本篇文章能够帮助你在Flutter for OpenHarmony项目中建立坚实的应用基础。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐