上一节,我们完成了Flutter的环境搭建。本节,我们开始搭建项目,简单了解Flutter及其基础组件。
- 项目创建
1.1 命令行创建
1.2 Android Studio创建 - 熟悉工程
2.1 简单实现
2.2 熟悉widget
2.3 Text
2.4 MaterialApp
2.5 ListView 列表视图 - 常用基础组件
3.1 基础文本
3.2 富文本
3.3 基础容器Container
1. 项目创建
1.1 命令行创建
以前
不支持驼峰写法,需要通过小写字母+下划线_命名,但是现在支持哦
flutter create flutter_demo
- 创建成功

- 按照指令,到
指定文件夹去运行项目:
cd FlutterDemo
flutter run
注意:
- 如果此时
未打开模拟器,会提示需要选择一个模拟器。- 如果此时打开了
多个模拟器,也会提示您选中一个模拟器来运行。
image.png选中模拟器后,flutter会自动使用Xcode工具进行编译。
image.png- 如果需要
真机调试,我们需要手动打开项目工程,去Xcode配置证书
终端运行Flutter命令键:Flutter run key commands. r Hot reload. 🔥🔥🔥 热重载(比对被修改部分,更新被修改代码) R Hot restart. 热重启(所有资源重新加载) h Repeat this help message. d Detach (terminate "flutter run" but leave application running). c Clear the screen q Quit (terminate the application on the device). s Save a screenshot to flutter.png. b Toggle the platform brightness setting (dark and light mode). (debugBrightnessOverride) w Dump widget hierarchy to the console. (debugDumpApp) t Dump rendering tree to the console. (debugDumpRenderTree) L Dump layer tree to the console. (debugDumpLayerTree) S Dump accessibility tree in traversal order. (debugDumpSemantics) U Dump accessibility tree in inverse hit test order. (debugDumpSemantics) i Toggle widget inspector. (WidgetsApp.showWidgetInspectorOverride) I Toggle oversized image inversion 🖼️. (debugInvertOversizedImages) p Toggle the display of construction lines. (debugPaintSizeEnabled) o Simulate different operating systems. (defaultTargetPlatform) z Toggle elevation checker. g Run source code generators. M Write SkSL shaders to a unique file in the project directory. v Launch DevTools. P Toggle performance overlay. (WidgetsApp.showPerformanceOverlay) a Toggle timeline events for all widget build methods.
- 如果使用
安卓模拟器,可以在顶部控制栏配置模拟器:
image.png选中模拟器,运行:
image.png
- 由于
个人习惯,我更喜欢在Xcode中开发,在终端使用命令运行。执行flutter run时,会让我们选择想使用的模拟器(后续我主要使用iPhone模拟器)。
1.2 Android Studio创建
- 除了使用
flutter create命令创建项目,我们还可使用Android Studio创建,也可以使用VSCode创建,它们都有flutter插件。
- 啥? 你问
Xcode是否可以创建?
想想苹果与谷歌的竞争者关系,就知道苹果不可能做这样的支持插件的。😂
1.2.1 使用Android Studio创建

-
四种创建方式:

-
项目
基本信息
image.png -
项目
唯一标识和支持的平台
image.png -
点
Finish后,会进行网络请求,拉取资源,创建成功。
(如果没配置镜像,拉取资源非常缓慢。 上一节有介绍如何配置镜像)
image.png
坑点
如果Android Studio正在运行项目,我们command + Q强制退出。下次打开Android Studio时,会回到当时的缓存。如果缓存成功找回,会运行正常。如果缓存找不到,会导致无法运行,而且新建工程也无法运行。处理方式:
删除flutter目录下cache缓存文件夹中的lockfile文件,再运行项目即可。(相当于XCode的Clean操作)# pwd请替换为自己flutter的文件目录 rm /pwd/flutter/bin/cache/lockfile
2. 熟悉工程
-
创建项目后,可以看到main.dart有很多代码。我们最快熟悉的方式是:全部删除,手动实现并分析:
image.png
2.1 简单实现
Flutter中,我们使用的开发语言是Dart,现在我们先体验,完整的Dart语法,我们可以去官网了解
- 手动实现:
// 以下代码,均使用Dart语言编写
// 资源包 (可以理解为我们iOS的UIKit)
import 'package:flutter/material.dart';
// main入口函数 (就像iOS main.m中的main函数)
void main() {
// runApp(app); // runApp就像iOS的 UIApplication, 而app就像我们的根控制器
// flutter中没有控制器和视图的概念,都是widget组件。
runApp(
Center( //我们用Center部件,自动居中展示。这是Center的构造函数
// child就像subView
// Text是一种文本框样式,设置默认值和显示方向(ltf: left to right 从左到右)
child: Text('Hello', textDirection: TextDirection.ltr,),
)
);
}
- flutter
运行,文本框成功展示:
image.png
2.2 熟悉widget
Widget: 作用类似于OC中的UIView,是小部件。分为两大类:
StatelessWidget:无状态组件。快捷键stless。
创建后就决定了样式,状态更改需要手动创建新widget。StatefulWidget: 有状态组件。快捷键stful。
本质上也是无状态组件,渲染时无状态,因为UI本身是无状态的。但它会保留组件状态,记录组件状态的属性改变时,会自动重新渲染。直到该组件完全销毁,才会释放记录的属性
2.2.1 无状态组件(StatelessWidget)
- 我们将
main入口的Center组件改成自定义的MyWidget无状态组件。
import 'package:flutter/material.dart';
void main() {
runApp(
MyWidget(), //()是构造函数,类似C++。
);
}
// 无状态Widget快捷键是 stless
// 一个Widget,就是一个class类
class MyWidget extends StatelessWidget {
@override
// build:会将小部件放到渲染树中去
// 渲染树: 会从main入口的第一个widget开始渲染,然后以树结构依次向下渲染组件
// 所有widget都必须有build方法,build返回的是什么,MyWidget在外界渲染的就是什么
Widget build(BuildContext context) {
return Center(
child: Text(
'Hello Flutter2',
textDirection: TextDirection.ltr,
),
);
}
}
- 选择
模拟器,debug运行,模拟器启动后,可以修改文本内容,点击热重载按钮,感受热重载的强大。

