Flutter学习之UI入门篇

1,278 阅读17分钟

前言

在上篇中学习了 Dart 语言的基础知识,接着我们来要来学习下 Flutter 的 UI 是如何布局的。在我们搭建环境的时候,我们创建了一个入门的 demo ,下面我们从这个 demo 学起。

入门

下面把 demo 生成的 main.dart 简化来学习:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Welcome to Flutter',
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Welcome to Flutter'),
        ),
        body: const Center(
          child: Text('Hello World'),
        ),
      ),
    );
  }
}

和 Android 里面的 xml 布局不同, Flutter 采用的是声明构建 UI 方式来布局页面的。Android 里面所有控件都是 View,而这里所有控件都是 Widget,只不过 View 和 Widget 有一些不同,View 有自己的生命周期,可以通过 invalidate 方法来进行重绘,也可以持有 View 做些 View 相关的操作,而 Widget 不同,它就是一个申明式对象,通过多个 Widget 来构建成一个 Widget 树,它的状态不可变,一旦状态改变,生命周期即结束了,所以它比 View 更轻量。

理解了 Widget 概念后,再回到上面程序里面就好理解了,程序里面的 MyApp、MaterialApp、Scaffold、AppBar、Center、Text 都是 Widget。继续跟着这些 Widget 的源码会发现,她们又分为 StatelessWidget、StatefulWidget、RenderObjectWidget。比如 MyApp,Text 是 StatelessWidget,MaterialApp,Scaffold,AppBar 是 StatefulWidget,Center 是 RenderObjectWidget,这三个 Widget 有什么区别呢?

StatelessWidget & StatefulWidget & RenderObjectWidget

为什么 Flutter 要归类这几种 Widget 呢?在上面有提到过,Widget 状态改变生命周期就结束,那我们怎么持有 Widget 来更新 Widget 操作呢,我们没法像 Android 里面的 View 一样直接调用 View 方法来操作,所以需要一个状态管理的 Widget 来更新操作,这样 StatefulWidget 就出现了,与之对应的就是 StatelessWidget,它包括一些不跟着交互而改变状态的 Widget。StatefulWidget 和 StatelessWidget 的区别就是 StatefulWidget 有一个跨帧存储和恢复状态数据的 State 对象。也就是 Widget 跟着交互会改变状态的用 StatefulWidget,不会改变的用 StatelessWidget。而 RenderObjectWidget 仅仅是渲染对象,它不包括尺寸的属性,比如这里的 Center,还有 Row、Column、Flex 等等也都是。那么怎么使用它们呢?

  • StatelessWidget
import 'package:flutter/material.dart';

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

class NewsApp extends StatelessWidget {
  const NewsApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const Text('News App');
  }
}

跑起来,会发现报错了

image.png

也就是 在跟布局里面的 build 返回的 Widget 必须是一个带方向性的部件,比如 MaterialApp 或者 WidgetsApp,它两有啥区别呢,这个后面再来看。这里先用 MaterialApp :

class NewsApp extends StatelessWidget {
  const NewsApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "News App",
      theme: ThemeData(
        primaryColor: Colors.white,
      ),
      home: const Text('home page'),
    );
  }
}

跑起来虽然页面很丑,但 StatelessWidget 的使用能提现出来。注意这里只是根布局约束了必须是返回 MaterialApp 或者 WidgetsApp。其他地方 StatelessWidget 的 build 可以返回任何 Widget。比如上面的 home 我们配置一个 StatelessWidget

home: const MyHome(),
class MyHome extends StatelessWidget {
  const MyHome({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const Text('home page');
  }
}

这样也是可以的。跑起来效果这里就不展示了,挺简单的

  • StatefulWidget

接着来看看 StatefulWidget 的使用

home: const _MyHome(title: 'home title'),
class _MyHome extends StatefulWidget {
  const _MyHome({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<StatefulWidget> createState() {
    return _MyHomeState();
  }
}

class _MyHomeState extends State<_MyHome>{
  @override
  Widget build(BuildContext context) {
    return Text(widget.title);
  }
}

StatefulWidget 需要结合 State 来使用,它增加了状态的管理,比如这里的 title 属性。

  • RenderObjectWidget

关于 RenderObjectWidget 相关的,我们直接使用相关 Widget 就好了,比如使用 Center

class _MyHomeState extends State<_MyHome>{
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(widget.title),
    );
  }
}

Widget

学完了上面的 StatelessWidget、StatefulWidget 和 RenderObjectWidget 之后,也算入门了,接下来的就是使用各种 Widget 以及学习它们的属性了

