Hello world
⌘ + shift + p

选择 Empty Application
模板
dart
// 导入Material风格的组件包
// 位置在flutter安装目录/packages/flutter/lib/material.dart
import 'package:flutter/material.dart';
void main() {
// runApp函数接收MainApp组件并将这个Widget作为根节点
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
// Describes the part of the user interface represented by this widget.
// The framework calls this method when this widget is inserted into the tree in a given
// [BuildContext] and when the dependencies of this widget change
// (e.g., an [InheritedWidget] referenced by this widget changes).
// This method can potentially be called in every frame and should not have any
// side effects beyond building a widget.
Widget build(BuildContext context) {
/// An application that uses Material Design.
/// 使用Material设计的组件,home代表默认页
return const MaterialApp(
/// The Scaffold is designed to be a top level container for
/// a [MaterialApp]. This means that adding a Scaffold
/// to each route on a Material app will provide the app with
/// Material's basic visual layout structure.
/// Scaffold,MateriaApp组件的顶层容器,规范样式之类的
home: Scaffold(
body: Center( /// 局中显示Hello World
child: Text('Hello World'),
),
),
);
}
}
build方法用于描述Widget的展示效果,当被添加到上下文的树 和Widget发生变化时会触发这个方法。因为这个方法是高频操作所以不应该有副作用。
热重载(Hot reload)
Flutter支持热重载,无需重启启动应用的情况下去重新刷新页面。通过将更新代码注入到运行的Dart虚拟机来实现热重载。在虚拟机使用新的字段和函数更新类后,Flutter框架自动重新构建widget。
修改后直接保存/点击调试那里的闪电图标能直接刷新

有状态(StatefulWidget) + 无状态(StatelessWidget)
Flutter中的一切都是Widget,Widget分为有状态和无状态两种,在 Flutter 中每个页面都是一帧,无状态就是保持在那一帧,而有状态的 Widget 当数据更新时,其实是创建了新的 Widget,只是 State 实现了跨帧的数据同步保存。
比如上面的MainApp
是无状态的Widget,而Scaffold
是有状态的Widget
dart
class MainApp extends StatelessWidget {
...
}
class Scaffold extends StatefulWidget {
...
}
创建新组件时继承有状态还是无状态的Widget
取决于是否要管理状态
基础组件
Text
Text 是现实单一样式的文本字符串组件。字符串可能跨多行中断,也可能全部显示在同一行上,取决于布局约束
dart
Widget build(BuildContext context) {
return MaterialApp(
home:Scaffold(
body:Center(
// 设置宽度限制为100点
child:Container(
width: 100,
height:30,
// 边框
decoration: BoxDecoration(border: Border.all()),
// TextOverflow.ellipsis 超过部分用...
// TextOverflow.clip -- Clip the overflowing text to fix its container. 超出部分换下一行,外部容器会被遮挡
// TextOverflow.visible -- Render overflowing text outside of its container. 超出容器部分能渲染
child: Text(overflow:TextOverflow.ellipsis, 'Hello world, how are you?'))
))
);
}
TextOverflow.ellipsis
的效果

TextOverflow.clip
的效果

TextOverflow.visible
的效果

maxLines
控制最大行数
softWrap
控制是否换行
当overflow
是TextOverflow.visible
时
softWrap: false

softWrap: true

Text.rich
使用Text.rich构造器,Text组件可以在一个段落中展示不同的样式
dart
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: const Text.rich(
TextSpan(
text: 'Hello', // default text style
children: <TextSpan>[
TextSpan(
text: ' beautiful ',
style: TextStyle(fontStyle: FontStyle.italic),
),
TextSpan(
text: 'world',
style: TextStyle(fontWeight: FontWeight.bold),
),
],
),
),
),
),
);
}

关于Text的交互
用GestureDetector widget
包装Text
,然后在GestureDetector.onTap
中处理点击事件。或者使用TextButton
来代替
Row,Column,Stack,Container
- Container: 只有一个子 Widget。默认充满,包含了padding、margin、color、宽高、decoration 等配置
- Row: 可以有多个子 Widget。水平布局。
- Column: 可以有多个子 Widget。垂直布局。
- Stack: 可以有多个子 Widget。 子Widget堆叠在一起。
- Center: 只有一个子 Widget。只用于居中显示,常用于嵌套child,给child设置居中。
- Padding: 只有一个子 Widget。只用于设置Padding,常用于嵌套child,给child设置padding。
- Expanded: 只有一个子 Widget。在 Column 和 Row 中充满。
- ListView: 可以有多个子Widget,列表布局
dart
// 水平布局
Row(
children: [
// 图标
const IconButton(
icon: Icon(Icons.menu),
tooltip: 'Navigation menu',
onPressed: null, // null disables the button
),
// Expanded expands its child
// to fill the available space.
// 填充满2个图标之间的空间
Expanded(child: title),
// 查询图标
const IconButton(
icon: Icon(Icons.search),
tooltip: 'Search',
onPressed: null,
),
],
)
dart
// 垂直布局
Column(
children: [
MyAppBar(
title: Text(
'示例标题',
style:
Theme.of(context) //
.primaryTextTheme.titleLarge,
),
),
const Expanded(child: Center(child: Text('容器'))),
],
)