reload热重载功能,是通过比较新旧代码变化,来更新被改动部分代码的。 但是有些场景下热重载是失败的,只能restart重启才可以。具体场景,参考Flutter官网介绍
Dart语言简写:
- Dart语言中,如果
函数只有一行内容时,可以使用=>缩写:// 改动前 //void main() { // runApp(MyWidget()); //} // 简写: void main() => runApp(MyWidget());
2.3 Text
-
Text是一个文本组件,是StatelessWidget不可变组件。
2.3.1 构造函数与参数
- 以
Text为例,构造函数是Text(),包含必选参数和可选值,可选值可以赋默认值:
image.png
2.3.2 final和const修饰符
final和const都类似于Swift的let,是不可变的。
-
final可以不赋初始值,运行时再赋值。 -
const必须在创建时就赋值。
比如Text中使用频率最高的style和textAlign,都是final声明,因为Text本身是StatelessWidget不可变的组件。

- 给
Text加入样式,使用变量创建TextStyle,_下划线表示私有变量
import 'package:flutter/material.dart';
// 入口,展示MyWidget组件
void main() => runApp(MyWidget());
class MyWidget extends StatelessWidget {
@override
// build 确定组件返回的内容
Widget build(BuildContext context) {
// final创建一个_textStyle不可变变量,_开头的属性,表示私有属性
final _textStyle = TextStyle(color: Colors.red, fontSize: 40.0, fontWeight: FontWeight.bold);
return Center(
child: Text(
'Hello Flutter',
textDirection: TextDirection.ltr,
style: _textStyle, // 直接使用变量
),
);
}
}
Text和TextStyle相关属性和参数,可以Command + 鼠标左键查看

2.4 MaterialApp
-
MaterialApp:Flutter推荐方式,提供快速构建APP的方式(包括导航栏、内容、主题等)
home: 类似于根控制器,需要指定一个widget
Scaffold: 类似于导航控制器NavigationController,包含导航栏相关属性。是一个可变组件statefulWidget
appBar:导航栏
body: 内容theme:主题,可控制主题色
import 'package:flutter/material.dart';
// 入口,展示MyWidget组件
void main() => runApp(App());
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
// MaterialApp:Flutter推荐方式,提供快速构建APP的方式(包括导航栏、内容、主题等)
return MaterialApp(
// home: 类似于根控制器,也是需要指定一个widget
// Scaffold: 类似于导航控制器NavigationController,包含导航栏相关属性。是一个可变组件statefulWidget
home: Scaffold(
// appBar:导航栏
appBar: AppBar(
title: Text('Flutter Demo'),
),
// body: 内容
body: MyWidget(),
),
// theme:主题,可控制主题色
theme: ThemeData(
primaryColor: Colors.yellow
),
);
}
}
class MyWidget extends StatelessWidget {
@override
// build 确定组件返回的内容
Widget build(BuildContext context) {
// final创建一个_textStyle不可变变量,_开头的属性,表示私有属性
final _textStyle = TextStyle(color: Colors.red, fontSize: 40.0, fontWeight: FontWeight.bold);
return Center(
child: Text(
'Hello Flutter',
textDirection: TextDirection.ltr,
style: _textStyle, // 直接使用变量
),
);
}
}

