我們會用一個購物清單的實作,來帶大家認識元件在Flutter中的相互使用與堆疊。並且,在接下來的章節中,我們會將視野著重於程式構造,將不再細講單一特定元件的屬性與使用方式!
為了應對更加複雜的使用者互動,我們會使用「State(狀態)」來區分元件,最顯而易見的,就是使用Stateless
與Stateful
元件。值得注意的是,我們先前介紹元件時大多都是使用「無狀態」的方式。
在StatefulWidgets中,會有setState()
函數,當執行到該所屬函數時,會根據改變的狀態重新做UI的建構渲染
void _increment() {
setState(() { //使用setState,以告訴Flutter框架狀態已經改變,需要重新構建UI以反映變化
_counter++;
});
}
我們拿一個使用ElevatedButton
的計數器程式作為說明。
Counter
類別: 是一個StatefulWidget。_CounterState
類別: 是Counter的內部狀態,在這個類別內有_increment()
函式_increment()
函式: 裡面有setState()
用於放置對變數_counter
的變化行為ElevatedButton
: 裡面的onPressed
,會呼叫一次_increment()
函式所以當程式執行時,會先建構初始化的計數器畫面,並隨著使用者點擊按鈕,呼叫_increment()
,並由setState更新累計的次數,並顯示當前的次數在畫面上。
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,以告訴Flutter框架狀態已經改變,需要重新構建UI以反映變化
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: _increment,//重新呼叫一次setState(),更新_counter
child: const Text('Increment'),
),
const SizedBox(width: 16),
Text('Count: $_counter'),
],
);
}
}
void main() {
runApp(
const MaterialApp(
home: Scaffold(
body: Center(
child: Counter(),
),
),
),
);
}
Stateful使用範例
我們回到購物車的範例,我們先設計出購物車項目的規劃,並且目前用inCart
變數手動設為"false","true"來建構
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)),
);
}
}
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: Center(
child: ShoppingListItem(
product: const Product(name: 'Chips'),
inCart: true,//為false時是藍色、為true時是劃掉且顏色為灰色
onCartChanged: (product, inCart) {},
),
),
),
),
);
}
ShoppingListItem使用範例(Stateless,無狀態模式)
inCart true的情況
inCart false的情況
我們現在新增一個_handleCartChanged
函數,使用setState()來控制每次的點擊互動更新,我們讓按鈕被點擊時固定呼叫此函數去更新inCart
的Boolean值,點擊一次會使其「移除」、再次進行點擊則會「加回」
void _handleCartChanged(Product product, bool inCart) {
setState(() {
if (!inCart) {
_shoppingCart.add(product);
} else {
_shoppingCart.remove(product);
}
});
}
_handleCartChanged函數
我們將剛剛Stateless的範例轉為使用Stateful來達成使用者動態更新購物車狀態,由剛剛的程式,我們可以新增_handleCartChanged
函數處理購物車狀態,並設定三項購物車內容做demo
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> {
final _shoppingCart = <Product>{};
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),
children: widget.products.map((product) {
return ShoppingListItem(
product: product,
inCart: _shoppingCart.contains(product),
onCartChanged: _handleCartChanged,
);
}).toList(),
),
);
}
}
void main() {
runApp(const MaterialApp(
title: 'Shopping App',
home: ShoppingList(
products: [
Product(name: 'Eggs'),
Product(name: 'Flour'),
Product(name: 'Chocolate chips'),
],
),
));
}
ShoppingListItem使用範例(Stateful,有狀態模式)