要使用material中这些预定义图标,需要将工程中的pubspec.yaml
文件里的uses-material-design
字段设置为true
使用Material组件
dart
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(title: 'Flutter Tutorial', home: TutorialHome()));
}
class TutorialHome extends StatelessWidget {
const TutorialHome({super.key});
@override
Widget build(BuildContext context) {
// Scaffold is a layout for
// the major Material Components.
return Scaffold(
appBar: AppBar(
leading: const IconButton(
icon: Icon(Icons.menu),
tooltip: 'Navigation menu',
onPressed: null,
),
title: const Text('Material Components'),
actions: const [
IconButton(
icon: Icon(Icons.search),
tooltip: 'Search',
onPressed: null,
),
],
),
// body is the majority of the screen.
body: const Center(child: Text('Material!')),
floatingActionButton: const FloatingActionButton(
tooltip: 'Add', // used by assistive technologies
onPressed: null,
child: Icon(Icons.add),
),
);
}
}

使用Scaffold
和AppBar
替换原来自定义的MyScaffold
和MyAppBar
手势处理
dart
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: GestureDetector(
child: Text('Hello world',overflow: TextOverflow.ellipsis,),
onTap: ()=> {
// 生产环境不要用print
print("123")
},)
),
),
);
}

更改小组件以响应输入
UI通常需要对用户的输入进行响应,比如点外卖时根据用户选择菜品计算最后的价格, Flutter
中使用StatefulWidgets
来处理这种场景。
继承StatefulWidget
,重写createState
方法
dart
class Counter extends StatefulWidget {
const Counter({super.key});
// 继承StatefulWidget的类要重写createState()方法,内容返回是_CounterState对象
// ``=>`` (胖箭头)简写语法用于仅包含一条语句的函数。该语法在将匿名函数作为参数传递时非常有用
@override
State<Counter> createState() => _CounterState();
}
所有的类都隐式定义成了一个接口。因此,任意类都可以作为接口被实现 ,定义一个继承State并实现Counter
类的方法
dart
/// [State] objects are created by the framework by calling the
/// [StatefulWidget.createState] method when inflating a [StatefulWidget] to
/// insert it into the tree.
/// 在这里当Counter组件被添加到渲染树时,因为也实现了Counter类,所以会调用对应的createState方法。
class _CounterState extends State<Counter> {
int _counter = 0;
// _ 代表私有方法
void _increment() {
// 调用setState()通知Flutter状态变化了,然后重新执行build方法实现实时刷新的效果
setState(() {
_counter++;
});
}

合并的示例
dart
import 'package:flutter/material.dart';
class Product {
const Product({required this.name});
final String name;
}
typedef CartChangedCallback = Function(Product product, bool inCart);
class ShoppingListItem extends StatelessWidget {
ShoppingListItem({
required this.product,
required this.inCart,
required this.onCartChanged,
}) : super(key: ObjectKey(product));
final Product product;
final bool inCart;
final CartChangedCallback onCartChanged;
Color _getColor(BuildContext context) {
return inCart //
? Colors.black54
: Theme.of(context).primaryColor;
}
TextStyle? _getTextStyle(BuildContext context) {
if (!inCart) return null;
return const TextStyle(
color: Colors.black54,
decoration: TextDecoration.lineThrough,
);
}
@override
Widget build(BuildContext context) {
return ListTile(
// 5. 点击Item时调用传入 onCartChanged 回调,并传入一开始接收的出参数
// 比如一开始在订单内inCart传true
onTap: () {
onCartChanged(product, inCart);
},
leading: CircleAvatar(
backgroundColor: _getColor(context),
child: Text(product.name[0]),
),
// 4.显示产品订单的样式
// 10.根据新参数重新显示样式
title: Text(product.name, style: _getTextStyle(context)),
);
}
}
class ShoppingList extends StatefulWidget {
// 要求传入 products属性
const ShoppingList({required this.products, super.key});
final List<Product> products;
// 2. 调用_ShoppingListState创建状态对象
@override
State<ShoppingList> createState() => _ShoppingListState();
}
class _ShoppingListState extends State<ShoppingList> {
final _shoppingCart = <Product>{};
// 6. 点击触发回调
void _handleCartChanged(Product product, bool inCart) {
setState(() {
// 7. 根据入参进行判断,如果一开始是true,则移除,否则添加,即取反操作
if (!inCart) {
_shoppingCart.add(product);
} else {
_shoppingCart.remove(product);
}
// 8. 通知Flutter 重新执行_ShoppingListState对象的build方法
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Shopping List')),
body: ListView(
padding: const EdgeInsets.symmetric(vertical: 8),
children:
// 3. 根据products属性创建ShoppingListItem,并传入产品信息,回调
// 9. 再次调用ShoppingListItem并传入新参数
widget.products.map((product) {
return ShoppingListItem(
product: product,
inCart: _shoppingCart.contains(product),
onCartChanged: _handleCartChanged,
);
}).toList(),
),
);
}
}
void main() {
runApp(
const MaterialApp(
title: 'Shopping App',
// 1. 创建ShoppingList对象,并传入Product参数
home: ShoppingList(
products: [
Product(name: 'Eggs'),
Product(name: 'Flour'),
Product(name: 'Chocolate chips'),
],
),
),
);
}

响应组件的生命周期相关事件
Flutter
调用createState
方法后,会将state
对象添加到渲染树并且调用state
对象的initState()
,可以重写这个方法中配置动画或准备订阅平台相关的服务,重写方法开始要先调用super.initState
。当state
对象不再需要时,Flutter
会调用对象的dispose
方法来执行清理操作,比如取消定时器,取消订阅,同样在重写方法中也要先调用super.dispose
其它
包缓存地址
sh
$ flutter pub get
# 命令下载的包在~/.pub-cache/hosted