2.5 ListView 列表视图
-
列表视图:类似iOS的UITableView,但是没有Section的概念。
2.5.1 准备Model
在布局之前,先准备数据:
- 新建
Model文件夹,新建car.dart文件:
image.png
// 不需要导入material.dart,因为Car直接继承Object
// Car 模型
class Car {
// 构造函数: {}内是可选值
const Car({
this.name,
this.imageUrl,
});
// 名称
final String name;
// 图片链接
final String imageUrl;
}
final List<Car> datas = [
Car(
name: '保时捷918 Spyder',
imageUrl:
'https://upload-images.jianshu.io/upload_images/2990730-7d8be6ebc4c7c95b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240',
),
Car(
name: '兰博基尼Aventador',
imageUrl:
'https://upload-images.jianshu.io/upload_images/2990730-e3bfd824f30afaac?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240',
),
Car(
name: '法拉利Enzo',
imageUrl:
'https://upload-images.jianshu.io/upload_images/2990730-a1d64cf5da2d9d99?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240',
),
Car(
name: 'Zenvo ST1',
imageUrl:
'https://upload-images.jianshu.io/upload_images/2990730-bf883b46690f93ce?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240',
),
Car(
name: '迈凯伦F1',
imageUrl:
'https://upload-images.jianshu.io/upload_images/2990730-5a7b5550a19b8342?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240',
),
Car(
name: '萨林S7',
imageUrl:
'https://upload-images.jianshu.io/upload_images/2990730-2e128d18144ad5b8?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240',
)
];
2.5.2 设置ListView
- 如果直接导入
Car模型,需要导入头文件
三种导入
头文件的方式
直接手写,在顶部import左键点击Car,出现红色小灯泡,点击import Library鼠标光标放在Car上,按住Option+Enter键,再按一次Enter键,可快捷导入import Libraryfinal List<Car> datas = []
-
Container: 类似于html的div,也类似于iOS的UIView,就是用来放东西的。需要设置大小(也可以被子控件撑出大小) -
Column: 内容垂直排列的容器 -
row: 内容水平排列的容器 -
stack: 内容重叠的容器 -
Image:图片的可变组件,network加载网络图片 -
SizedBox:空容器,有大小。(有时为了便于内部插入元素,会直接使用Container) -
MaterialApp的debugShowCheckedModeBanner:隐藏导航栏Debug角标
import 'package:flutter/material.dart';
import 'Model/car.dart';
// 入口,展示MyWidget组件
void main() => runApp(App());
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
// MaterialApp:Flutter推荐方式,提供快速构建APP的方式(包括导航栏、内容、主题等)
return MaterialApp(
// 隐藏导航栏Debug角标
debugShowCheckedModeBanner: false,
// home: 类似于根控制器,需要指定一个widget
home: Home(),
// theme:主题,可控制主题色
theme: ThemeData(
primaryColor: Colors.yellow
),
);
}
}
// Home 组件
class Home extends StatelessWidget {
// 回调函数,返回widget组件
Widget _cellForRow(BuildContext context, int index) {
// Container类似于html的div,也类似于iOS的UIView,就是用来放东西的
// 需要大小(也可以被子控件撑出大小)
return Container(
color: Colors.white,
// height: 20,
margin: EdgeInsets.all(10), //EdgeInsets.only(top: 1),
// 子控件
// Column 内容垂直排列的容器 row 内容水平排列的容器 stack 内容重叠的容器
// Image图片可变组件,network加载网络图片
child: Column(
children: <Widget>[
Image.network(datas[index].imageUrl),
SizedBox(height: 8,),
Text(
datas[index].name,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18.0,
fontStyle: FontStyle.italic),),
SizedBox(height: 8,)
]
)
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[100],
appBar: AppBar(
title: Text("Flutter Demo"),
),
// ListView 列表组件(没有iOS的Section概念)
body: ListView.builder(
// cell个数
itemCount: datas.length,
// cell内容(等同与cellForRow)build 是渲染
// iOS中是使用代理和协议完成,这里是直接使用回调函数,有两个入参
itemBuilder: _cellForRow,
),
);
}
}
// 模型数组
// 没导入头文件时,会提示需要导入Car头文件
// 三种导入头文件的方式
// 1. 直接手写,在顶部import
// 2. 左键点击Car,出现红色小灯泡,点击import Library
// 3. 鼠标光标放在Car上,按住Option+Enter键,再按一次Enter键,可快捷导入import Library
// final List<Car> datas = []
-
效果展示:
image.png 实际开发中,我们可以将
ListView内容抽离出来,做成单独文件listView_demo.dart:
import 'package:flutter/material.dart';
import 'car.dart';
class ListViewDemo extends StatelessWidget {
// 回调函数,返回widget组件
Widget _cellForRow(BuildContext context, int index) {
return Container(
color: Colors.white,
margin: EdgeInsets.all(10)
child: Column(
children: <Widget>[
Image.network(datas[index].imageUrl),
SizedBox(height: 8,),
Text(
datas[index].name,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18.0,
fontStyle: FontStyle.italic),),
SizedBox(height: 8,)
]
)
);
}
@override
Widget build(BuildContext context) {
return ListView.builder(
// cell个数
itemCount: datas.length,
itemBuilder: _cellForRow,
);
}
}
- 在
main.dart中,直接使用我们封装的ListViewDemo(导入头文件)即可:
import 'package:flutter/material.dart';
import 'Model/listView_demo.dart';
// 入口,展示MyWidget组件
void main() => runApp(App());
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
// MaterialApp:Flutter推荐方式,提供快速构建APP的方式(包括导航栏、内容、主题等)
return MaterialApp(
// 隐藏导航栏Debug角标
debugShowCheckedModeBanner: false,
// 根组件
home: Home(),
// 主题
theme: ThemeData(
primaryColor: Colors.yellow
),
);
}
}
// Home 组件
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[100],
appBar: AppBar(
title: Text("Flutter Demo"),
),
// 列表
body: ListViewDemo(),
);
}
}
3. 常用基础组件
- 上面讲了
简单封装,我们现在对几个基础组件进行简单封装:
3.1 基础文本
- 新建一个
base_widget.dart文件,创建TextDemo组件:
- 通过
属性声明变量(内容和样式),_开头的属性为私有属性。$+属性名:快捷插入变量内容。 如果后面有其他字符等信息,可使用{}包裹起来。Text可设置maxLines最大行数,超出部分样式通过overflow设置(ellipsis为尾部省略号)
import 'package:flutter/material.dart';
//【普通文本 Demo】
class TextDemo extends StatelessWidget {
// 文本样式(私有属性)
final TextStyle _textStyle = TextStyle(
fontSize: 24.0,
);
final String _title = 'Flutter入门';
final String _author = 'HT';
@override
Widget build(BuildContext context) {
// $ + 属性名: 快捷插入变量内容。 如果后面有其他字符等信息,可使用{}包裹起来。
return Text(
'《$_title》这是一个TextDemo,使用Flutter开发。由iOS开发者${_author}开发,快速配置,简易上手的零基础学习方式。欢迎阅读和上手练习,不懂之处,留言交流',
textAlign: TextAlign.center,
style: _textStyle,
maxLines: 4, // 最多4行
overflow: TextOverflow.ellipsis, // 超出显示省略号
);
}
}
- 在
main.dart中指定body为TextDemo:
image.png -
展示样式:
image.png
3.2 富文本
- 使用
RichText组件,通过给text指定TextSpan类型,添加children:<TextSpan>数组,数组内创建TextSpan,并赋值样式即可:
import 'package:flutter/material.dart';
//【富文本 Demo】
class RichTextDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RichText(
//基础元素
text: TextSpan(
text: '《Flutter 入门》',
style: TextStyle(
fontSize: 30,
color: Colors.black,
) ,
// 子元素
children:<TextSpan>[
// 元素一
TextSpan(
text: 'HT',
style: TextStyle(
fontSize: 20,
color: Colors.blue,
),
),
// 元素二
TextSpan(
text: '666',
style: TextStyle(
fontSize: 40,
color: Colors.red,
),
),
]),
);
}
}
- 在
main.dart中指定body为RichTextDemo:
image.png -
展示样式:
image.png
3.3 基础容器Container
- 创建基础组件
BaseWidgetDemo,返回Container组件:
//【基础组件Demo】
class BaseWidgetDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 容器背景蓝色(没有给定大小时,根据子视图大小撑开)
return Container(
color: Colors.blue,
// 子视图横向布局
child: Row(
children: <Widget>[
Container(
// 内边距
padding: EdgeInsets.only(left: 10,top: 10,right: 10,bottom: 10),
// 外边距
margin: EdgeInsets.only(left: 10,top: 10,right: 10,bottom: 10),
// 子元素红色
color: Colors.red,
// 子元素内容包含➕号图片
child: Icon(Icons.add),
),
Container(
// 子元素红色
color: Colors.red,
// 子元素内容包含➕号图片
child: Icon(Icons.add),
),
],
),
);
}
}

-
展示样式:
image.png
Dart中没有iOS的Button概念:
因为Button实际是图片+文字+手势+状态组成,以及封装相应的响应事件。我们一般使用手势小部件,包装图片和文字即可。
【快捷键】
command+option+L:自动格式化
command+-:折叠当前函数
command+shift+-:折叠全部函数
command+{:回到上一步
command+}: 回到下一步
command+shift+O: 快速到指定文件
stless:不可变组件
stful: 可变组件
本节主要是Flutter的基础体验,通过本节,其实我们已经感受到了Flutter的便捷。
下一节,Flutter入门三:自动布局(Row/Column/Stack)与两种Widget
















