最近打算自己做一个电商 App 调研之后选择技术栈为 Flutter 因此打算梳理一下 Flutter 的所有知识点,做个预热。
- 在了解了一些 flutter 的基本知识之后,我们需要做的事情如下:
- 使用更多组件,例如 Modal Dialogs
- 处理用户的 input 事件
- 对 Material 组件库进行全局配置,例如设置主题等
- flutter 中的日期类型 DateTime:
dart
class Expense {
final DateTime date;
date = DateTime.now();
}
- 每一个实例都需要一个 id 作为增删改查的依据,为此我们需要引入 uuid 库:
Powershell
flutter pub add uuid
在使用的时候我们首先引入然后定义一个实例,最终使用的是实例上的方法:
dart
import 'package:uuid/uuid.dart';
const uuid = Uuid();
- 初始化组件参数 对于一个实例来说,其创建的时候有的参数从外部传入,但是有的则是随机值,例如随机产生的 id, 这两部分合在一起构成了所有的构造参数。
dart
final uuid = Uuid();
class Expense {
Expense({
required this.title,
required this.amount,
required this.date,
}) : id=uuid.v4();
final String id;
final title id;
final double amount;
final DateTime date;
}
其实这两部分本来是格式统一的,但是我们对传入的参数做了简化处理,导致代码看起来分成了两个部分。
- 枚举类型及其作用 如果将一个变量的类型限制为 String 而其本身是枚举,则可能造成的问题就是范围过大以及在编写代码的时候编辑器不会进行提示,因此我们需要枚举类型,而 flutter 中提供了这种类型。
dart
enum Category { food, travel, leisure, work}
以上有三个点需要注意:
- 使用 enum 修饰符
- 变量名要大些
- 花括号中的内容不需要使用引号包裹
我们使用枚举值创建一个字面量 Map:
dart
const categoryIcons = {
Category.food: Icons.lunch_dining,
Category.travel: Icons.flight_takeoff,
Category.leisure: Icons.movie,
Category.work: Icons.work,
}
- 类中定义的列表类型的常量
dart
class _ExpensesState extends State<Expenses> {
final List<Expense> _registeredExpenses = [
Expenses(
title: 'Flutter Course',
amount: 19.99,
date: DateTime.now(),
category: Category.work,
),
Expenses(
title: 'Cinema',
amount: 15.69,
date: DateTime.now(),
category: Category.leisure,
),
];
}
- 虚拟滚动列表 之前我们一直都在使用 Column 组件做竖直方向上的元素排列,但是这里有一个很大的问题:
- 如果需要使用滚动条,我们必须结合其它组件一起
- Column 组件展示列表会把列表元素一次性全部加载完,这很耗费性能,并且在有些情况下是不可行的。 针对上面的问题,flutter 提供了虚拟滚动列表组件 -- ListView, 使用这个组件需要至少传递俩个参数,第一个参数是 int 类型,表示的是列表 item 的总数;第二个参数则是以函数返回值的方式传入需要渲染的列表组件。
dart
import 'package:flutter/material.dart';
import 'package:expense_tracker/models/expense.dart';
class ExpensesList extends StatelessWidget {
const ExpensesList({
super.key,
required this.expenses,
});
final List<Expense> expenses;
@override
Widget build(BuildContext context) {
return ListView(
// 这里应该添加 ListView 的子组件,例如 ListTile,来显示 expenses 列表
itemCount: expenses.length,
itemBuilder: (ctx,index) => Text(expenses[index].title),
);
}
}
然后再使用这个无状态的列表组件:
dart
children:[
const Text('The chart'),
ExpensesList(expenses: _registeredExpenses),
],
实际上,我们的结构应该是:Scaffold -> Column -> Expanded -> ListView
dart
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Expense Tracker',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
final List<Expense> registeredExpenses = []; // 假设这里已经初始化了费用数据
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Expense Tracker'),
),
body: Column(
children: [
const Text('The chart'),
Expanded(
child: ExpensesList(expenses: registeredExpenses),
),
],
),
);
}
}
class Expense {
// 假设这是费用项的类定义
final String id;
final double amount;
final String description;
Expense({required this.id, required this.amount, required this.description});
}
class ExpensesList extends StatelessWidget {
const ExpensesList({
Key? key,
required this.expenses,
}) : super(key: key);
final List<Expense> expenses;
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: expenses.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(expenses[index].description),
subtitle: Text('\$${expenses[index].amount}'),
);
},
);
}
}
- flutter 中的浮点数转字符串 浮点数转字符串一定会遇到的一个问题就是精度问题,因为浮点数有可能是无限小数,所以我们使用
toStringAsFixed(accurate)
方法
dart
Text(expense.amount.toStringAsFixed(2))
- 给 Column 设置 padding 和 html 有点不一样,padding 是在组件的外面设置的:
dart
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 16,
),
child: Column(),
),
- 模板字符串 2 之前说过,和 js 不同 dart 中的模板字符串好像不需要使用花括号,但是这只是简单的情况,对于一些复杂的情况,我们使用花括号包裹总是比较安全的
dart
Text('\$${expense.amount.toStringAsFixed(2)}')
- 左右排布 在 flutter 中实现左右排布可以通过 Spacer 组件实现,这个组件的特点就是能够吃掉所有剩余的空间,因此左右排布的本质是
Row( children: [左组件 Spacer 右组件] )
dart
Row(
children: [
Text('\$${expenses[index].amount.toStringAsFixed(2)}'),
const Spacer(),
Row(
children: [
const Icon(Icons.alarm),
const SizedBox(width: 8),
Text(expenses[index].date.toString()),
],
),
],
),
- 渲染列表项的合适组件 我们使用 Card 组件承接列表中每个单元的渲染是比较常见的做法:
dart
Card(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 16,
),
child: Text(expense.title),
),
);
- 在 flutter 项目中安装国际化:
Powershell
flutter pub add intl
- 一些重点内容的总结
- 引入常见库
dart
import 'package:flutter/material.dart';
import 'package:uuid/uuid.dart';
import 'package:intl/intl.dart';
- 使用 uuid 和 日期实例
dart
const uuid = Uuid();
final formatter = DateFormat.yMd();
- 使用 getter 而不是函数获取格式化之后的日期
dart
String get formattedDate {
return formatter.format(date);
}
- 内置组件 AppBar 在 Scaffold 组件内部,我们现在使用的最多的就是 body 配置项,表示 Scaffold 页面的内容部分;与之相对应的是 appBar 配置项,我们可以在这里配置页面的 bar, 不仅如此,我们一般使用 AppBar 组件作为这个属性的值:
dart
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
// 在这里添加 AppBar 操作按钮
],
), // AppBar
body: Column(
children: [
const Text('The chart'), // 标题文本
Expanded( // 使 ExpensesList 占据剩余空间
child: ExpensesList(expenses: registeredExpenses),
),
],
), // Column
); // Scaffold
}
这个 AppBar 组件会自动预留页面上方的空间,防止摄像头等物理设备遮挡页面上的元素。
- 设置 Material 组件库的主题
dart
void main() {
runApp(
MaterialApp(
theme: ThemeData(useMaterial3: true),
home: const Expenses(),
)
);
}