StatelessWidget
less
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(HomePage());
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "首页",
theme: ThemeData(scaffoldBackgroundColor: Colors.blue),
home: Scaffold(
appBar: AppBar(title: Text("购物车"),),
body: ListView(
children: [
Text("零食"),
Text("电脑")
],
),
bottomNavigationBar: Container(
height: 60,
color: Colors.green,
child: Center(
child: Text("结算"),
),
),
),
);
}
}
StatelessWidget进阶
less
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(const MyApp());
}
// ─────────────────────────────────────────────
// 知识点 1: 无状态组件的基本结构
// ─────────────────────────────────────────────
// 继承 StatelessWidget,重写 build 方法
// 无状态组件 = 不可变(final)+ 无状态(无 setState)
// 适用于 UI 不依赖可变数据的场景
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '无状态组件知识点',
home: const HomePage(),
theme: ThemeData(primarySwatch: Colors.blue),
);
}
}
// ─────────────────────────────────────────────
// 知识点 2: 从父组件接收参数
// ─────────────────────────────────────────────
// 通过构造函数接收数据,所有字段必须声明为 final
// 这是 StatelessWidget 传递数据的唯一方式(单向数据流)
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('StatelessWidget 详解')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
// 知识点 3: 复用组件 ------ 无状态组件可自由复用
// 因为没有状态,同一个组件类可以创建多个实例
Text('无状态组件的核心知识点', style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
SizedBox(height: 16),
KnowledgeCard(
title: '1. 不可变性',
description: '所有属性必须是 final,运行时无法修改',
icon: Icons.lock,
color: Colors.red,
),
SizedBox(height: 8),
KnowledgeCard(
title: '2. 单向数据流',
description: '数据只能从父组件通过构造函数传入,子组件无法修改',
icon: Icons.arrow_downward,
color: Colors.blue,
),
SizedBox(height: 8),
KnowledgeCard(
title: '3. build 方法',
description: '必须重写,返回一个 Widget,每次重建都会重新调用',
icon: Icons.build,
color: Colors.green,
),
SizedBox(height: 8),
KnowledgeCard(
title: '4. 性能优势',
description: '无状态组件重建开销小,Flutter 会跳过不必要的重建',
icon: Icons.speed,
color: Colors.orange,
),
SizedBox(height: 8),
KnowledgeCard(
title: '5. Context 作用',
description: 'BuildContext 是组件树的引用,用于查找父级数据(Theme、MediaQuery 等)',
icon: Icons.account_tree,
color: Colors.purple,
),
SizedBox(height: 24),
ComparisonTitle(),
SizedBox(height: 8),
_ComparisonTable(),
SizedBox(height: 24),
_UsageTips(),
],
),
);
}
}
// ─────────────────────────────────────────────
// 知识点 3: 自定义可复用的无状态组件
// ─────────────────────────────────────────────
// 提取独立组件,便于复用和维护
// 所有传入参数必须声明为 final
class KnowledgeCard extends StatelessWidget {
final String title;
final String description;
final IconData icon;
final Color color;
const KnowledgeCard({
super.key,
required this.title,
required this.description,
required this.icon,
this.color = Colors.blue,
});
@override
Widget build(BuildContext context) {
return Card(
elevation: 3,
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CircleAvatar(
backgroundColor: color.withValues(alpha: 0.15),
child: Icon(icon, color: color),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
const SizedBox(height: 4),
Text(description, style: TextStyle(fontSize: 14, color: Colors.grey[700])),
],
),
),
],
),
),
);
}
}
// ─────────────────────────────────────────────
// 知识点 4: 通过 Theme.of(context) 查找父级数据
// ─────────────────────────────────────────────
// StatelessWidget 通过 context 向上查找组件树中的数据
// 这是 InheritedWidget 的机制
class ComparisonTitle extends StatelessWidget {
const ComparisonTitle({super.key});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context); // 通过 context 获取 Theme
return Text(
'对比:StatelessWidget vs StatefulWidget',
style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
);
}
}
// ─────────────────────────────────────────────
// 知识点 5: 组件树的概念
// ─────────────────────────────────────────────
// 无状态组件是组件树的叶子节点
// 父组件通过构造函数将数据向下传递
class _ComparisonTable extends StatelessWidget {
const _ComparisonTable();
@override
Widget build(BuildContext context) {
return Table(
border: TableBorder.all(color: Colors.grey.shade300),
columnWidths: const {
0: FlexColumnWidth(1),
1: FlexColumnWidth(2),
2: FlexColumnWidth(2),
},
children: [
TableRow(
decoration: BoxDecoration(color: Colors.grey),
children: [
_Cell('对比项', isHeader: true),
_Cell('StatelessWidget', isHeader: true),
_Cell('StatefulWidget', isHeader: true),
],
),
TableRow(children: [
_Cell('状态'),
_Cell('无状态'),
_Cell('有状态'),
]),
TableRow(children: [
_Cell('修改数据'),
_Cell('无法修改(final)'),
_Cell('通过 setState 修改'),
]),
TableRow(children: [
_Cell('生命周期'),
_Cell('只有 build'),
_Cell('initState / build / dispose 等'),
]),
TableRow(children: [
_Cell('性能'),
_Cell('更轻量'),
_Cell('相对较重'),
]),
TableRow(children: [
_Cell('使用场景'),
_Cell('静态 UI、展示型页面'),
_Cell('交互型、动态数据页面'),
]),
],
);
}
}
class _Cell extends StatelessWidget {
final String text;
final bool isHeader;
const _Cell(this.text, {this.isHeader = false});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
child: Text(
text,
style: TextStyle(
fontWeight: isHeader ? FontWeight.bold : FontWeight.normal,
color: isHeader ? Colors.white : Colors.black87,
),
),
);
}
}
// ─────────────────────────────────────────────
// 知识点 6: 何时使用 StatelessWidget
// ─────────────────────────────────────────────
class _UsageTips extends StatelessWidget {
const _UsageTips();
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.blue.shade200),
),
child: const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('💡 何时使用 StatelessWidget?', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
SizedBox(height: 8),
Text('✅ 页面内容不依赖于运行时可变数据'),
Text('✅ 仅展示从父组件传入的数据'),
Text('✅ 不需要修改自身状态'),
Text('✅ 纯 UI 组件(如卡片、列表项、图标等)'),
SizedBox(height: 8),
Text('❌ 需要交互响应(如点击改变状态)时,应使用 StatefulWidget'),
],
),
);
}
}
StatefullWidget
less
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(HomePage());
}
class HomePage extends StatefulWidget {
HomePage({Key? key}) : super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Container(
child: MaterialApp(
title: "title",
theme: ThemeData(scaffoldBackgroundColor: Colors.green),
home: Scaffold(
appBar: AppBar(title: Text("首页"),),
body: Center(
child: ListView(
children: [
Text("电脑"),
Text("显卡"),
],
),
),
bottomNavigationBar: Container(
height: 60,
color: Colors.blue,
child: Text("购买"),
),
),
),
);
}
}
StatefullWidget
setState里面写需要修改的数据。推荐写法,虽然写外面也能刷新。
甚至写在setState后面也能更新。因为这个方法只是标记下一帧需要刷新。不是立刻执行。
scss
// Flutter 框架内部 setState 的简化逻辑
void setState(VoidCallback fn) {
fn(); // 1. 先执行你传入的回调(修改状态)
_element!.markNeedsBuild(); // 2. 再把当前 Element 标记为「脏」
}
less
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(
MaterialApp(
title: "title",
theme: ThemeData(scaffoldBackgroundColor: Colors.greenAccent),
home: HomePage(),
),
);
}
class HomePage extends StatefulWidget {
HomePage({Key? key}) : super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("首页")),
bottomNavigationBar: Container(
height: 60,
color: Colors.blue,
child: Center(child: Text("购买")),
),
body: Center(
child: Row(
mainAxisAlignment: .center,
children: [
GestureDetector(
child: Text("-2"),
onDoubleTap: () {
setState(() {
count -= 2;
});
},
),
TextButton(
onPressed: () {
setState(() {
count -= 1;
});
},
child: Text("-1"),
),
Text("100"),
TextButton(
onPressed: () {
setState(() {
count += 1;
});
},
child: Text("+1"),
),
GestureDetector(
child: Text("+2"),
onDoubleTap: () {
setState(() {
count += 2;
});
},
),
],
),
),
);
}
}
StatefullWidget进阶
less
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'StatefulWidget 全方位知识点',
theme: ThemeData(primarySwatch: Colors.blue),
home: const KnowledgeHome(),
);
}
}
// ═══════════════════════════════════════════════════════════
// 知识点导航页
// ═══════════════════════════════════════════════════════════
class KnowledgeHome extends StatelessWidget {
const KnowledgeHome({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('StatefulWidget 知识体系')),
body: ListView(
padding: const EdgeInsets.all(16),
children: const [
Text('核心知识点', style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
SizedBox(height: 16),
_KnowledgeItem(
title: '1. 基本结构',
subtitle: 'Widget + State 两段式',
page: StructureDemo(),
),
_KnowledgeItem(
title: '2. setState 机制',
subtitle: '修改状态 + 标记重建',
page: SetStateDemo(),
),
_KnowledgeItem(
title: '3. 完整生命周期',
subtitle: 'createState → initState → build → dispose',
page: LifecycleDemo(),
),
_KnowledgeItem(
title: '4. mounted 检查',
subtitle: '异步回调后判断是否还挂载',
page: MountedDemo(),
),
_KnowledgeItem(
title: '5. Key 与状态复用',
subtitle: '控制 State 是否被复用',
page: KeyDemo(),
),
_KnowledgeItem(
title: '6. App 生命周期',
subtitle: '前后台切换监听',
page: AppLifecycleDemo(),
),
],
),
);
}
}
class _KnowledgeItem extends StatelessWidget {
final String title;
final String subtitle;
final Widget page;
const _KnowledgeItem({
required this.title,
required this.subtitle,
required this.page,
});
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
title: Text(title, style: const TextStyle(fontWeight: FontWeight.w600)),
subtitle: Text(subtitle),
trailing: const Icon(Icons.chevron_right),
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => page),
),
),
);
}
}
// ═══════════════════════════════════════════════════════════
// 知识点 1:基本结构
// ─────────────────────────────────────────────────────────
// StatefulWidget 由两部分组成:
// ① Widget 类:不可变,描述配置(props、key)
// ② State 类:可变,持有状态 + build 方法
// 分离的原因:Widget 频繁重建(每帧都可能 new),但 State 需要持久保留
// ═══════════════════════════════════════════════════════════
class StructureDemo extends StatelessWidget {
const StructureDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('1. 基本结构')),
body: const Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('两段式结构', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
SizedBox(height: 12),
Text('''
class MyWidget extends StatefulWidget {
const MyWidget({super.key, required this.title});
final String title; // ① 配置:不可变,final
@override
State<MyWidget> createState() => _MyWidgetState(); // ② 创建 State
}
class _MyWidgetState extends State<MyWidget> {
int _count = 0; // ③ 状态:可变,非 final
@override
Widget build(BuildContext context) {
return Text('\${widget.title}: \$_count'); // ④ 通过 widget.xxx 访问配置
}
}''', style: TextStyle(fontFamily: 'monospace', fontSize: 13)),
SizedBox(height: 16),
Text('🔑 关键点', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
SizedBox(height: 8),
Text('• Widget 不可变 → 每次父组件 rebuild 都会 new 一个新 Widget'),
Text('• State 可变且持久 → 不会随 Widget 重建而丢失'),
Text('• 通过 widget.属性 访问 Widget 的配置'),
Text('• State 的泛型 <MyWidget> 绑定了它服务的 Widget 类型'),
],
),
),
);
}
}
// ═══════════════════════════════════════════════════════════
// 知识点 2:setState 机制
// ─────────────────────────────────────────────────────────
// setState 做两件事:① 执行回调(修改状态)② markNeedsBuild(标记重建)
// rebuild 是延迟的(下一帧),不是立即执行
// ═══════════════════════════════════════════════════════════
class SetStateDemo extends StatefulWidget {
const SetStateDemo({super.key});
@override
State<SetStateDemo> createState() => _SetStateDemoState();
}
class _SetStateDemoState extends State<SetStateDemo> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('2. setState 机制')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('$_count', style: const TextStyle(fontSize: 48)),
const SizedBox(height: 20),
// ✅ 正确:修改写在 setState 回调内
ElevatedButton(
onPressed: () {
setState(() {
_count += 1; // 修改和通知绑定在一起
});
},
child: const Text('+1(正确写法)'),
),
const SizedBox(height: 20),
const Text(
'setState 内部逻辑:\nfn() 执行回调 → markNeedsBuild() 标记脏\n→ 下一帧才真正 rebuild',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey),
),
],
),
),
);
}
}
// ═══════════════════════════════════════════════════════════
// 知识点 3:完整生命周期
// ─────────────────────────────────────────────────────────
// createState → initState → didChangeDependencies → build
// → (didUpdateWidget + build 循环) → deactivate → dispose
// ═══════════════════════════════════════════════════════════
class LifecycleDemo extends StatefulWidget {
const LifecycleDemo({super.key});
@override
State<LifecycleDemo> createState() => _LifecycleDemoState();
}
class _LifecycleDemoState extends State<LifecycleDemo> {
final List<String> _logs = [];
int _tick = 0;
void _log(String msg) {
// 用 addPostFrameCallback 避免在 build 阶段调用 setState
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() => _logs.insert(0, msg));
}
});
}
@override
void initState() {
super.initState();
_log('① initState ------ 初始化(仅一次,适合订阅、请求)');
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_log('② didChangeDependencies ------ 依赖变化(InheritedWidget)');
}
@override
void didUpdateWidget(covariant LifecycleDemo oldWidget) {
super.didUpdateWidget(oldWidget);
_log('③ didUpdateWidget ------ 父组件 rebuild');
}
@override
Widget build(BuildContext context) {
_log('④ build ------ 构建 UI(第 ${_tick + 1} 次)');
return Scaffold(
appBar: AppBar(title: const Text('3. 生命周期')),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(12),
child: ElevatedButton(
onPressed: () => setState(() => _tick++),
child: Text('触发 rebuild ($_tick)'),
),
),
Expanded(
child: ListView.builder(
itemCount: _logs.length,
itemBuilder: (context, i) => ListTile(
dense: true,
title: Text(_logs[i], style: const TextStyle(fontSize: 13)),
),
),
),
],
),
);
}
@override
void deactivate() {
_log('⑤ deactivate ------ 从树中移除');
super.deactivate();
}
@override
void dispose() {
_log('⑥ dispose ------ 销毁,释放资源');
super.dispose();
}
}
// ═══════════════════════════════════════════════════════════
// 知识点 4:mounted 检查
// ─────────────────────────────────────────────────────────
// 异步操作(网络请求、定时器)完成后,组件可能已被销毁
// 此时调用 setState 会报错,必须先检查 mounted
// ═══════════════════════════════════════════════════════════
class MountedDemo extends StatefulWidget {
const MountedDemo({super.key});
@override
State<MountedDemo> createState() => _MountedDemoState();
}
class _MountedDemoState extends State<MountedDemo> {
String _status = '等待中...';
bool _loading = false;
Future<void> _fetchData() async {
setState(() {
_loading = true;
_status = '请求中...';
});
// 模拟网络请求
await Future.delayed(const Duration(seconds: 3));
// ⚠️ 关键:异步回调前必须检查 mounted
// 如果用户在请求期间返回了上一页,组件已被 dispose
// 此时 setState 会抛 "setState() called after dispose()"
if (!mounted) return; // ← 没有这行会崩溃
setState(() {
_loading = false;
_status = '请求完成 ✅';
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('4. mounted 检查')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(_status, style: const TextStyle(fontSize: 20)),
const SizedBox(height: 8),
const Text(
'试试点下面按钮后立刻返回上一页\n(不检查 mounted 会崩溃)',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey, fontSize: 13),
),
const SizedBox(height: 20),
_loading
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: _fetchData,
child: const Text('发起请求(3秒)'),
),
],
),
),
);
}
}
// ═══════════════════════════════════════════════════════════
// 知识点 5:Key 与状态复用
// ─────────────────────────────────────────────────────────
// 默认情况下,同类型 Widget 在同位置会复用 State
// 用 Key 可以强制 Flutter 创建新 State(而非复用旧的)
// ═══════════════════════════════════════════════════════════
class KeyDemo extends StatefulWidget {
const KeyDemo({super.key});
@override
State<KeyDemo> createState() => _KeyDemoState();
}
class _KeyDemoState extends State<KeyDemo> {
// 两个开关:控制是否给子组件加 Key
bool _useKey = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('5. Key 与状态复用')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('当前:${_useKey ? "使用 ValueKey" : "不使用 Key"}',
style: const TextStyle(fontSize: 16)),
const SizedBox(height: 8),
const Text(
'下方两个方块颜色会交换\n观察它们的数字是否跟着颜色走',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey, fontSize: 13),
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_ColorBox(
key: _useKey ? const ValueKey('red') : null,
color: Colors.red,
label: '红',
),
const SizedBox(width: 16),
_ColorBox(
key: _useKey ? const ValueKey('blue') : null,
color: Colors.blue,
label: '蓝',
),
],
),
const SizedBox(height: 30),
ElevatedButton(
onPressed: () => setState(() => _useKey = !_useKey),
child: Text(_useKey ? '切换为:无 Key' : '切换为:有 Key'),
),
const SizedBox(height: 12),
Text(
_useKey
? '有 Key:State 跟着 Key 走(数字跟随颜色)'
: '无 Key:State 按位置复用(数字留在原位)',
style: const TextStyle(fontStyle: FontStyle.italic, color: Colors.green),
),
],
),
),
);
}
}
// 带内部状态的色块:点击数字 +1
class _ColorBox extends StatefulWidget {
final Color color;
final String label;
const _ColorBox({super.key, required this.color, required this.label});
@override
State<_ColorBox> createState() => _ColorBoxState();
}
class _ColorBoxState extends State<_ColorBox> {
int _count = 0;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => setState(() => _count++),
child: Container(
width: 80,
height: 80,
color: widget.color,
alignment: Alignment.center,
child: Text(
'${widget.label}\n$_count',
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.white, fontSize: 16),
),
),
);
}
}
// ═══════════════════════════════════════════════════════════
// 知识点 6:App 生命周期
// ─────────────────────────────────────────────────────────
// 通过 WidgetsBindingObserver 监听 App 前后台切换
// 适合:暂停/恢复定时器、保存草稿、上报埋点
// ═══════════════════════════════════════════════════════════
class AppLifecycleDemo extends StatefulWidget {
const AppLifecycleDemo({super.key});
@override
State<AppLifecycleDemo> createState() => _AppLifecycleDemoState();
}
class _AppLifecycleDemoState extends State<AppLifecycleDemo>
with WidgetsBindingObserver {
AppLifecycleState? _lastState;
final List<String> _logs = [];
@override
void initState() {
super.initState();
// 注册监听
WidgetsBinding.instance.addObserver(this);
_logs.add('已注册 App 生命周期监听');
}
@override
void dispose() {
// 必须取消监听,否则内存泄漏
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
// App 生命周期变化回调
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
setState(() {
_lastState = state;
_logs.insert(0, '${DateTime.now().toIso8601String().substring(11, 19)} ${state.name}');
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('6. App 生命周期')),
body: Column(
children: [
Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
color: Colors.blue.shade50,
child: Column(
children: [
const Text('当前状态', style: TextStyle(color: Colors.grey)),
Text(
_lastState?.name ?? '未知',
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
],
),
),
const Padding(
padding: EdgeInsets.all(12),
child: Text(
'切到后台再切回来,观察状态变化\n• resumed: 前台可见\n• inactive: 失去焦点(如来电)\n• paused: 进入后台\n• detached: 仍在运行但未渲染',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 13, color: Colors.grey),
),
),
Expanded(
child: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: _logs.length,
itemBuilder: (context, i) => ListTile(
dense: true,
title: Text(_logs[i], style: const TextStyle(fontSize: 13)),
),
),
),
],
),
);
}
}