
                            
                            因收到Google相关通知，网站将会择期关闭。相关通知内容
                            
                            
                            07 使用组件构建静态界面
                            1. 代码的分文件管理

在计数器项目中，我们知道界面中展示的内容和组件息息相关，其中其决定性作用的是 MyHomePage 组件。但所有的代码都塞在了 main.dart 文件中，随着需求的增加，把所有代码都放一块，显然是不明智的。所以首先来看一下如何分文件来管理代码。

如下所示，先创建一个 counter 文件夹，用于盛放计数器界面的相关代码文件；然后创建 counter_page.dart 文件，并把 MyHomePage 的相关代码放入其中：



这时，可以将 main.dart 中 MyHomePage 的相关代码删除；会发现红色的波浪线，表示找不到 MyHomePage 类型：



此时只需要通过 import 关键字，在 main.dart 上方导入文件即可：

---->[main.dart]----
import 'counter/counter_page.dart';

// 略同...


分文件管理代码就像整理书籍，分门别类地进行摆放，各个区域各司其职，自己容易检阅，别人也容易看懂。下面创建一个 guess 文件夹，用于盛放本模块 猜数字 小项目的相关代码：





2. 创建你自己的组件

首先要明确一点，文件夹的名称、文件的名称、类型的名称、属性的名称、函数的名称，都是可以任意的，甚至可以使用汉字(但不建议)，只要在使用时对应访问即可。但一个好名字对于阅读来非常重要，取 a1,c,b,d45,rrr 这样的名字，对阅读者而言是灾难，也许过两天，连你自己也认不得。所以一个好名字是个非常重要，不要偷懒， 最好有明确的含义。

比如对于猜数字这个需求来说，整体的界面可以叫 GuessPage , 这里我们可以先借用一下计数器中 MyHomePage 代码，照葫芦画瓢，改巴改巴。先把 MyHomePage 代码复制到 guess_page.dart 中。


重命名小技巧: 当你想对一个类型、函数、属性名进行重命名，并且想让在它们使用使用处自动修改。可以将鼠标点在名称上，右键 -> Refactor -> Rename ; 也可以使用后面的快捷键直接操作。




输入新名称后，点击 Refactor , 所有使用处都会同步更新：





然后在 main.dart 中，将 GuessPage 作为 home 参数，即可展示 GuessPage 组件中的界面效果：

---->[main.dart]----
import 'guess/guess_page.dart';
    // 略同...
    home: const GuessPage(title: '猜数字'),


接下来的任务是如何修改代码，来完成猜数字的功能需求。首先我们来完成一件简单的事：


点击按钮生成随机 0~99 之间的数字





初始效果
点击生成随机数













3.随机数的生成与界面更新

计数器项目中，点击按钮之所以界面数字自加，是因为 _incrementCounter 方法中，触发了 _counter++ 。所以想要在点击时显示随机数，思路很简单：将 _counter 变量赋值为随机数即可。

void _incrementCounter() {
  setState(() {
    _counter++;
  });
}


所以首先需要了解一下 Dart 中如何生成随机数：在 dart 的 math 包中，通过 Random 类型可以生成随机数。这里生成的是随机整数，使用 nextInt 方法，它有一个入参，表示生成随机数的最大值(不包含)。比如传入 100 时，将返回 0~99 之间的随机整数：

import 'dart:math';

Random _random = Random();
_random.nextInt(100);




这样，点击按钮时只要将 _counter 赋值为随机数即可，如下所示：

void _incrementCounter() {
  setState(() {
    _counter = _random.nextInt(100);
  });
}


但之前说过，名字非常重要。但这里 _counter 含义是计数器，_incrementCounter 含义是自增计数器，在当前需求的语境中并不是非常适合。起名字最好和其功能相关，比如可以将数值变量可以称之 _value ; 方法可以称之 _generateRandomValue，这样代码阅读起来就会更容易理解。
同样，也可以使用 Refactor 重命名：

class _GuessPageState extends State<GuessPage> {

  int _value = 0;
  