  1. MaterialApp、CupertinoApp、WidgetsApp 使用

在上面我们提到,App 的根 Widget 必须是这三个其中之一,那么它们有啥区别呢?当我们想要使用 md 风格的 App 时候,我们使用 MaterialApp,当我们想用 IOS 风格的 App 则使用 CupertinoApp,如果要自定义主题那我们使用 WidgetsApp。那么它们怎么使用呢?在上面我们使用了 MaterialApp ,它结合 Scaffold 和 AppBar 来使用。上面 _MyHomeState 改成下面:

class _MyHomeState extends State<_MyHome>{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title),),
      body: Center(
        child: Text(widget.title),
      ),
    );
  }
}

image.png

  • CupertinoApp

IOS 风格的用法,它包括按钮,选择器等等都是遵循 IOS 的风格

class NewsApp extends StatelessWidget {
  const NewsApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const CupertinoApp(
      title: "News App",
      home: CupertinoStoreHomePage(),
    );
  }
}

class CupertinoStoreHomePage extends StatelessWidget {
  const CupertinoStoreHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(
        middle: Text('Cupertino Store'),
      ),
      child: Container(),
    );
  }
}

image.png

  • WidgetsApp

WidgetsApp 自定义自己的风格,它有一些比 MaterialApp 和 CupertinoApp 多了一些属性,比如:

class NewsApp extends StatelessWidget {
  const NewsApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return WidgetsApp(
      //应用文本默认样式
      textStyle: const TextStyle(
        color: Color(0x00000000),
        fontSize: 18,
        fontWeight: FontWeight.normal,
      ),
      debugShowWidgetInspector: true,
      color: Colors.blue,
      //选择按钮生成器
      inspectorSelectButtonBuilder: (BuildContext context, VoidCallback onPressed) {
        return FloatingActionButton(
          child: const Icon(Icons.search),
          onPressed: onPressed,
          mini: true,
        );
      },
       // (生成路由的回调函数,当导航的命名路由的时候,会使用这个来生成界面),如果在routes中没有找到路由,则使用onGenerateRoute回调建立界面
      onGenerateRoute: (RouteSettings settings) {
        return MaterialPageRoute<void>(
          settings: settings,
          builder: (BuildContext context) =>
              Scaffold(body: Center(child: Text(settings.toString()))),
        );
      }
    );
  }
}

image.png

  1. MaterialApp 和 CupertinoApp 属性

这两个需要设置的属性是一致的:

  • navigatorKey

全局导航键,在整个应用程序中唯一的标识元素,生命周期跟着整个应用。有全局键的 Widget 要自己维护子 Widget 树,当 Widget 移动它一个新位置的时候,那么子 Widget 也要移动到对应的位置,同时原来的位置进行删除。所以使用全局键是很耗性能的,它会让所有依赖的子 Widget 都进行重建。非必要情况不使用它,一般建议我们用 Key,ValueKey,ObjectKey,UniqueKey。如果指定了 navigatorKey,那么这个 Widget 将不再需要通过 Navigator.of 来获取了,直接通过 GlobalKey.currentState 来获取了。使用如下

navigatorKey: GlobalKey<AppNavigatorState>()
class AppNavigatorState extends NavigatorState {
  @override
  void initState() {
    super.initState();
  }
  
}

继承 NavigatorState,通过重写方法的方式维护自己的 Navigator 状态。

  • scaffoldMessengerKey

也是一个全局键,为创建 ScaffoldMessenger 使用的 key,如果我们指定了 scaffoldMessengerKey,那么这个 Widget 将不再需要通过 ScaffoldMessenger.of 来获取了,直接通过 GlobalKey.currentState 来获取了,使用类似 navigatorKey,它状态对应 ScaffoldMessengerState。

scaffoldMessengerKey: GlobalKey<AppScaffoldMessengerState>(),
class AppScaffoldMessengerState extends ScaffoldMessengerState {
  @override
  void initState() {
    super.initState();
  }

}
  • home

app 页面展示内容,也就是我们页面布局开始的地方,使用方法看页面具体布局,返回 Widget 就行了,如上面提到过的:

