
2.13 把小部件拆分到单独的文件中
我们已经学习了很多基础的小部件,那么怎样建立小部件之间的联系呢?在编写Flutter应用的过程中,需要经常做的一件事就是拆分代码并封装。不可以把所有的代码都放在一个根的小部件中,就像Myapp这个StatefulWidget小部件一样。我们可以把应用拆分成多个细粒度的小部件,并把它们分发到多个文件中,这样可以使每个小部件和文件都易读,也容易维护。怎样拆分呢?
我们使用StatefulWidget来管理Card小部件和news数组,如果仔细观察可以看到StatefulWidget是从这个列小部件开始渲染Card的,其他的小部件如MaterialApp、Scaffold、AppBar还有RaisedButton,它们都不会改变。RaisedButton按钮是触发改变状态的部分,但也可以把这个按钮拆分出来。
首先我们把Card列表拆分出来。创建一个新的文件,命名为news.dart,可以随意命名,但按照惯例文件名全部小写,如果有多个单词的话,用下画线分隔。文件格式是dart。在这个文件中渲染资讯列表,可以把Card列表所在的列小部件复制到news.dart文件中。在news.dart文件中创建一个类News。现在需要扩展来自Flutter中的类,我们需要在每一个文件中添加import,代码如下所示:
import 'package:flutter/material.dart'; //引入material包
因为每一个文件都是独立的。在main.dart中引入的material包不会在这里生效,所以这里需要引入flutter/material包,然后创建一个小部件,并复制Column的逻辑放到这个小部件中,现在的问题是这里需要继承一个有状态的小部件还是无状态的小部件呢?两种方式都可以,但最好在这里使用无状态的小部件,这列Card是需要改变的,为什么使用无状态的小部件呢?因为数据的变化实际上是发生在其他的地方。News小部件接收一组news数据,这组news数据可能被改变,但是它是在我们创建的News小部件之外被改变的。在News小部件中添加一个build()方法,使用Visual Studio Code的提示创建,代码如下:

把复制Column的逻辑放到return后面,代码如下:

IDE提示这个news不存在,怎样把news数组传到News这个小部件中呢?可以从外部传入数据,然后就可以在News小部件中使用传入的数据了。我们可以通过构造器来实现,创建一个构造器,输入类名,小括号,大括号,代码如下:

构造器在小部件创建的时候就会被调用,构造器还有其他的特性,现在用到的一个特性是接收数据news数组,再可以给构造器命名一个参数news,参数名可以任意命名,然后把传进来的news存储到News类的属性中,所以需要在类中添加一个属性,代码如下:

news属性的类型是字符串型的数组,它不能改变,也没有初始化。现在把构造器中的news存储到这个属性news当中。Dart语言提供了一个方便的快捷方式,在这里输入this.news,会自动获取传入的参数,并将其存储在具有相同名称的属性中,这里需要在构造器后面加分号,代码如下:

这样就可以通过构造器将数据传递到属性中了。类News下面有一个波浪线,提示类中的所有内容都是不可变的,如图2.21所示。
因为创建的是StatelessWidget,无论怎样它都不能对变化做出反应,所以必须标注属性不可以改变,在属性前面加一个特殊的关键字final,代码如下:

图2.21 类News的提示信息

这是Dart的一个特性,它告诉Flutter属性news将永远不会改变。从构造器获得的值初始化后属性news将永远不会改变。也可以不加这个final,加上它是为了更清楚这是一个仅从外部设置的值,如果有新的值从外部传进来,它只是简单地替换之前的值,只是替换,不会改变,然后再使用替换的这个值调用build()方法。