上一节,我们完成了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
注意:
- 如果此时
未打开模拟器
,会提示需要选择
一个模拟器
。- 如果此时打开了
多个模拟器
,也会提示您选中一个模拟器
来运行
。
选中
模拟器后,flutter
会自动使用Xcode工具
进行编译
。
- 如果需要
真机调试
,我们需要手动打开
项目工程
,去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.
- 如果使用
安卓模拟器
,可以在顶部控制栏
配置模拟器
:
选中模拟器,运行:
- 由于
个人习惯
,我更喜欢
在Xcode
中开发
,在终端
使用命令运行
。执行flutter run
时,会让我们选择
想使用的模拟器
(后续我主要使用iPhone模拟器
)。
1.2 Android Studio
创建
- 除了使用
flutter create
命令创建项目,我们还可使用Android Studio
创建,也可以使用VSCode
创建,它们都有flutter插件
。
- 啥? 你问
Xcode
是否可以创建?
想想苹果
与谷歌
的竞争者关系,就知道苹果不可能
做这样的支持
插件的。😂
1.2.1 使用Android Studio
创建
-
四种
创建方式:
-
项目
基本信息
-
项目
唯一标识
和支持
的平台
-
点
Finish
后,会进行网络请求
,拉取资源
,创建成功。
(如果没配置
镜像,拉取资源非常缓慢
。 上一节有介绍如何配置镜像)
坑点
如果Android Studio
正在运行
项目,我们command + Q
强制退出。下次打开Android Studio
时,会回到
当时的缓存
。如果缓存
成功找回,会运行正常
。如果缓存
找不到,会导致无法运行,而且新建工程
也无法运行
。处理方式:
删除
flutter目录下cache缓存文件夹
中的lockfile
文件,再运行项目
即可。(相当于XCode
的Clean
操作)# pwd请替换为自己flutter的文件目录 rm /pwd/flutter/bin/cache/lockfile
2. 熟悉工程
-
创建项目
后,可以看到main.dart
有很多代码
。我们最快熟悉
的方式是:全部删除
,手动实现
并分析
:
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
运行
,文本框成功展示
:
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()
,包含必选参数
和可选值
,可选值可以赋默认值
:
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
文件:
// 不需要导入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 Library
final 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 = []
-
效果展示:
实际开发中,我们可以将
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
:
-
展示样式:
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
:
-
展示样式:
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),
),
],
),
);
}
}
-
展示样式:
Dart
中没有iOS
的Button
概念:
因为Button
实际是图片
+文字
+手势
+状态
组成,以及封装相应的响应事件
。我们一般使用手势小部件
,包装图片
和文字
即可。
【快捷键】
command
+option
+L
:自动格式化
command
+-
:折叠当前函数
command
+shift
+-
:折叠全部函数
command
+{
:回到上一步
command
+}
: 回到下一步
command
+shift
+O
: 快速到指定文件
stless
:不可变组件
stful
: 可变组件
本节主要是Flutter
的基础体验
,通过本节,其实我们已经感受到了Flutter
的便捷
。
下一节,Flutter入门三:自动布局(Row/Column/Stack)与两种Widget