home: const _MyHome(title: 'home title'),
class _MyHome extends StatefulWidget {
  const _MyHome({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<StatefulWidget> createState() {
    return _MyHomeState();
  }
}
class _MyHomeState extends State<_MyHome> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: GestureDetector(
          onTap: (){Navigator.of(context).pushNamed(Details.routeName);},
          child: Text(widget.title),
        ),
      ),
    );
  }
}
  • routes

App 页面路由名称映射集合,当我们跳转的时候使用我们映射的名称就好了,方面页面修改了,修改一个地方就好了,使用如下:

routes:{
        Details.routeName: (context) => const Details(),
      }
class Details extends StatefulWidget {
  static String routeName = "/details";

  @override
  _DetailsState createState() => new _DetailsState();
}

class _DetailsState extends State<Details> {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("详情"),
      ),
      body: Center(
        child: Container(
          color: Colors.blueAccent,
          child: Text(
            '第二页',
            style: TextStyle(fontSize: 32),
          ),
        ),
      ),
    );
  }
}

那么我们跳转就可以这样,在 home 使用中有使用到

Navigator.of(context).pushNamed(Details.routeName);
  • initialRoute

启用应用后需要自动跳转的第二个页面的路由,使用就是指定一个页面路由:

initialRoute: Details.routeName,
routes: {
  Details.routeName: (context) => const Details(),
});
  • onGenerateRoute

生成路由配置,当跳转的页面在 routes 属性未配置或者找不到的时候,则会调用该方法创建页面,反之,则不调用。需要配置一个方法:

typedef RouteFactory = Route<dynamic> Function(RouteSettings settings);

所以使用如下:

onGenerateRoute: (RouteSettings setting) {
        return PageRouteBuilder(
          pageBuilder: (BuildContext context, Animation animation,
              Animation secondaryAnimation) {
            //生成的页面
            return const Details();
          },
        );
      },
  • onGenerateInitialRoutes

生成启动应用时的路由列表,结合 initialRouteName 一起使用,根据 initialRouteName 的值生成不同的路由列表。它有个默认值 Navigator.defaultGenerateInitialRoutes。

  • onUnknownRoute

优先级最低,当跳转的页面在 routes 属性未配置或者找不到并且 onGenerateRoute 返回 null 的时候,则会调用该方法创建页面,反之,则不调用。使用方法和 onGenerateRoute 属性一样,通常这里用来做一些类似 404 页面。

onUnknownRoute: (RouteSettings setting) {
        //当 onGenerateRoute 返回 null 的时候调用,通常处理一些未找到的页面
        return new PageRouteBuilder(
          pageBuilder: (BuildContext context, Animation animation,
              Animation secondaryAnimation) {
            //这里为返回的Widget
            return Text('404');
          },
        );
      },
  • navigatorObservers

路由监听器,监听跳转页面的时候的过程,使用如下:

navigatorObservers:<NavigatorObserver>[
        new NavigatorObserver(),
      ],
class HomeNavObserver extends NavigatorObserver{
  @override
  void didPush(Route route, Route previousRoute) {
    // TODO: implement didPush
    super.didPush(route, previousRoute);
  }
  @override
  void didPop(Route route, Route previousRoute) {
    // TODO: implement didPop
    super.didPop(route, previousRoute);
  }
  @override
  void didRemove(Route route, Route previousRoute) {
    // TODO: implement didRemove
    super.didRemove(route, previousRoute);
  }
  @override
  void didReplace({Route newRoute, Route oldRoute}) {
    // TODO: implement didReplace
    super.didReplace(newRoute, oldRoute);
  }
  @override
  void didStartUserGesture(Route route, Route previousRoute) {
    // TODO: implement didStartUserGesture
    super.didStartUserGesture(route, previousRoute);
  }
  @override
  void didStopUserGesture() {
    // TODO: implement didStopUserGesture
    super.didStopUserGesture();
  }
}
  • builder

加载页面前做一些隐性设置,如加载处理,国际化语言设置,字体大小设置,横竖屏方向设置等等,对应的方法为

typedef TransitionBuilder = Widget Function(BuildContext context, Widget child);

如加载页面前做个加载框

 builder: (BuildContext context, Widget child) {
        return Stack(
          alignment: Alignment.center,
          children: <Widget>[
            child,
            const CircularProgressIndicator(
              valueColor: AlwaysStoppedAnimation(Colors.red),
            ),
          ],
        );
      },

执行效果为:

image.png

  • title

任务管理器的标题。

title:"任务管理器的标题",

image.png

  • onGenerateTitle