  Random _random = Random();
  
  void _generateRandomValue() {
    setState(() {
      _value = _random.nextInt(100);
    });
  }




4. 头部栏 AppBar 和输入框 TextFiled 的使用

接下来，我们要将头部的标题栏 AppBar 改成如下的样式，中间是可以输入的输入框，右侧是一个运行的图标按钮。





AppBar 常用于左中右布局结构，如下所示：

AppBar(
  leading: 左侧,
  actions: [右侧列表],
  title: 中间部分,
)


也就是说在不同的参数中，可以插入不同的组件进行显示。比如这里 leading 指定为 Icon 组件，展示图标：

leading: Icon(Icons.menu, color: Colors.black,),


actions 入参是一个组件列表，这里放入一个 IconButton 组件，展示图标按钮：

actions: [
  IconButton(
    splashRadius: 20,
    onPressed: (){}, 
    icon: Icon(Icons.run_circle_outlined, color: Colors.blue,)
  )
],


title 入参是中间部分，使用 TextField 组件展示输入框。这里组件构造时的入参对象都是用于配置展示信息的，可以简单认识一下，不用急着背诵，以后慢慢接触，早晚都会非常熟悉。

TextField(
  keyboardType: TextInputType.number, //键盘类型: 数字
  decoration: InputDecoration( //装饰
      filled: true, //填充
      fillColor: Color(0xffF3F6F9), //填充颜色
      constraints: BoxConstraints(maxHeight: 35), //约束信息
      border: UnderlineInputBorder( //边线信息
        borderSide: BorderSide.none,
        borderRadius: BorderRadius.all(Radius.circular(6)),
      ),
      hintText: "输入 0~99 数字", //提示字
      hintStyle: TextStyle(fontSize: 14) //提示字样式
   ),
),




最后说一下，如何去掉顶部的灰块：AppBar 组件在构造时，可以通过 systemOverlayStyle 入参控制状态类和导航栏的信息。如下代码可以使顶部状态栏变成透明色，文字图标是暗色：



