首页系统综合问题flutter系列之:Material主题的基础-MaterialApp

flutter系列之:Material主题的基础-MaterialApp

时间2023-05-11 01:35:04发布分享专员分类系统综合问题浏览238

今天小编给各位分享applocale怎么使用的知识,文中也会对其通过flutter系列之:Material主题的基础-MaterialApp和9.GridView常用的创建方法等多篇文章进行知识讲解,如果文章内容对您有帮助,别忘了关注本站,现在进入正文!

内容导航:

  • flutter系列之:Material主题的基础-MaterialApp
  • 9.GridView常用的创建方法
  • Flutter 在body中设置TabBar和TabBarView
  • Flutter 小技巧之优化你使用的 BuildContext
  • 一、flutter系列之:Material主题的基础-MaterialApp

    简介

    为了简化大家的使用,虽然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风格的第一入口,希望大家能够熟练掌握它的用法。

    本文的例子:https://github.com/ddean2009/learn-flutter.git

    更多内容请参考 http:///06-flutter-material-materialapp/

    最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

    欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

    一、9.GridView常用的创建方法

    import 'package:flutter/material.dart';

    main() => runApp(KSJMyApp());

    class KSJMyAppextends StatelessWidget {

    @override

    Widget build(BuildContext context) {

    return MaterialApp(

    home: MyApp(),

        );

      }

    }

    class MyAppextends StatelessWidget {

    @override

    Widget build(BuildContext context) {

    return Scaffold(

    appBar: AppBar(

    title: Text('KSJ'),

          ),

          body: NewCountGridViewNewWidget(),

          floatingActionButton: FloatingActionButton(

    child: Icon(Icons.add),

              onPressed: () {

    print("+++");

              }),

          floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,

        );

      }

    }

    class NewCountGridViewNewWidgetextends StatelessWidget {

    const NewCountGridViewNewWidget({

    Key key,

      }) :super(key: key);

      @override

    Widget build(BuildContext context) {

    return GridView.count(

    crossAxisCount:9,

          crossAxisSpacing:5,

          mainAxisSpacing:10,

          children: List.generate(100, (index) {

    return Container(

    color: index %2 ==0 ? Colors.red : Colors.blue,

            );

          }),

        );

      }

    }

    class GridViewMaxExtentNewWidgetextends StatelessWidget {

    const GridViewMaxExtentNewWidget({

    Key key,

      }) :super(key: key);

      @override

    Widget build(BuildContext context) {

    return GridView(

    padding: EdgeInsets.symmetric(horizontal:8, vertical:5),

          gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(

    maxCrossAxisExtent:100,

              mainAxisSpacing:10,

              crossAxisSpacing:20,

              childAspectRatio:0.5),

          children: List.generate(1000, (index) {

    return Container(

    color: index %2 ==0 ? Colors.red : Colors.blue,

            );

          }),

        );

      }

    }

    class GridViewDemo1NewWidgetextends StatelessWidget {

    const GridViewDemo1NewWidget({

    Key key,

      }) :super(key: key);

      @override

    Widget build(BuildContext context) {

    return GridView(

    // 以自身宽度为基准

    //        gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(maxCrossAxisExtent: 35),

    // 以Item个数为基准

          padding: EdgeInsets.symmetric(horizontal:8, vertical:5),

          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(

    crossAxisCount:5,

            mainAxisSpacing:5,

            crossAxisSpacing:5,

            childAspectRatio:0.1,

          ),

          children: List.generate(1000, (index) {

    return Container(

    color: index %2 ==0 ? Colors.red : Colors.blue,

            );

          }),

        );

      }

    }

    二、Flutter 在body中设置TabBar和TabBarView

    最近在开发中想实现一个AppBar下面有选项卡,来回切换的页面功能,百度了很多没有和自己需求符合的,网上大都是返回的Scaffold,使用系统的Appbar,添加至.bottom中,但是现在项目中用的是自定义的Appbar,不想破坏系统的统一封装。

    所以在body 中实现TabBar 和 TabBarView,开始使用Column一直不行,只能显示一个,但是在body里面同时放置 TabBar 和 TabBarView需要注意
    TabBarView 的父 Widget 必须知道宽高才能布局,否则,会报错:BoxConstraints forces an infinite height.

    使用 Column + Expanded 即可:

    注:还有设置tabbar的tab背景颜色,tabbar中的tab的背景颜色取的实际是AppBar的主题色,所以我们将tabbar放在Material中来重置了主题色,变成我们想要的背景色.

    buildTabBar为创建TabBar的方法:

    buildBodyView创建视图方法:

    三、Flutter 小技巧之优化你使用的 BuildContext

    Flutter 里的 BuildContext 相信大家都不会陌生,虽然它叫 Context,但是它实际是 Element 的抽象对象,而在 Flutter 里,它主要来自于 ComponentElement 。

    关于 ComponentElement 可以简单介绍一下,在 Flutter 里根据 Element 可以简单地被归纳为两类:

    所以一般情况下,我们在 build 方法或者 State 里获取到的 BuildContext 其实就是 ComponentElement 。

    那使用 BuildContext 有什么需要注意的问题

    首先如下代码所示,在该例子里当用户点击 FloatingActionButton 的时候,代码里做了一个 2秒的延迟,然后才调用 pop 退出当前页面。

    正常情况下是不会有什么问题,但是当用户在点击了 FloatingActionButton 之后,又马上点击了 AppBar 返回退出应用,这时候就会出现以下的错误提示。

    可以看到此时 log 说,Widget 对应的 Element 已经不在了,因为在 Navigator.of(context) 被调用时, context 对应的 Element 已经随着我们的退出销毁。

    一般情况下处理这个问题也很简单, 那就是增加 mounted 判断,通过 mounted 判断就可以避免上述的错误

    上面代码里的 mounted 标识位来自于 State , 因为 State 是依附于 Element 创建,所以它可以感知 Element 的生命周期 ,例如 mounted 就是判断 _element != null; 。

    那么到这里我们收获了一个小技巧: 使用 BuildContext 时,在必须时我们需要通过 mounted 来保证它的有效性

    那么单纯使用 mounted 就可以满足 context 优化的要求了吗

    如下代码所示,在这个例子里:

    由于在 5 秒之内,Item 被划出了屏幕,所以对应的 Elment 其实是被释放了,从而由于 mounted 判断, SnackBar 不会被弹出。

    那如果假设需要在开发时展示点击数据上报的结果,也就是 Item 被释放了还需要弹出,这时候需要如何处理

    我们知道不管是 ScaffoldMessenger.of(context) 还是 Navigator.of(context) ,它本质还是通过 context 去往上查找对应的 InheritedWidget 泛型,所以其实我们可以提前获取。

    所以,如下代码所示,在 Future.delayed 之前我们就通过 ScaffoldMessenger.of(context); 获取到 sm 对象,之后就算你直接退出当前的列表页面,5秒过后 SnackBar 也能正常弹出。

    为什么页面销毁了,但是 SnackBar 还能正常弹出

    因为此时通过 of(context); 获取到的 ScaffoldMessenger 是存在 MaterialApp 里,所以就算页面销毁了也不影响 SnackBar 的执行。

    但是如果我们修改例子,如下代码所示,在 Scaffold 上面多嵌套一个 ScaffoldMessenger ,这时候在 Item 里通过 ScaffoldMessenger.of(context) 获取到的就会是当前页面下的 ScaffoldMessenger 。

    这种情况下我们只能保证Item 不可见的时候 SnackBar 还能正常弹出, 而如果这时候我们直接退出页面,还是会出现以下的错误提示,因为 ScaffoldMessenger 也被销毁了 。

    所以到这里我们收获第二个小技巧: 在异步操作里使用 of(context) ,可以提前获取,之后再做异步操作,这样可以尽量保证流程可以完整执行

    既然我们说到通过 of(context) 去获取上层共享往下共享的 InheritedWidget ,那在哪里获取就比较好

    还记得前面的 log 吗?在第一个例子出错时,log 里就提示了一个方法,也就是 State 的 didChangeDependencies 方法。

    为什么是官方会建议在这个方法里去调用 of(context) ?

    首先前面我们一直说,通过 of(context) 获取到的是 InheritedWidget ,而 当 InheritedWidget 发生改变时,就是通过触发绑定过的 Element 里 State 的 didChangeDependencies 来触发更新, 所以在 didChangeDependencies 里调用 of(context) 有较好的因果关系

    那我能在 initState 里提前调用吗

    当然不行,首先如果在 initState 直接调用如 ScaffoldMessenger.of(context).showSnackBar 方法,就会看到以下的错误提示。

    这是因为 Element 里会判断此时的 _StateLifecycle 状态,如果此时是 _StateLifecycle.created 或者 _StateLifecycle.defunct ,也就是在 initState 和 dispose ,是不允许执行 of(context) 操作。

    当然,如果你硬是想在 initState 下调用也行,增加一个 Future 执行就可以成功执行

    那我在 build 里直接调用不行吗

    直接在 build 里调用肯定可以,虽然 build 会被比较频繁执行,但是 of(context) 操作其实就是在一个 map 里通过 key - value 获取泛型对象,所以对性能不会有太大的影响。

    真正对性能有影响的是 of(context) 的绑定数量和获取到对象之后的自定义逻辑 ,例如你通过 MediaQuery.of(context).size 获取到屏幕大小之后,通过一系列复杂计算来定位你的控件。

    例如上面这段代码,可能会导致键盘在弹出的时候,虽然当前页面并没有完全展示,但是也会导致你的控件不断重新计算从而出现卡顿。

    所以到这里我们又收获了一个小技巧: 对于 of(context) 的相关操作逻辑,可以尽量放到 didChangeDependencies 里去处理

    关于applocale怎么使用的问题,通过《Flutter 在body中设置TabBar和TabBarView》、《Flutter 小技巧之优化你使用的 BuildContext》等文章的解答希望已经帮助到您了!如您想了解更多关于applocale怎么使用的相关信息,请到本站进行查找!

    爱资源吧版权声明:以上文中内容来自网络,如有侵权请联系删除,谢谢。

    applocale怎么使用
    AdobeCameraRaw支持哪些相机品牌 AdobeCS序列号的种类有哪些,区别在哪里?