2.3 Flutter主题

为了在整个应用中使用同一套颜色和字体样式,可以使用“主题”这种方式。定义主题有两种方式:全局主题,或使用Theme来定义应用程序局部的颜色和字体样式。事实上,全局主题只是由应用程序根MaterialApp创建的主题。

定义一个主题后,就可以在我们自己的Widget中使用它,Flutter提供的Material Widgets将使用主题为AppBars、Buttons、Checkboxes等设置背景颜色和字体样式。

2.3.1 创建应用主题

创建主题的方法是将ThemeData提供给MaterialApp构造函数,这样就可以在整个应用程序中共享包含颜色和字体样式的主题。ThemeData的主要属性如表2-1所示。

表2-1 ThemeData属性及描述

(续)

(续)

如果没有提供主题,Flutter将创建一个默认主题。主题数据的示例代码如下:

        MaterialApp(
          title: title,
          theme: ThemeData(
            brightness: Brightness.dark,
            primaryColor: Colors.lightBlue[800],
            accentColor: Colors.cyan[600],
          ),
        );

2.3.2 局部主题

如果我们想在应用程序的某一部分使用特殊的颜色,那么就需要覆盖全局的主题。有两种方法可以解决这个问题:创建特有的主题数据或扩展父主题。

1.创建特有的主题数据

实例化一个ThemeData并将其传递给Theme对象,代码如下:

        Theme(
          // 创建一个特有的主题数据
          data: ThemeData(
            accentColor: Colors.yellow,
          ),
          child: FloatingActionButton(
            onPressed: () {},
            child: Icon(Icons.add),
          ),
        );

2.扩展父主题

扩展父主题时无须覆盖所有的主题属性,我们可以使用copyWith方法来实现,代码如下:

        Theme(
          // 覆盖accentColor为Colors.yellow
          data: Theme.of(context).copyWith(accentColor: Colors.yellow),
          child: FloatingActionButton(
            onPressed: null,
            child: Icon(Icons.add),
          ),
        );

2.3.3 使用主题

主题定义好后就可以使用了。首先,通过函数Theme.of(context)来获取主题,方法是查找最近的主题,如果找不到就会找整个应用的主题。

下面来看一个简单的示例—将应用的主题颜色定义为绿色,在界面中间再加一个带有背景色的文本。

完整的示例代码如下所示:

        import 'package:flutter/foundation.dart';
        import 'package:flutter/material.dart';


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


        class MyApp extends StatelessWidget {
          @override
          Widget build(BuildContext context) {
            final appName = '自定义主题';


            return MaterialApp(
              title: appName,
              // 主题配置
              theme: ThemeData(
                // 应用程序整体主题的亮度
                brightness: Brightness.light,
                // App主要部分的背景色
                primaryColor: Colors.lightGreen[600],
                // 前景色(文本、按钮等)
                accentColor: Colors.orange[600],
              ),
              home: MyHomePage(
                title: appName,
              ),
            );
          }
        }


        class MyHomePage extends StatelessWidget {
          // 标题
          final String title;
          //接收title值key为widget的唯一标识
          MyHomePage({Key key, @required this.title}) : super(key: key);


          @override
          Widget build(BuildContext context) {
            return Scaffold(
              appBar: AppBar(
                title: Text(title),
              ),
              body: Center(
                child: Container(
                  // 获取主题的accentColor
                      color: Theme.of(context).accentColor,
                      child: Text(
                        '带有背景颜色的文本组件',
                        // 获取主题的文本样式
                        style: Theme.of(context).textTheme.title,
                      ),
                    ),
                  ),
                  floatingActionButton: Theme(
                    // 使用copyWith的方式获取accentColor
                    data: Theme.of(context).copyWith(accentColor: Colors.grey),
                    child: FloatingActionButton(
                      onPressed: null,
                      child: Icon(Icons.computer),
                    ),
                  ),
                );
              }
            }

这里我们第一次用到了Key。Flutter里Key是每一个Widget的唯一身份标识,它是在组件创建及渲染时生成的。上面的代码把Key作为参数传入Widgth中,则此Widget会根据指定的名字生成Key,Key是一个可选参数。

Flutter是受React启发的,参考了Virtual Dom(虚拟Dom)的Diff算法。在Diff的过程中,如果节点有Key进行比对,则能够最大限度重用已有的节点(特别是在有列表渲染的场景中)。总之,这里我们可以知道Key能够提高性能,所以每个Widget都会构建方法,也都会有一个Key参数可选,贯穿着整个框架。Key派生出Localkey和Globalkey,二者的用途如下所示:

❑Localkey :直接继承自Key,它应用于拥有相同父Element的组件进行比较的情况。Localkey派生出了许多子类,如ValueKey、ObjectKey、UniqueKey。

❑GlobalKey:可以通过GlobalKey找到持有该GlobalKey的Widget、State和Element。注意,GlobalKey是非常昂贵的,需要谨慎使用。

自定义主题的效果如图2-1所示。

图2-1 自定义主题效果

2.4 使用包资源

Flutter包类似于Java语言里的jar包,由全球众多开发者共同提供第三方库,例如网络请求(http)、自定义导航/路由处理(fluro)、集成设备API(如url_launcher&battery)以及第三方平台SDK(如Firebase)等。这使得开发者可以快速构建应用程序,而无须重新创建。

1.包仓库

所有包(package)都会发布到Dart的包仓库里,如图2-2所示,输入想使用的包后点击搜索按钮即可。

图2-2 Dart包仓库

提示 包仓库地址为https://pub.dartlang.org

2.包使用示例

接下来使用url_launcher这个包来详解讲解第三方包的使用,步骤如下。

步骤1打开pubspec.yaml文件,在dependencies下添加包的名称及版本,参见图2-3中箭头指向的内容。

图2-3 添加url_launcher包

步骤2点击Packages get获取工程配置文件中所添加的引用包,或者打开命令行窗口执行flutter packages get命令,如图2-4所示。

图2-4 执行Packages get命令

注意 在更新包资源的过程中注意观察控制台消息,可能有版本错误、网络问题,这些都会导致更新失败。

步骤3打开main.dart文件,导入url_launcher.dart包:

        import 'package:url_launcher/url_launcher.dart';

步骤4这时就可以使用launch方法来打开url地址了:

        const url = 'https://www.baidu.com';
        launch(url);

完整的main.dart代码如下所示:

    import 'package:flutter/material.dart';
    import 'package:url_launcher/url_launcher.dart';


    void main() => runApp(MyApp());


    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: '使用第三方包示例',
          home: Scaffold(
            appBar: AppBar(
              title: Text('使用第三方包示例'),
            ),
            body: Center(
              child: RaisedButton(
                onPressed: () {
                    // 指定url并发起请求
                    const url = 'https://www.github.com';
                    // 调用url_launcher包里的launch方法
                    launch(url);
                },
                child: Text('打开GitHub'),
              ),
            ),
          ),
        );
      }
    }

步骤5启动示例,打开界面如图2-5所示。

图2-5 使用第三方包示例初始界面

图2-6 打开GitHub页面效果图

点击“打开GitHub ”按钮,页面会跳转至百度页面,如图2-6所示。