1. 为什么第一课先讲变量?——星际指挥中心的"仪表盘思维"
很多人把 Flutter 学习的第一天浪费在环境安装上,装完 SDK 又配编辑器,最后跑起来一个 hello world,关掉就忘了。我反倒觉得,第一天最该先搞定的是语言基础里的变量和基本类型。不是 UI,不是路由,只是一串串数据的种类和命名。你可能会说,这不就是所有编程教程的第一章吗?对,就是它。但我这次不是干巴巴地背语法,而是借了一个“星际指挥中心模型”来学,效果完全不一样。
先解释一下我的学习背景。我的目标很明确:用 Flutter 做一套能跑在鸿蒙生态里的跨端应用。Flutter 这个跨端框架的优势不用多说,一套 Dart 代码可以同时编译到多个平台,UI 是自己画的,不依赖系统原生控件,所以跨端一致性很好。鸿蒙生态现在越来越完整,如果以后要为鸿蒙设备做应用,同时又不想把 Android/iOS 的逻辑重写一遍,Flutter 就是值得赌一把的方向。但问题也在这里:鸿蒙开发和传统安卓开发不一样,工具链、权限、系统接口都有各自的门道,如果第一天就把所有平台的环境全折腾完,大概率连代码都没碰就想放弃。
所以我把第一天的目标切得很小:不碰页面布局,不碰网络请求,只学“变量和基本类型”。这个内容虽然基础,但它决定了后面你写 Flutter 任何功能的时候,数据到底是怎么被声明、被传递、被限制的。我用的“星际指挥中心模型”也很简单:把整个应用想象成一个太空指挥中心,每一个变量就是仪表盘上的一个读数,每一种数据类型就是一个独立的信号通道。int 是战损计数器,double 是能量值,String 是通讯消息,bool 是护盾开关,List 是编队花名册,Map 是物资清单。这样一想,变量就变成了有画面感的东西。
1.1 从跨端框架学习路径说起
刚开始学 Flutter,最容易犯的错误是急着看 Widget 写法。我也踩过这个坑,看到网上的教程一上来就教你 Center、Text、Container,就跟着抄,抄完根本不知道为何这样写。后来我停下来想了一下:Flutter 的一切 UI 都是由数据驱动的,你不懂数据怎么存储、怎么流动,Widget 写起来就是一坨一坨的魔法。比如你要在指挥中心大屏上显示“当前能量:65%”,这个 65 到底是什么类型?是整数还是浮点数?能不能直接拼到字符串里?这些就是变量和基本类型要回答的问题。
所以我给自己定的学习路径是:Dart 语法基础 -> 简单 Widget -> 状态管理 -> 网络与异步 -> 平台适配。第一天只走第一步。我不要求自己看完所有 Dart 语法,只要求把变量声明和常用类型过一遍,并且能写出来、能解释清楚。这个“能解释清楚”特别重要,因为如果你能用自己的话向别人解释 var 和 dynamic 的区别,说明你是真的理解了。
这个星际指挥中心模型就是我用来“向自己解释”的工具。它不复杂,但很有效。我建议你也给自己的学习内容造一个类似的模型,哪怕是一个咖啡店、一个超市、一个图书馆,都行。目的就是让抽象概念有一个具体抓手。
1.2 这个模型到底怎么用
我先说结论:模型不是用来画在纸上的,而是用来把知识点挂上去的。每学一个语法,就问一句:如果我在指挥中心里,这会是什么?学 int 的时候,我就想象指挥中心里有一个“当前可出动舰船数量”的读数,这个读数只能是整数,不可能是 3.7 艘。学 double 的时候,我想到“反应堆能量储备”,它可以是 87.5%。学 String 的时候,我想到通讯频道里的消息。学 bool,我想到护盾升高或降下,只有开和关两种状态。
这样一来,变量和现实对象之间就有了一一映射关系。等后面学 List、Map 时,我甚至能直接用职权表或者旗语编号来理解。这个模型还有一个好处:它能帮我建立“数据状态”的概念。指挥中心不可能只看一个数字,它需要同时管理能量、舰船数量、人员名单、后勤物资,所以一个完整应用也不可能只用一个变量,而是需要一堆变量配合。变量之间的组合关系,就是应用的数据底座。
2. 环境准备:先把 Flutter 跑起来
第一天的任务虽然以语法为主,但环境还是要先搭好的,不然处处是“纸上谈兵”。我这里分享的是我自己的搭建过程,比较贴近普通学习者的路径,不是纯鸿蒙开发的专业路线。简单说:我先把标准 Flutter 环境跑通,用 Android/iOS 模拟器验证 Dart 逻辑,后面再切到鸿蒙侧联调。这样第一天不会被双平台工具链的复杂性拖垮。
2.1 工具链组成与版本理解
Flutter 环境其实有三个层次:Flutter SDK、Dart SDK、各平台构建工具。Flutter SDK 会自动带对应版本的 Dart,所以你不需要单独装 Dart。Dart 是 Flutter 的语言底座,变量和基本类型的规则都发生在 Dart 这个层面。各平台构建工具则负责把发码编译成目标平台的东西,Android 需要 Android SDK,iOS 需要 Xcode,鸿蒙则需要对应集成工具。对于我等学习者来说,最稳的做法是先让 Flutter 自带的检查器告诉你缺什么,再一项项补。
我建议安装完 Flutter SDK 后,先运行flutter doctor。这个命令会检查你的环境里缺了哪些依赖,比如 Android toolchain 没配好、Android licenses 没接受、Xcode 没装等。它会直接给出提示。注意:不要看到提示就慌,大部分问题都是工具路径没配置好,或者许可证没签署。我之前有一次就是 Android licenses 没接受,导致项目一直创建失败,运行flutter doctor --android-licenses后一键解决。
如果你后面要跑鸿蒙设备,我建议单独查阅当前 Flutter 版本配合鸿蒙工具链的说明。鸿蒙侧的适配和官方 Android/iOS 不完全一样,需要额外下载适配组件。但第一天我们只学 Dart 语法,先不用把这部分全部装上,否则光环境就要折腾两天。
2.2 创建第一个项目
环境检查通过后,创建一个项目。我用的是:
flutter create command_center这个命令会在当前目录下生成一个名为 command_center 的 Flutter 项目。如果想限制平台,可以加--platforms=android,ios,这样就不会生成暂时用不到的目录。不过如果你是计划好要做鸿蒙的,可以先用默认的完整结构跑起来,后面再按鸿蒙适配要求补充。项目创建完成后,进入目录运行:
flutter run前提是你已经打开了一个模拟器或者连接了真机。运行起来后,你会看到终端里滚动日志,然后手机会弹出一个默认的计数器应用。这时候按一下键盘上的r,可以进行热重载;按R是全量重启;按q退出。很多 Flutter 初学者第一次看到终端里的r交互会不知所措,我建议你把这个三个按键记在本子上,它们就是你开发期间用得最多的操作。
2.3 项目骨架与入口文件
创建好的项目里面,最核心的是lib/main.dart。Flutter 启动时会找这个文件的main()函数作为入口。什么概念呢?就像指挥中心的总电源开关,整个应用的一切都从这个函数启动。第一天我们不需要理解 main.dart 里面那些 Widget 代码,只需要知道一件事:这个文件中可以写变量,也可以打印输出。
如果你打开 main.dart,会看到默认代码有一堆MaterialApp、Scaffold、MyHomePage。这些是框架模板,先不要改动。为了清空心智负担,你可以把 main.dart 的内容暂时替换成下面这样,确保环境真的能用,也让我们可以随心所欲练习变量:
void main() { print('指挥中心准备就绪'); }如果你运行后能在终端看到这行字,说明环境完全没问题。后面所有变量和基本类型练习,都可以在main()函数里进行。等掌握基础语法后再回到 UI,就不容易被模板代码干扰。
3. 变量声明:Dart 的四种“接线方式”
接下来是正餐。Dart 里声明变量的方式有好几种,分别是var、final、const,还有dynamic和类型显式声明。这些方式可不是随便用的,它们决定了变量能不能修改、什么时候确定值、允许多大的灵活性。我把它想象成指挥中心的四类接线方式:有些线是临时插上的,有些线是永久固定住的,有些线是出厂就焊死的,有些线可以乱接但很不安全。
3.1 var 和类型推导
var应该是你用得最多的关键字。它叫“类型推导”,意思是编译器会根据你赋的初始值,自动推断出变量的类型,之后这个变量就只能存放该类型的数据。举个例子:
var energy = 87.5; var squadCount = 3; var centerName = '曙光指挥中心';这里 Dart 会自己推断energy是double,squadCount是int,centerName是String。你不用手写类型,但它的静态类型已经被锁定了。如果你后面试图给squadCount赋一个字符串,比如squadCount = 'abc',Dart 会直接编译报错,因为它已经被推断为整数类型了。
这里有个常见误区:有人以为var等于动态类型,其实不是。var一旦推断出类型,类型就固定了。想真正让变量可以随便改变类型,要用dynamic,但那是另一个故事。在实际开发里,优先用var或者显式类型,因为它能让编译器帮你检查错误。命令中心里,每个仪表盘代表什么量就固定什么值,这样才不会把所有数据全糊成一团。
3.2 final 和 const:只读变量的两种层次
final和const都表示“不能重新赋值”,但它们的层析不同。final是运行时初始化后不可修改,const是编译期就必须确定的常量。我用的记忆方法是:final是舰船在出发时固定的舷号,const是舰船图纸上刻死的型号规格。舷号只有拿到船才知道,但型号在造这艘船之前就定好了。
final String centerId = 'ALPHA-477'; const int protocolVersion = 1;第一行的centerId,你在程序启动后可以给它赋值,但是赋值之后不允许再改。第二行的protocolVersion,必须在编译期就是一个确定的值,所以你不能写const int currentTime = DateTime.now().millisecondsSinceEpoch;,因为这句话要运行时才能知道时间,DateTime.now()不是编译期常量。如果你有这样的需求,应该用final int currentTime = DateTime.now().millisecondsSinceEpoch;。
还有一种细节要特别注意:final和const限制的是“变量重新赋值”,不是“对象内部内容修改”。比如:
final List<String> squadList = ['侦察编队', '医疗编队']; squadList.add('护卫编队');这段代码是合法的。squadList这个变量本身还是指向同一个列表,但列表内部的元素可以增加。const就不一样了,const创建的是一个编译期不可变对象,比如const List<String> fixedList = ['x'];之后再调用fixedList.add('y')会运行时报错。这个区别在写命令中心的编队列表时很关键:编队的成员可以变,但编制权限本身不能变。
3.3 dynamic、Object 和 null 安全
Dart 里还有两种“宽松型”声明方式:dynamic和Object。外加一个很重要的概念:null 安全。
先说dynamic。用它声明变量后,这个变量可以接收任何类型的值,且在编译期不会做类型检查。听起来很方便,但我基本不建议在日常代码里用。你想想,如果指挥中心有一条通讯线缆可以传送文字、数字、图片、语音,接受端怎么知道当前来的是什么?大概率会解错码。dynamic就是这条“乱码线路”。用久了代码全是坑。而Object则是一个更上层的类型,它可以引用任何非空对象,但如果你想调用对象上特有的方法,必须显式取出来并转换类型,编译器会保护你。
null 安全可以这样理解:普通变量默认不允许为空。如果你写:
String signal = null;会报错,因为String类型不能为 null。如果某个变量确实可能暂时没有值,你需要把它声明为可空类型,加一个问号:
String? unknownSignal; int? sensorValue;在命令中心的场景里,这就像有些传感器的信号会丢失,你没法保证每次都能读到数值。所以可空类型是“允许没信号”的通道。使用可空变量时,你要先判断一下是不是 null 才能用,否则编译器会拦着你。这种设计其实是在帮你减少空指针问题,刚开始不习惯,多写几天就会爱上。
3.4 变量作用域:指挥大厅的“广播范围”
变量并不是定义出来就能全局都用,它有自己的作用域。简单说,花括号{}决定了一块变量的可见范围。在函数内部声明的变量,只能在这个函数里用;在 main.dart 最外层声明的变量,整个文件都能看到。这个原则和指挥中心一样:总指挥的命令整栋楼都能听到,但一个舰长在舱室内给队员的命令,只在他那个舱室有效。
void main() { var tempSignal = '内部信号'; // tempSignal 只能在这里用 }如果你试图在 main 外面写代码访问tempSignal,那肯定找不到。作用域的概念让程序更安全,因为你不小心把一段内部数据广播到了全局,很容易出问题。Day 1 阶段,你只需要记住:变量尽量在靠近使用它的地方声明,不要一股脑全堆在全局。
4. 基本类型:给指挥中心配好数据通道
Dart 的常用基本类型不多:数值、字符串、布尔、列表、映射、集合。这些类型如果放到星际指挥中心模型里,每一个都对应一种清晰的画面。下面我用这个模型一个个过。
4.1 数值类型:能量值、坐标和战损
数值类型最常用的是int和double,它们的父类型是num。int表示整型,比如舰船数量、信标数量、任务剩余秒数,不可能是小数。double表示浮点型,比如能量百分比、坐标精度、护盾衰减率。
int shipCount = 5; double energyRate = 0.87; num mixedValue = 10;写代码的时候要留意:double能接收小数,也可以接收一个整数字面量,但反过来不行。例如int x = 3.7;直接报错。如果你有一个 double 结果想转成 int,可以用toInt(),但要注意这是直接截掉小数部分,不是四舍五入:
double shieldRate = 0.64; int percent = (shieldRate * 100).toInt(); // 64如果用round()就会绕过四舍五入。实战中这类转换很常见,比如后端返回的消耗数字可能带小数,你显示进度条就经常要转 int。另外,Dart 的除法结果类型也容易踩坑。/运算不管操作数是 int 还是 double,结果都是 double;如果你想拿到整数商,需要~/:
int groupCount = 10 ~/ 3; // 3这是一种整除运算,适合用来把人或资源平均分组。取余数用%。这些运算规则听起来基础,但很多人在实战里因为它导致界面闪错,所以我特意列出来。
4.2 字符串与布尔:信号和开关
字符串String是文本数据,可以包含数字、中文、英文、符号。它就是一则通讯消息。定义的时候用单引号或双引号都行,更推荐单引号,因为更简洁。多行文本用三引号:
String centerName = '曙光指挥中心'; String report = ''' 当前状态: 能量 87.5% 护盾 开启 ''';字符串拼接和插值是写 Flutter 时天天用的能力。以前有些语言需要写一堆加号,Dart 可以直接在字符串里写${expression}:
String statusMessage = '中心$centerName 能量$energyRate%'; String multi = '舰船数量:$shipCount,百分比计算:${shipCount * 10}';我在命令中心模型里,把${}想象成“信号占位符”。你要把当前仪表盘的数值实时播报给大厅,就可以用这种方式塞进去。注意:如果表达式是简单的变量名,可以省略花括号写成$centerName;如果是复杂表达式、方法调用、算术运算,就必须写${...}。
布尔类型bool只有true和false两个值。它不是像 JavaScript 那样有“真值”和“假值”的判断,Dart 里 if 条件必须是一个布尔值,不能直接判断一个整数是否为真。比如:
bool isShieldOnline = true;bool通常用于开关状态、标记位、权限判断。在指挥中心里,护盾、雷达、通讯加密都是典型的 bool 类型:开就是 true,关就是 false。后面写 if 语句、循环、甚至网络请求的回调,到处都会用到 bool。
4.3 集合类型:编队列表、物资表和信标集合
集合类型是 Flutter 数据结构的重头戏。最常用的是List和Map,其次是Set。
List是有序列表,就像编队花名册。它有索引,从 0 开始。比如:
List<String> squad = ['侦察编队', '护卫编队', '医疗编队']; print(squad[0]); // 侦察编队这种类型适合存放一组同类型的东西:舰船编号、任务目标列表、日志记录。你可以用.add()增加成员,用.remove()删除成员,用.length获取数量。如果所有成员都是同一个类型,强烈建议声明成List<String>而不是List,这样往里塞错了类型编译器会直接提示。
Map是键值对集合,像物资清单:一个名称对应一个数量。实际场景缓存数据时 Map 很常见。例如:
Map<String, int> supplies = { 'energyBlock': 42, 'medicalKit': 12, 'ammoBox': 8, };你可以用supplies['energyBlock']拿到 42,如果键不存在则返回 null。这里要注意,除非你声明 Map 的 value 是可空类型,否则读取时编译器可能会认为结果是int?,接下来想直接使用就得多一步空判断。不过 Day 1 阶段,先会用查表就行。
Set是元素不重复的集合,适合信标编号、权限标识这种不能重复的数据。比如:
Set<String> beacons = {'alpha', 'bravo'}; beacons.add('alpha'); // 不会增加,因为已经存在它和 List 最核心的区别就是不保证顺序且自动去重。如果你在指挥中心里要记录“哪些空间站信号已接入”,用 Set 就很合适,重复注册的信号会被自动忽略。
4.4 类型转换与运算符上的常见坑
变量类型之间不是随便改的。比如字符串转数字要用int.parse('42'),但如果字符串里不是合法数字,比如int.parse('abc'),会直接抛异常。这时候用int.tryParse('abc')更安全,它无法解析时返回 null,不会崩。反过来,数字转字符串很简单,直接.toString()就行。
有个很容易忽略的坑:数字类型和字符串类型用+拼接时,Dart 不会自动做类型转换。你不能写:
var message = '能量剩余:' + energy + '%'; // 报错因为energy是 double,不能直接和 String 相加。要么把 energy 转成字符串,要么直接用字符串插值。我在第一天上手时被这个报错教育过,之后每次拼字符串第一反应都是插值,很少用+。
还有运算符优先级问题,在字符串插值里很容易踩。'结果是$energy + 2'会输出 87.5 + 2,而不是先计算 89.5。因为 Dart 只把$energy当作值,后面的+ 2是普通文本。想要先计算必须写'结果是${energy + 2}'。这个细节看起来小,但调试时很迷惑人。
5. 实战:写一个“曙光指挥中心”的状态底座
环境跑通了,语法也过了一遍,这时候就该动手写点像样的东西。我不写抽象示例,直接写一个指挥中心的初始状态底座。这不算完整 UI,但它是将来界面的数据来源。你把这份代码替换到 main.dart 里运行,终端会打印出指挥中心的状态信息。
5.1 需求与代码
假设我们要建立“曙光指挥中心”的基础信息:编队主力数量、能量总值、护盾在线状态、当前舰队列表、物资清单、已接入信标集合,以及一个可能没有信号的未知传感器。把这些不同种类的信息用合适的变量类型表达出来,就是下面的代码:
void main() { const int maxSquad = 12; final String centerName = '曙光指挥中心'; var energy = 87.5; double shieldPercent = 0.64; bool isShieldOnline = true; List<String> squadList = ['侦察编队', '护卫编队', '医疗编队']; Map<String, int> supplyMap = { 'energyBlock': 42, 'medicalKit': 12, 'ammoBox': 8, }; Set<String> activeBeacons = {'orbital-1', 'orbital-2'}; int? unknownSignal = null; print('指挥中心:$centerName'); print('编制上限:$maxSquad'); print('能量:$energy'); print('护盾能量:${(shieldPercent * 100).toStringAsFixed(0)}%'); print('护盾在线:$isShieldOnline'); print('编队数量:${squadList.length}'); print('物资:$supplyMap'); print('已接入信标:$activeBeacons'); print('未知信号:$unknownSignal'); }运行后,终端会依次打印这些状态。这个过程不是让你背代码,而是让你观察每种类型在真实运行时的输出模样。你会发现 List 的输出是[侦察编队, 护卫编队, 医疗编队],Map 是键值对结构,Set 是无序去重后的集合。亲眼看到这些输出,比读十遍文档都管用。
5.2 一行一行拆解
这里选几个关键声明拆开说。const int maxSquad = 12;是编译期常量,因为它设定的是编制上限,在系统设计时就已经写死,运行时不该改变。final String centerName = '曙光指挥中心';是运行时常量,理论上中心名称可能在启动时从配置文件读取,但读出来以后不再修改。用final比用var更安全,能防止后面代码不小心把 centerName 改了。
var energy = 87.5;我故意用 var,因为它仍然会被推断成 double,但它比显式写double energy多了一点“后面我会改”的心理暗示。在 Flutter 实际开发里,能量值一定会在游戏过程中变化,不用担心 var 限制类型,只要新值也是 double 就行。double shieldPercent = 0.64;写成显式 double,因为 0.64 这种浮点量很明确,数值类型一眼能看出来。
int? unknownSignal = null;是可空类型变量,模拟一个暂时没有信号的传感器读数。它现在为空,但以后可能被赋予整数值。打印它时会显示 null,这个正常。如果你在后面要读取它,不能直接参与算术,必须先判断是否为空,否则编译器会警告。
这段代码里还有一个我建议你亲手体验的点:修改energy的初始值,改成 120,然后在终端热重载r,你会看到打印结果立刻变化。这个反馈速度非常快,几乎下一秒你就能理解“变量是变化的量”这个本质。
5.3 如果要在界面上显示这些状态(小预告)
现在代码只是打印到终端,但 Flutter 的核心是 UI,你迟早得把这些数据显示在屏幕上。等第 2 天我开始接触 Widget 时,会把这些变量塞进Text组件里,做成一个真正的状态面板。这里我先不展开,但你心里要有底:今天我们定义的每一条变量,以后都会变成界面上的一个字或者一个进度条。
所以第一天写“模型”意义重大。它不是浪费时间的想象力练习,而是提前把应用的数据地基夯实了。等到写 UI 时,你不需要边写边想“这个值该存成 List 还是 Map”,因为这些决策今天已经想明白了。
6. 第一天踩过的坑:环境与语法排查实录
第一天虽然内容不复杂,但我实际学习中还是遇到了不少问题。这里我写下几个真实遇到的坑和排查方法,给同样刚起步的你做参考。这些内容都不是常规教程会写的,但往往就是卡住新手最久的地方。
6.1 Flutter 新建项目跑不起来的几个原因
我最早遇到的是“flutter 新建项目后跑不起来”。看着像环境问题,但原因五花八门。最常见的是网络问题,因为 Gradle 需要下载很多依赖包,一旦网络不畅,创建项目就是卡在 “Running Gradle task ...”,最后超时报错。这时候不要反复删项目重建,先检查几个点:
flutter doctor -v是否全部通过;flutter devices能否检测到模拟器或真机;- 确认当前目录没有非法字符。
如果你的项目目录里包含中文或者空格,有时候也会导致构建失败,这个坑我确实见过。最好把项目目录统一放到纯英文路径下,比如D:\dev\command_center。
另一种情况是模拟器启动了,但flutter run依然报错,终端里出现一堆红字。红字不一定都是 Dart 代码错误,很多时候是平台层的 Gradle 脚本问题。比如有次报错是关于 “Applying Flutter’s main Gradle plugin imperatively”,这多半是项目生成时的 Flutter 版本和当前 Gradle 配置不匹配。如果项目不是我手动改过的,我先不碰 Gradle 脚本,直接尝试把项目删除重建,用当前 Flutter 版本重新生成,问题往往就消失了。
还有一条:如果终端出现类似E/flutter (31173): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] unhandled exception,这种红字意味着应用运行到一半,Dart 代码里抛出了一个未捕获异常。它不是环境没配好的信号,而是程序逻辑问题。你要往日志上翻,找 “Unhandled Exception” 后面那行,那才是错误核心。有可能是某个变量为空,或者访问了不存在的数组下标,或者解析字符串失败。
6.2 Dart 语法和类型相关的编译报错
第一天的语法坑也有不少。最让我印象深刻的是var类型固定后,再给变量赋非兼容值会报错。比如我原本写var shieldOnline = 1;然后想shieldOnline = true;,这是不允许的,因为 Dart 已经推断出它是 int 类型,bool 值放不进去。这也是我前面反复强调var不是动态类型的原因。
还有一个类型坑是集合的元素类型不一致。比如:
List<String> items = ['x']; items.add(42);第二行直接编译失败,因为 items 声明的是字符串列表。这个报错其实是在保护你,它防止你在指挥中心的编队名单里塞进一个数字编号。刚开始你可能会觉得烦,觉得 “为什么要管这么严”,但写多了你就会知道这种严格带来的省心。
字符串插值少一双花括号也会出问题。我的教训是:复杂表达式一定要用${}包住。比如print('能量增加后:$energy + 10'),输出不是你想要的 97.5,而是一段文本。别怀疑,这就是最真实的初学者体验。每踩一次这种坑,你对 Dart 的求值规则理解就深一层。
6.3 我把排查步骤固定成了标准动作
踩了几次坑之后,我总结了一个固定的排查节奏,每次遇到问题都按这个顺序来:
- 先看终端里第一处标红信息,不要被后面一大片跟随日志吓住;
- 自己在代码里找“可能为空但被直接使用”的变量;
- 跑一遍
flutter analyze,这个静态检查能指出很多类型隐患; - 如果环境问题,运行
flutter doctor -v; - 最后还不行,关闭模拟器,重新启动并
flutter run。
这个方法不一定是最优的,但对新手来说很管用。特别是flutter analyze,它在编译前就能告诉你代码哪里有问题,而且提示通常比编译器输出的更友好。我建议你在写完今天的代码后,都执行一下flutter analyze,看着它输出 “No issues found”,才算真正完成了当天的练习。
7. 第二天之前,我建议你做一次“回看”
学习是个反反复复的过程,第一天别看内容简单,其实信息量不小。我在学完变量和基本类型后,没有急着看 Widget,而是把今天的代码重新读了一遍,然后闭着眼睛问自己几个问题:
- var、final、const 三者的核心区别是什么?
- dynamic 为什么不适合当一个常规变量来用?
- null 安全的问号到底加在哪里?
- List、Map、Set 分别在什么场景下用?
如果这些问题你能不看笔记答上来,说明第一天过关了。答不上来也没关系,再回头把今天的例子重新敲一遍,敲到能顺手写出为止。我个人在实际学习中的体会是:编程不是看会的,也不是背会的,是敲出来的。变量和基本类型更是如此,你闭着眼睛都能写出来的时候,后面学 Widget 才会觉得一路畅通。
最后再分享一个小技巧:把你今天写的“曙光指挥中心”代码保存成一个备份,第二天学控制流和函数时,可以直接在这个模型上扩展。比如用 if 判断护盾值低于 30% 就输出警告,用 for 循环遍历编队名单。同一个模型反复用,能让你把每天学的零散知识点串成一条线。这比每天新开一个 demo 要有效得多。