Flutter - 概览
Hello world
⌘ + shift + p
选择 Empty Application
模板
// 导入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});// 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 Worldchild: 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
class MainApp extends StatelessWidget {
...
}class Scaffold extends StatefulWidget {
...
}
创建新组件时继承有状态还是无状态的Widget
取决于是否要管理状态
基础组件
Text
Text 是现实单一样式的文本字符串组件。字符串可能跨多行中断,也可能全部显示在同一行上,取决于布局约束
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组件可以在一个段落中展示不同的样式
Widget build(BuildContext context) {return MaterialApp(home: Scaffold(body: Center(child: const Text.rich(TextSpan(text: 'Hello', // default text stylechildren: <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,列表布局
// 水平布局
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,),],
)
// 垂直布局
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组件
import 'package:flutter/material.dart';void main() {runApp(const MaterialApp(title: 'Flutter Tutorial', home: TutorialHome()));
}class TutorialHome extends StatelessWidget {const TutorialHome({super.key}); 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 technologiesonPressed: null,child: Icon(Icons.add),),);}
}
使用Scaffold
和AppBar
替换原来自定义的MyScaffold
和MyAppBar
手势处理
build(BuildContext context) {return MaterialApp(home: Scaffold(body: Center(child: GestureDetector(child: Text('Hello world',overflow: TextOverflow.ellipsis,),onTap: ()=> {// 生产环境不要用printprint("123")},)),),);
}
Widget
更改小组件以响应输入
UI通常需要对用户的输入进行响应,比如点外卖时根据用户选择菜品计算最后的价格, Flutter
中使用StatefulWidgets
来处理这种场景。
继承StatefulWidget
,重写createState
方法
class Counter extends StatefulWidget {const Counter({super.key});// 继承StatefulWidget的类要重写createState()方法,内容返回是_CounterState对象// ``=>`` (胖箭头)简写语法用于仅包含一条语句的函数。该语法在将匿名函数作为参数传递时非常有用 State<Counter> createState() => _CounterState();
}
所有的类都隐式定义成了一个接口。因此,任意类都可以作为接口被实现,定义一个继承State并实现Counter
类的方法
/// [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++;});}
合并的示例
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,);} Widget build(BuildContext context) {return ListTile(// 5. 点击Item时调用传入 onCartChanged 回调,并传入一开始接收的出参数// 比如一开始在订单内inCart传trueonTap: () {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创建状态对象 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方法});} 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
其它
包缓存地址
$ flutter pub get
# 命令下载的包在~/.pub-cache/hosted
参考
- Flutter
- Flutter-UI
- Flutter - 我给官方提PR,解决run命令卡住问题 😃
- Day16 - Flutter - 屏幕适配