为了简化大家的使用,虽然 flutter 推荐所有的 widget 都有自己来进行搭建,但是在大框架上面,flutter 提供了 Material 和 Cupertino 两种主题风格的 Widgets 集合,大家可以在这两种风格的继承上进行个性化定制和开发。

这两种风格翻译成中文就是:材料和库比蒂诺?什么鬼....我们还是使用默认的英文名来称呼它们吧。

本文我们将会深入讲解 Material 主题的基础-MaterialApp。

MaterialApp 初探

如果你使用最新的 android Studio 创建一个 flutter 项目的话,android Studio 会自动为你创建一个基于 flutter 的应用程序。

我们来看下自动创建的 main.dart 文件:

  Widget build(BuildContext context) {    return MaterialApp(      title: 'Flutter Demo',      theme: ThemeData(        primarySwatch: Colors.blue,      ),      home: const MyHomePage(title: 'Flutter Demo Home Page'),    )  }

这个 build 方法返回的 widget 就是这个 flutter 应用程序的根 Widget。可以看到,默认情况下是返回一个 MaterialApp。

在上面的样例代码中,为 MaterialApp 设置了 tile,theme 和 home 属性。

title 是 MaterialApp 的标题,theme 是整个 MaterialApp 的主题,home 表示的是 app 进入时候应该展示的主页面。

默认情况下 MyHomePage 会返回一个类似下面代码的 Scaffold:

    return Scaffold(      appBar: AppBar(        title: Text(widget.title),      ),      body: Center(        child: Column(          mainAxisAlignment: MainAxisAlignment.center,          children: const <Widget>[            Text(              'home page',            ),          ],        ),      ),    );

这样我们可以得到常见的 MaterialApp 界面:

MaterialApp 详解

有了上面的框架,我们就可以在 home 中构建自己的组件,从而开始 flutter 的愉快 app 之旅。

那么 MaterialApp 还有其他的什么功能吗?它的底层原理是怎么样的呢?一起来看看吧。

首先是 MaterialApp 的定义:

class MaterialApp extends StatefulWidget

可以看到 MaterialApp 是一个 StatefulWidget,表示 MaterialApp 可能会根据用户的输入不同,重新 build 子组件,因为通常来说 MaterialApp 表示一个应用程序总体,所以它需要考虑很多复杂的交互情况,使用 StatefulWidget 是很合理的。

MaterialApp 中的 theme

接下来我们看下 MaterialApp 可以配置的主题。

MaterialApp 中有下面几种 theme:

  final ThemeData? theme;
  final ThemeData? darkTheme;
  final ThemeData? highContrastTheme;
  final ThemeData? highContrastDarkTheme;
  final ThemeMode? themeMode;

ThemeData 用来定义 widget 的主题样式,ThemeData 包括 colorScheme 和 textTheme 两部分。

为了简单起见,flutter 提供了两个简洁的 Theme 创建方式,分别是 ThemeData.light 和 ThemeData.dark。 当然你也可以使用 ThemeData.from 从 ColorScheme 中创建新的主题。

那么问题来了,一个 app 为什么有这么多 ThemeData 呢?

默认情况下 theme 就是 app 将会使用的 theme,但是考虑到现在流行的 theme 切换的情况,所以也提供了 darkTheme 这个选项。

如果 theme 和 darkTheme 都设置的话,那么将会根据 themeMode 来决定具体到底使用哪个主题。

注意,默认的主题是 ThemeData.light()

highContrastTheme 和 highContrastDarkTheme 的存在也是因为在某些系统中需要 high contrast 和 dark 的主题版本,这些 ThemeData 是可选的。

themeMode 这个字段,如果取 ThemeMode.system,那么默认会使用系统的主题配置,具体而言,是通过调用 MediaQuery.platformBrightnessOf 来查询系统到底是 Brightness.light 还是 Brightness.dark.

虽然默认是 ThemeMode.system,但是你也可以指定其为 ThemeMode.light 或者 ThemeMode.dark.

MaterialApp 中的 routes

和 web 页面的首页一样,在 MaterialApp 中,我们也需要定义一些页面跳转的路由信息。

