Flutter widget
Flutter widgets 是从React框架中获得的灵感, 核心思想是build your UI out of widgets, widgets根据当前的配置和state描述了它应该是什么样子的, 当widget的state改变之后, widget就会重构这个描述, 并且会根据diffs算法, 根据前一个描述来计算出最小的更改, 把当前的widge切换到下一个state.
import 'package:flutter/material.dart';
void main() {
runApp(
const Center(
child: Text(
'Hello, world!',
textDirection: TextDirection.ltr,
),
),
);
runApp函数会把给定的widget作为根, 就比如上面的Center和Text, 会强制的把Center铺满全屏, 如果要写App的话, 一般是要继承自StatelessWidget或StatefulWidget, 继承自哪个是要取决这个widget会不会有变化, 每个widget都要重写 build()函数, 就是这个函数用来描述widget树来着
基础的Widgets
- Text 展示文本
- Row, Column 横向/纵向的一个容器 是以web的flexbox为基础
- Stack 线性的一个布局容器, 是以web的绝对布局为基础
- Container 就是一个容器, 可以设置很多东西: border, shadow
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( // scaffold 的框架
appBar: AppBar(
leading: const IconButton(
icon: Icon(Icons.menu),
tooltip: 'Navigation menu',
onPressed: null,
),// 设置左上角的按钮
title: const Text('Example title'), // 导航栏中间的title
actions: const [
IconButton(
icon: Icon(Icons.search),
tooltip: 'Search',
onPressed: null,
), // 导航栏右侧的按钮
],
),
// body is the majority of the screen.
body: const Center(
child: Text('Hello, world!'), // 中间显示的文本
),
floatingActionButton: const FloatingActionButton(
tooltip: 'Add', // used by assistive technologies
onPressed: null,
child: Icon(Icons.add),
), // 底部右下角的浮动按钮
);
}
}
flutter里最神奇的是手势动画什么的也是widget, 本身GestureDetector并不是可见的, 但是用户是可以触碰到的, 当用户点击了Container就会触发onTap()callback, 许多的系统控件都是使用这种方式, 比如IconButton, FloatingActionButton用的是onPressed()
import 'package:flutter/material.dart';
class MyButton extends StatelessWidget {
const MyButton({super.key});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
print('MyButton was tapped!');
},
child: Container(
height: 50.0,
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0),
color: Colors.lightGreen[500],
),
child: const Center(
child: Text('Engage'),
),
),
);
}
}
void main() {
runApp(
const MaterialApp(
home: Scaffold(
body: Center(
child: MyButton(),
),
),
),
);
}
一个可变的实例, 这里需要注意一个问题StatefulWidget和State是两个对象, 并不是同一个, 因为这两个对象具有不同的生命周期, widget是一个临时的对象, 因为这货会根据state的变化而重组, 而state是一直存在的
import 'package:flutter/material.dart';
class Counter extends StatefulWidget {
const Counter({super.key});
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _counter = 0;
void _increment() {
setState(() {
// 数据变化必须要在setState这个函数里, 这个函数可以触发rebuild, 如果不通过这个函数
// 数据变化则不会体现到UI上
_counter++;
});
}
@override
Widget build(BuildContext context) {
// 每次setState被调用的时候, 这个方法都会被调用, flutter底层做过优化
// 所以... just do it
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: _increment,
child: const Text('Increment'),
),
const SizedBox(width: 16),
Text('Count: $_counter'),
],
);
}
}
void main() {
runApp(
const MaterialApp(
home: Scaffold(
body: Center(
child: Counter(),
),
),
),
);
}
👆🏻这个例子比较简单粗暴, widget的修改直接就放到build里就完事儿了, 真实的情况, 可能不一个widget的不同部分要处理的情况不同, 所以, 有必要把他们给分开, 这样看着会复杂, 但项目的复杂度上来之后, 就会很有必要
import 'package:flutter/material.dart';
class CounterDisplay extends StatelessWidget {
const CounterDisplay({required this.count, super.key});
final int count;
@override
Widget build(BuildContext context) {
return Text('Count: $count');
}
}
class CounterIncrementor extends StatelessWidget {
const CounterIncrementor({required this.onPressed, super.key});
final VoidCallback onPressed;
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
child: const Text('Increment'),
);
}
}
class Counter extends StatefulWidget {
const Counter({super.key});
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _counter = 0;
void _increment() {
setState(() {
++_counter;
});
}
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CounterIncrementor(onPressed: _increment),
const SizedBox(width: 16),
CounterDisplay(count: _counter),
],
);
}
}
void main() {
runApp(
const MaterialApp(
home: Scaffold(
body: Center(
child: Counter(),
),
),
),
);
}
最外围的Row是一个可变组件(红色部分), 需要计数以及state的改变都是他在维护, 而里面的蓝色框框部分, 被分成了两部分都是不可变组件, 左侧的可点击按钮, 是通过callback回调出来, 去调用_increment(), 而右侧的展示部分, 则是通过传参方式进行传入
tips: _CounterState 里的build每次state变化的时候都会调用
结合一下, 自定义一个ShopListItem
import 'package:flutter/material.dart';
// 产品类
class Product {
const Product({required this.name});
final String name;
}
// 回调的callback
typedef CartChangedCallback = Function(Product pro, bool inCart);
// item
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;
// 根据是否 inCart 改变颜色
Color _getColor(BuildContext context) {
return inCart ? Colors.black54 : Theme.of(context).primaryColor;
}
// 根据是否 inCart 该干文本style, 中线
TextStyle? _getTextStyle(BuildContext context) {
if(!inCart) return null;
return const TextStyle(
color: Colors.black54,
decoration: TextDecoration.lineThrough,
);
}
@override
Widget build(BuildContext context) {
return ListTile(
onTap: () {onCartChanged(product, inCart);},
leading: CircleAvatar(backgroundColor: _getColor(context),
child: Text(product.name[0]),),
title: Text(product.name, style: _getTextStyle(context),),
);
}
}
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: Center(child:
ShoppingListItem(
product: const Product(name:"Map"),
inCart: true,
onCartChanged: (product, inCart) {},
),
),
),
),
);
}
动态的改变item的状态, 第一次执行的时候会创建一个shoppingList的widget tree对象, 因为是继承自StatefulWidget, 同时会创建State, 但是当有改动需要重组的时候(这个widget会被标记为dirty然后会被安排进行重组), widget tree会创建一个新的, 但是会重用State对象. 如果想监听widget的改变可以重写didUpdateWidget()函数, 这个会把oldWidget和当前的widget进行对比
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(
onTap: () {
onCartChanged(product, inCart);
},
leading: CircleAvatar(
backgroundColor: _getColor(context),
child: Text(product.name[0]),
),
title: Text(
product.name,
style: _getTextStyle(context),
),
);
}
}
class ShoppingList extends StatefulWidget {
const ShoppingList({required this.products, super.key});
final List<Product> products;
@override
State<ShoppingList> createState() => _ShoppingListState();
}
class _ShoppingListState extends State<ShoppingList> {
// 用set包含选中的item, 无序且不重复的集合
final _shoppingCart = <Product>{};
// 回调触发build执行, 刷新UI
void _handleCartChanged(Product product, bool inCart) {
setState(() {
if (!inCart) {
_shoppingCart.add(product);
} else {
_shoppingCart.remove(product);
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Shopping List'),
),
body: ListView(
padding: const EdgeInsets.symmetric(vertical: 8.0),
children: widget.products.map((product) {
return ShoppingListItem(
product: product,
inCart: _shoppingCart.contains(product),
onCartChanged: _handleCartChanged, // 通过listitem 的callback 回调
);
}).toList(),
),
);
}
}
void main() {
runApp(const MaterialApp(
title: 'Shopping App',
home: ShoppingList(
products: [
Product(name: 'Eggs'),
Product(name: 'Flour'),
Product(name: 'Chocolate chips'),
],
),
));
}
widget 的生命周期事件
在StatefulWidget调用`createState()`后, framework会在widget树中插入一个新的state对象, 然后会调用State对象的`initState()`, 这个函数只会执行一次, 如果子类重写该方法, 那么就需要调用`super.initState`.
当一个state对象不再需要的时候, framework会调用state对象的`dispose()`函数进行清理工作, 跟`initState()`一样, 如果重写必须调用super