任务管理器的标题 。 和 title 属性一样,不同的是可以通过 context 来获取本地资源。设置的优先级高于 title 。

onGenerateTitle:(BuildContext context){
          return  MaterialLocalizations.of(context)....;
      },
  • color

设置 App 主题色,比 theme 的 primaryColor 优先级更高,源码如下

final Color materialColor = widget.color ?? widget.theme?.primaryColor ?? Colors.blue;

使用颜色值就好了

color: Colors.white,
  • theme

设置应用的主题

theme: ThemeData(
        //设置页面背景颜色白天黑夜模式
        brightness: Brightness.light,
        //MaterialColor 多种颜色混合的主题色
        primarySwatch: Colors.cyan,
        //Color,主题色
        primaryColor: Colors.cyan,
        //设置文字图标等白天黑夜模式
        primaryColorBrightness: Brightness.light,
        //白天模式颜色
        primaryColorLight: Colors.white,
        //黑夜模式颜色
        primaryColorDark: Colors.black,
        //如 FloatingActionButton 的背景颜色
        accentColor: Colors.green,
        //FloatingActionButton 白天黑夜模式
        accentColorBrightness: Brightness.dark,
        //canvas 绘制的地方的颜色
        //canvasColor: Colors.black,
        //scaffold 控件的默认背景颜色
        scaffoldBackgroundColor: Colors.white,
        //BottomAppBar 控件的默认背景颜色
        bottomAppBarColor: Colors.blue,
        //Card 控件的默认背景颜色
        cardColor: Colors.blue,
        // 分割线默认颜色,如 ListTiles 的分割线
        dividerColor: Colors.blue,
        //高亮默认颜色,如 FloatingActionButton 按下的颜色
        //highlightColor: Colors.black,
        //水波纹默认颜色,如 FloatingActionButton 控件点击的水波纹效果
        splashColor: Colors.red,
        //水波纹的自定义处理
        splashFactory: new HandlerFactory(),
        //如文本选中一行的默认颜色(暂不知道用在哪里)
        selectedRowColor: Colors.white,
        //未选中控件并且有点击事件(如 Checkbox 控件)的默认颜色
        unselectedWidgetColor: Colors.black,
        //禁用不可点击按钮或者复选框的默认颜色
        disabledColor:Colors.red,
        //button 并且 onPress 不为 null 的默认背景颜色
         buttonColor:Colors.grey,
        //button 并且 onPress 不为 null 的样式
         buttonTheme:new ButtonThemeData(

        ),
        //PaginatedDataTable 控件 在选项选中的时候 头部的默认背景颜色
         secondaryHeaderColor:Colors.grey,
        // TextField 文本被选中的颜色。
         textSelectionColor:Colors.grey,
        //TextField 光标的颜色
        cursorColor:Colors.grey,
        //选定文本句柄的默认颜色。
         textSelectionHandleColor:Colors.red,
        //如 进度条 ProgressIndicator 控件的默认背景颜色。
         backgroundColor:Colors.grey,
        //Dialog 控件的默认背景色
         dialogBackgroundColor:Colors.grey,
         //TabBar 控件指示器的默认颜色。
        indicatorColor:Colors.grey,
         //TextField 提示文本或占位符文本的默认文本颜色。
        hintColor:Colors.grey,
        //TextField 输入验证错误提示的默认文本颜色。
        errorColor:Colors.red,
        //Switch,Radio和Checkbox 等控件激活状态的默认颜色。
        toggleableActiveColor:Colors.grey,
        //字体路径
        fontFamily:"lib/fonts/...",
        //页面文本默认样式
        textTheme:TextTheme(),
        //状态栏或者标题栏的文本默认样式,对应 primaryColor。
        primaryTextTheme:TextTheme(),
        //如 FloatingActionButton 的样式,对应 primaryColor。
        accentTextTheme:TextTheme(),
        //对应 InputDecorator、TextField和TextFormField 属性InputDecoration。
        inputDecorationTheme:new InputDecorationTheme(),
        //图标的默认样式。
         iconTheme:new IconThemeData(),
        //标题栏或者状态栏的默认图标样式。
         primaryIconTheme:new IconThemeData(),
        //如 FloatingActionButton 的默认图标样式。
        accentIconTheme:new IconThemeData(),
        //Slider 系列控件的默认样式。
         sliderTheme:new SliderThemeData(),
         //tabBar 控件的默认样式。
         tabBarTheme:new TabBarTheme(),
         //card 控件的默认样式
         cardTheme:new CardTheme(),
         //Chip 控件的默认样式
         chipTheme:new ChipThemeData(),
         //widget 适配的平台。
        platform:TargetPlatform.android,
         //md 控件的点击范围。
         materialTapTargetSize:new MaterialTapTargetSize(),
         //对应平台的默认 MaterialPageRoute 转换。
         pageTransitionsTheme:new PageTransitionsTheme(),
         //AppBar 控件 默认样式
         appBarTheme:new AppBarTheme(),
         //BottomAppBarTheme 控件的默认样式
         bottomAppBarTheme:new BottomAppBarTheme(),
         //一组13种颜色,配置大多数组件的颜色属性。
         //colorScheme:,
         //弹窗默认样式
         dialogTheme:DialogTheme(),
         //floatingActionButton 的默认样式
         floatingActionButtonTheme:new FloatingActionButtonThemeData(),
        //TextTheme、primaryTextTheme和accentTextTheme的颜色文本主题值。
         typography:new Typography(),
         //Cupertino 控件默认样式
         //cupertinoOverrideTheme:,
      ),
  • darkTheme