 AppBar(
  systemOverlayStyle: SystemUiOverlayStyle(
      statusBarIconBrightness: Brightness.dark,
      statusBarColor: Colors.transparent
  ),




5. 叠放 Stack 组件和列 Column 组件的使用

当用户输入的数字大了，或小了。需要界面上给予提示。为了更加醒目，这里给出的设计如下所示。如果大了，上半屏亮红色，展示 大了；如果小了，下半屏亮蓝色，展示 小了：




小了提示
大了提示










可以看出此时中间的文字是浮在提示色块之上的，想实现这种多个组件层层堆叠的效果，可以使用 Stack 组件来完成。其构造函数中 children 入参传入组件列表，在显示层次上后来居上。
如下所示，将 Stack 作为 body , 在其中放入一个红色的 Container 容器组件, 以及之前的主内容。运行后会看到：中间文字就会浮在红色容器之上。



body: Stack(
  children: [
    Container(color: Colors.redAccent),
    //主内容略...
  ],
),




除了堆叠的效果，还有一个问题，如何实现 上下平分区域 呢? 我们前面知道 Column 组件可以让两个组件竖直排列。在 Column 中可以通过 Expanded 组件延展剩余区域，另外 Spacer() 组件相当于空白的 Expanded。当存在多个 Expanded 组件时，就可以按比例分配剩余空间，默认是 1:1 ，也可以通过 flex 入参调节占比。举个小例子，如下所示：




Expanded 红+Expanded 蓝
Expanded 红+ Spacer











---->[红+蓝]----
Column(
  children: [
    Expanded( child: Container(color: Colors.redAccent)),
    Expanded( child: Container(color: Colors.blueAccent)),
  ],
),

---->[红+空]----
Column(
  children: [
    Expanded( child: Container(color: Colors.redAccent)),
    Spacer()
  ],
),


到这里，猜数字项目中需要使用的基础组件就已经会师完毕。大家可以基于现在已经掌握的知识，完成如下效果：
注: Container 组件的 child 入参可以设置内容组件。 本例参考代码见： guess_page.dart





6.组件的简单封装

上面虽然完成了布局效果，但是从 guess_page.dart 中可以感觉到，随着需求的增加，各种组件全都塞到了一块。这才只是一个简单的布局，就已经有点不堪入目了；如果再加上交互的逻辑，恐怕要乱成一锅粥了。

其实对于新手而言，编程语言语法本身并不是什么难事，对规整代码的把握才是最大的挑战；很容易要什么，写什么，最后什么东西都塞在一块，连自己都看不下去了，从而心灰意冷，劝退放弃。小学时，老师就教导我们，遇到巨大的问题，要尝试将它分解成若干个小问题，逐一解决。

其实有些大问题在肢解过程中，会有某些类似的小问题，这些小问题可以通过某种相同的解决方案来处理。这种通用解决方案，就是一种封装的思想，问题的专属解决方案一旦封装完毕，输入问题，就可以解决问题，使用者不必在意处理的过程，可以大大提升解决问题的效率。回忆一下，在介绍函数时，通过 bmi 函数，封装体质指数的计算公式，就是通过函数来封装解决方案。



对于组件来说，也是一样：某些相似的结构，也可以通过 封装 进行复用。比如这里 大了 和 小了 只是颜色和文字不同，两者的结构类似。就可以通过封装来简化代码：
最简单的封装形式是通过函数封装，通过入参来提供界面中差异性的信息，如下所示 _buildResultNotice 函数接收颜色和消息，返回 Widget 组件:

Widget _buildResultNotice(Color color, String info) {
  return Expanded(
      child: Container(
    alignment: Alignment.center,
    color: color,
    child: Text(
      info,
      style: TextStyle(
          fontSize: 54, color: Colors.white, fontWeight: FontWeight.bold),
    ),
  ));
}


如果把相似的结果写两遍，只会徒增无意义的代码。而封装之后，只需要调用方法，就可以完成任务：

Column(
  children: [
    _buildResultNotice(Colors.redAccent,'大了'),
    _buildResultNotice(Colors.blueAccent,'小了'),
  ],
),




如果封装体的代码非常复杂，或者需要单独维护，以便之后修改方便定位，也可以通过新组件的形式来封装组件。如下所示，新创建 result_notice.dart 文件，在其中定义 ResultNotice 组件，专门处理结果提示信息的展示任务。



一方面，专人专用，需要更改时直接在这里更改。比如你让另一个人帮忙改改某处的代码，而他对项目不熟悉，如果代码分离的得当，你告诉他这个界面由 xxx.dart 文件负责，他就可以在不了解项目的前提下，对界面进行修改。这就是 职责分离 的益处。不同人干自己擅长的事，有利于整体结构的稳定。

另一方面也能缓解 guess_page.dart 中的代码压力，不至于随着需求的增加代码量激增，对可读性友好。在使用时，可以将 ResultNotice 视为普通的组件，放在 Column 之中：

Column(
  children: [
    ResultNotice(color:Colors.redAccent,info:'大了'),
    ResultNotice(color:Colors.blueAccent,info:'小了'),
  ],
),




同样，这里 AppBar 组件的构建逻辑也是太复杂的，可以在 guess_app_bar.dart 中单独维护。这里主要是出于简化 guess_page.dart 中代码的考量，不强求可复用性。



这样 guess_page.dart 中的代码就整洁了很多，其他两个文件也在各司其职。相对与之前全塞在一块，更便于阅读，封装之后的代码见 guess_page.dart 。



7.本章小结

本章主要学习了如何通过 Flutter 框架提供的组件，来搭建期望的界面呈现效果。期间介绍了如何分文件来管理代码、以及通过自定义组件来封装构建逻辑。最后简单分析了一下组件封装的优势。

学完本章，你应该能够自己动手搭建一些简单的静态界面了。但应用程序是要和用户进行交互的，就需要界面随着用户的交互进行变化。下一篇将从用户交互的角度，通过代码来实现猜数字的具体功能。

                        
                        
                            