在讲解 routes 之前,我们需要明白 flutter 中有两个和路由相关的定义,分别是 routes 和 Navigator。

其中 routes 是路由的定义,它表示的是不同路径对应的 widget 地址,比如下面的 routers 的定义:

routes: <String, WidgetBuilder> {       '/a': (BuildContext context) => MyPage(title: 'page A'),       '/b': (BuildContext context) => MyPage(title: 'page B'),       '/c': (BuildContext context) => MyPage(title: 'page C'),     },

routers 的类型是 Map<String, WidgetBuilder>,对应的 key 就是路由地址,value 就是路由地址对应的 WidgetBuilder。

Navigator 是一个 Widget,用来对 routers 进行管理。

Navigator 可以通过是用 Navigator.pages、Navigator.push 或者 Navigator.pop 来对 routers 进行管理。举个例子:

push:

 Navigator.push(context, MaterialPageRoute<void>(   builder: (BuildContext context) {     return Scaffold(       appBar: AppBar(title: Text('My Page')),       body: Center(         child: TextButton(           child: Text('POP'),           onPressed: () {             Navigator.pop(context);           },         ),       ),     );   }, ));

pop:

Navigator.pop(context);

对于 MaterialApp 来说,如果是/ route,那么将会查找 MaterialApp 中的 home 属性对应的 Widget,如果 home 对应的 Widget 不存在,那么会使用 routers 里面配置的。

如果上面的信息都没有,则说明需要创建 router,则会调用 onGenerateRoute 方法来创建新的 routers。

所以说 onGenerateRoute 是用来处理 home 和 routers 方法中没有定义的路由。你也可以将其看做是一种创建动态路由的方法。

最后,如果所有的 route 规则都不匹配的话,则会调用 onUnknownRoute。

如果 home,routes,onGenerateRoute,onUnknownRoute 全都为空,并且 builder 不为空的话,那么将不会创建任何 Navigator。

MaterialApp 中的 locale

local 是什么呢?local 在国际化中表示的是一种语言,通过使用 Local,你不用再程序中硬编码要展示的文本,从而做到 APP 的国际化支持。

dart 中的 local 可以这样使用:

const Locale swissFrench = Locale('fr', 'CH');const Locale canadianFrench = Locale('fr', 'CA');

在 MaterialApp 中,需要同时配置 localizationsDelegates 和 supportedLocales:

MaterialApp(  localizationsDelegates: [    // ... app-specific localization delegate[s] here    GlobalMaterialLocalizations.delegate,    GlobalWidgetsLocalizations.delegate,  ],  supportedLocales: [    const Locale('en', 'US'), // English    const Locale('he', 'IL'), // Hebrew    // ... other locales the app supports  ],  // ...)

supportedLocales 中配置的是支持的 locales,localizationsDelegates 用来生成 WidgetsLocalizations 和 MaterialLocalizations.

有关 locale 的具体使用,可以关注后续的文章。

MaterialApp 和 WidgetsApp

MaterialApp 是一个 StatefulWidget,那么和它绑定的 State 叫做:_MaterialAppState, _MaterialAppStatez 中有个 build 方法,返回的 widget 到底是什么呢?

    return ScrollConfiguration(      behavior: widget.scrollBehavior ?? const MaterialScrollBehavior(),      child: HeroControllerScope(        controller: _heroController,        child: result,      ),    );

可以看到,最终返回的是一个 ScrollConfiguration,它的本质是返回一个包装在 HeroControllerScope 中的 result。

什么是 Hero 呢?Hero 在 flutter 中是一个组件,用来表示在路由切换的过程中,可以从老的路由 fly 到新的路由中。这样的一个飞行的动画,也叫做 Hero 动画。

而这个 result 其实是一个 WidgetsApp。

WidgetsApp 就是 MaterialApp 底层的 Widget,它包装了应用程序通常需要的许多小部件。

WidgetsApp 的一个主要功能就是将系统后退按钮绑定到弹出导航器或退出应用程序。

从实现上讲,MaterialApp 和 CupertinoApp 都使用它来实现应用程序的基本功能。

总结

MaterialApp 作为 Material 风格的第一入口,希望大家能够熟练掌握它的用法。

 

Logo

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

更多推荐