黑夜模式的主题样式,设置同 theme 属性

  • highContrastTheme

系统要求“高对比度”时使用的主题,针对 IOS 设备可以设置而设置的主题

  • highContrastDarkTheme

系统要求“高对比度”时使用的深色主题,针对 IOS 设备可以设置而设置的主题

  • themeMode

主题模式,跟随系统,白天,深色模式等等。

this.themeMode = ThemeMode.system,
  • local

设置语言,null 为跟随系统

locale:Locale('zh','cn'),

_languageCode

static const Map<String, String> _deprecatedLanguageSubtagMap = const <String, String>{
    'in': 'id', // Indonesian; deprecated 1989-01-01
    'iw': 'he', // Hebrew; deprecated 1989-01-01
    'ji': 'yi', // Yiddish; deprecated 1989-01-01
    'jw': 'jv', // Javanese; deprecated 2001-08-13
    'mo': 'ro', // Moldavian, Moldovan; deprecated 2008-11-22
    'aam': 'aas', // Aramanik; deprecated 2015-02-12
    'adp': 'dz', // Adap; deprecated 2015-02-12
    'aue': 'ktz', // ǂKxʼauǁʼein; deprecated 2015-02-12
    'ayx': 'nun', // Ayi (China); deprecated 2011-08-16
    'bgm': 'bcg', // Baga Mboteni; deprecated 2016-05-30
    'bjd': 'drl', // Bandjigali; deprecated 2012-08-12
    'ccq': 'rki', // Chaungtha; deprecated 2012-08-12
    'cjr': 'mom', // Chorotega; deprecated 2010-03-11
    'cka': 'cmr', // Khumi Awa Chin; deprecated 2012-08-12
    'cmk': 'xch', // Chimakum; deprecated 2010-03-11
    'coy': 'pij', // Coyaima; deprecated 2016-05-30
    'cqu': 'quh', // Chilean Quechua; deprecated 2016-05-30
    'drh': 'khk', // Darkhat; deprecated 2010-03-11
    'drw': 'prs', // Darwazi; deprecated 2010-03-11
    'gav': 'dev', // Gabutamon; deprecated 2010-03-11
    'gfx': 'vaj', // Mangetti Dune ǃXung; deprecated 2015-02-12
    'ggn': 'gvr', // Eastern Gurung; deprecated 2016-05-30
    'gti': 'nyc', // Gbati-ri; deprecated 2015-02-12
    'guv': 'duz', // Gey; deprecated 2016-05-30
    'hrr': 'jal', // Horuru; deprecated 2012-08-12
    'ibi': 'opa', // Ibilo; deprecated 2012-08-12
    'ilw': 'gal', // Talur; deprecated 2013-09-10
    'jeg': 'oyb', // Jeng; deprecated 2017-02-23
    'kgc': 'tdf', // Kasseng; deprecated 2016-05-30
    'kgh': 'kml', // Upper Tanudan Kalinga; deprecated 2012-08-12
    'koj': 'kwv', // Sara Dunjo; deprecated 2015-02-12
    'krm': 'bmf', // Krim; deprecated 2017-02-23
    'ktr': 'dtp', // Kota Marudu Tinagas; deprecated 2016-05-30
    'kvs': 'gdj', // Kunggara; deprecated 2016-05-30
    'kwq': 'yam', // Kwak; deprecated 2015-02-12
    'kxe': 'tvd', // Kakihum; deprecated 2015-02-12
    'kzj': 'dtp', // Coastal Kadazan; deprecated 2016-05-30
    'kzt': 'dtp', // Tambunan Dusun; deprecated 2016-05-30
    'lii': 'raq', // Lingkhim; deprecated 2015-02-12
    'lmm': 'rmx', // Lamam; deprecated 2014-02-28
    'meg': 'cir', // Mea; deprecated 2013-09-10
    'mst': 'mry', // Cataelano Mandaya; deprecated 2010-03-11
    'mwj': 'vaj', // Maligo; deprecated 2015-02-12
    'myt': 'mry', // Sangab Mandaya; deprecated 2010-03-11
    'nad': 'xny', // Nijadali; deprecated 2016-05-30
    'ncp': 'kdz', // Ndaktup; deprecated 2018-03-08
    'nnx': 'ngv', // Ngong; deprecated 2015-02-12
    'nts': 'pij', // Natagaimas; deprecated 2016-05-30
    'oun': 'vaj', // ǃOǃung; deprecated 2015-02-12
    'pcr': 'adx', // Panang; deprecated 2013-09-10
    'pmc': 'huw', // Palumata; deprecated 2016-05-30
    'pmu': 'phr', // Mirpur Panjabi; deprecated 2015-02-12
    'ppa': 'bfy', // Pao; deprecated 2016-05-30
    'ppr': 'lcq', // Piru; deprecated 2013-09-10
    'pry': 'prt', // Pray 3; deprecated 2016-05-30
    'puz': 'pub', // Purum Naga; deprecated 2014-02-28
    'sca': 'hle', // Sansu; deprecated 2012-08-12
    'skk': 'oyb', // Sok; deprecated 2017-02-23
    'tdu': 'dtp', // Tempasuk Dusun; deprecated 2016-05-30
    'thc': 'tpo', // Tai Hang Tong; deprecated 2016-05-30
    'thx': 'oyb', // The; deprecated 2015-02-12
    'tie': 'ras', // Tingal; deprecated 2011-08-16
    'tkk': 'twm', // Takpa; deprecated 2011-08-16
    'tlw': 'weo', // South Wemale; deprecated 2012-08-12
    'tmp': 'tyj', // Tai Mène; deprecated 2016-05-30
    'tne': 'kak', // Tinoc Kallahan; deprecated 2016-05-30
    'tnf': 'prs', // Tangshewi; deprecated 2010-03-11
    'tsf': 'taj', // Southwestern Tamang; deprecated 2015-02-12
    'uok': 'ema', // Uokha; deprecated 2015-02-12
    'xba': 'cax', // Kamba (Brazil); deprecated 2016-05-30
    'xia': 'acn', // Xiandao; deprecated 2013-09-10
    'xkh': 'waw', // Karahawyana; deprecated 2016-05-30
    'xsj': 'suj', // Subi; deprecated 2015-02-12
    'ybd': 'rki', // Yangbye; deprecated 2012-08-12
    'yma': 'lrr', // Yamphe; deprecated 2012-08-12
    'ymt': 'mtm', // Mator-Taygi-Karagas; deprecated 2015-02-12
    'yos': 'zom', // Yos; deprecated 2013-09-10
    'yuu': 'yug', // Yugh; deprecated 2014-02-28
  };

_countryCode

 static const Map<String, String> _deprecatedRegionSubtagMap = const <String, String>{
    'BU': 'MM', // Burma; deprecated 1989-12-05
    'DD': 'DE', // German Democratic Republic; deprecated 1990-10-30
    'FX': 'FR', // Metropolitan France; deprecated 1997-07-14
    'TP': 'TL', // East Timor; deprecated 2002-05-20
    'YD': 'YE', // Democratic Yemen; deprecated 1990-08-14
    'ZR': 'CD', // Zaire; deprecated 1997-07-14
  };
  • localizationsDelegates

国际化代理类,国际化 App 的时候会用到,它主要作用是为构造相应类的特定于语言环境的实例。flutter 默认提供了两种代理 GlobalMaterialLocalizations.delegate 和 GlobalWidgetsLocalizations.delegate, GlobalMaterialLocalizations.delegate 是为 Material Components 库提供了本地化的字符串和其他值,GlobalWidgetsLocalizations.delegate 是定义 widget 默认的文本方向,从左到右或从右到左, GlobalCupertinoLocalizations.delegate 为 Cupertino(ios风格)库提供了本地化的字符串和其他值。

 localizationsDelegates: [
     // ... app-specific localization delegate[s] here
     GlobalMaterialLocalizations.delegate,
     GlobalWidgetsLocalizations.delegate,
   ]
  • localeListResolutionCallback,localeResolutionCallback

对语言变化的监听,比如切换系统语言等,localeResolutionCallback 返回的第一个参数是当前语言的Locale,而 localeListResolutionCallback 返回当前手机支持的语言集合。

  • supportedLocales

App 支持的多语言,默认设置为美国英语

supportedLocales: [
        const Locale('en'), // English
        const Locale('he'), // Hebrew
        const Locale('zh'), // Chinese
        // ... other locales the app supports
      ],
  • debugShowMaterialGrid

Material Design 调试的基线网格,有助于布局时 widget 对齐,设置为 true 效果如下

image.png

  • showPerformanceOverlay

是否显示 GPU 和 UI 刷新率监控图,性能调试作用

image.png

  • checkerboardRasterCacheImages

是否显示光栅缓存图像的棋盘格

  • checkerboardOffscreenLayers

是否显示层级图

  • showSemanticsDebugger

是否显示布局边界

image.png

  • debugShowCheckedModeBanner

是否显示右上角的 debug 标签

  • shortcuts

刘海屏兼容设置

 Widget build(BuildContext context) {
   return WidgetsApp(
     shortcuts: <ShortcutActivator, Intent>{
       ... WidgetsApp.defaultShortcuts,
       const SingleActivator(LogicalKeyboardKey.select): const ActivateIntent(),
     },
     color: const Color(0xFFFF0000),
     builder: (BuildContext context, Widget? child) {
       return const Placeholder();
     },
   );
 }
  • actions

添加 App Actions 小部件,App 桌面快捷启动方式的功能

 Widget build(BuildContext context) {
   return WidgetsApp(
     actions: <Type, Action<Intent>>{
      ... WidgetsApp.defaultActions,
       ActivateAction: CallbackAction<Intent>(
         onInvoke: (Intent intent) {
           // Do something here...
           return null;
        },
       ),
     },
     color: const Color(0xFFFF0000),
     builder: (BuildContext context, Widget? child) {
       return const Placeholder();
     },
   );
 }
  • restorationScopeId

恢复状态时候需要使用到,比如如果 ListView 设置了 restorationId ,当我们滑到某个地方,按返回键退出,如果需要再次进来回到这个滑动的位置的地方。要让它生效就需要设置 MaterialApp  的 restorationScopeId,它指定的是一个随意的字符串。

  • scrollBehavior

配置 ScrollBehavior 控件的行为,默认为 MaterialScrollBehavior。

  • useInheritedMediaQuery

MediaQuery 用于查询解析给定数据的媒体信息(例如,window宽高/横竖屏/像素密度比 等信息)官方提供这个组件让开发者可以获取想要的数据。它主要用于不同尺寸大小设备的适配。在后面学习 MediaQuery 的使用的时候应该会用到,暂时不知道有什么作用。

  1. WidgetsApp 属性

这里说说和上面 MaterialApp 不一样的属性

  • pageRouteBuilder

设置页面跳转的转场,也可以自定义页面跳转时的动画。例如这里使用 material 的转场效果:

pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) {
  return MaterialPageRoute(builder: builder, settings: settings);
}
  • textStyle

它主要是为应用中的文本使用的默认样式

textStyle: const TextStyle(
  color: Color(0x00000000),
  fontSize: 18,
  fontWeight: FontWeight.normal,
),
  • debugShowWidgetInspector

调试 Widget 布局的工具,对应 Flutter widget inspector 工具,它可以可视化和查看 Widget 树结构

  • inspectorSelectButtonBuilder

创建 WidgetInspector。 用于视图模式和检查模式之间切换的小部件。 这使得 MaterialApp 可以使用材质按钮来切换检查器的选择模式,而不需要 WidgetInspector 依赖于 material 包。

inspectorSelectButtonBuilder:
    (BuildContext context, VoidCallback onPressed) {
  return FloatingActionButton(
    child: const Icon(Icons.search),
    onPressed: onPressed,
    mini: true,
  );
}

好了,这篇就先到这里,后面继续学习。