面试复习题-Flutter场景题
🌟 场景题 1:实现一个“下拉刷新 + 上拉加载更多”的商品列表
需求:
- 商品列表支持下拉刷新。
- 滑动到底部自动加载更多。
- 加载中显示“加载中...”Footer。
- 加载失败显示“点击重试”按钮。
- 网络请求失败有重试机制。
✅ 高级回答要点:
dart
深色版本
class ProductList extends StatefulWidget {@override_ProductListState createState() => _ProductListState();
}class _ProductListState extends State<ProductList> {final ProductBloc _bloc = ProductBloc();final ScrollController _scrollController = ScrollController();@overridevoid initState() {_bloc.fetchProducts();_scrollController.addListener(_onScroll);super.initState();}void _onScroll() {if (_scrollController.position.pixels >=_scrollController.position.maxScrollExtent - 200) {_bloc.loadMore();}}@overrideWidget build(BuildContext context) {return RefreshIndicator(onRefresh: () => _bloc.refresh(),child: BlocBuilder<ProductBloc, ProductState>(bloc: _bloc,builder: (context, state) {if (state is ProductLoading && state.products.isEmpty) {return const Center(child: CircularProgressIndicator());}return ListView.builder(controller: _scrollController,itemCount: state.products.length + (state.hasMore ? 1 : 0),itemBuilder: (context, index) {if (index == state.products.length) {if (state.loadFailed) {return ElevatedButton(onPressed: _bloc.retryLoadMore,child: const Text('点击重试'),);}return const Padding(padding: EdgeInsets.all(16),child: Center(child: CircularProgressIndicator()),);}return ProductItem(product: state.products[index]);},);},),);}@overridevoid dispose() {_bloc.dispose();_scrollController.dispose();super.dispose();}
}
💡 考察点:
- 状态管理:使用
Bloc
或Riverpod
管理复杂状态(加载中、失败、是否有更多)。 - 性能:
ListView.builder
懒加载。 - 用户体验:Footer 显示加载/失败状态。
- 内存管理:
ScrollController
和Bloc
正确释放。 - 可维护性:逻辑与 UI 分离。
🌟 场景题 2:实现一个“登录页”,支持手机号、密码、验证码登录,且表单验证
需求:
- 手机号输入框(带格式化:138 **** ****)。
- 密码或验证码输入。
- “获取验证码”按钮(60秒倒计时)。
- 表单验证(手机号格式、密码长度、验证码6位)。
- 提交按钮禁用状态控制。
✅ 高级回答要点:
dart
深色版本
class LoginForm extends StatefulWidget {@override_LoginFormState createState() => _LoginFormState();
}class _LoginFormState extends State<LoginForm> {final _formKey = GlobalKey<FormState>();final _phoneController = TextEditingController();final _codeController = TextEditingController();bool _isCodeLogin = true;bool _isSubmitting = false;int _countdown = 0;@overridevoid initState() {super.initState();_phoneController.addListener(_formatPhone);}void _formatPhone() {// 实现手机号格式化:138****1234}void _startCountdown() {setState(() => _countdown = 60);Timer.periodic(const Duration(seconds: 1), (timer) {if (_countdown <= 1) {timer.cancel();setState(() => _countdown = 0);} else {setState(() => _countdown--);}});}void _submit() async {if (_formKey.currentState?.validate() ?? false) {setState(() => _isSubmitting = true);try {await login(_phoneController.text, _codeController.text);Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => Home()));} catch (e) {ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('登录失败: $e')));} finally {setState(() => _isSubmitting = false);}}}@overrideWidget build(BuildContext context) {return Form(key: _formKey,child: Column(children: [TextFormField(controller: _phoneController,decoration: const InputDecoration(labelText: '手机号'),validator: (value) => isValidPhone(value) ? null : '请输入正确的手机号',),if (_isCodeLogin) ...[TextFormField(controller: _codeController,decoration: InputDecoration(labelText: '验证码',suffixIcon: _countdown > 0? Text('$_countdowns'): TextButton(onPressed: _countdown == 0 ? _startCountdown : null,child: const Text('获取验证码'),),),validator: (value) => value?.length == 6 ? null : '验证码为6位',),] else ...[// 密码输入框],ElevatedButton(onPressed: (_formKey.currentState?.isValid ?? false) && !_isSubmitting ? _submit : null,child: _isSubmitting ? const CircularProgressIndicator() : const Text('登录'),),],),);}
}
💡 考察点:
- 表单管理:
GlobalKey<FormState>
+TextFormField
。 - 输入控制:
TextEditingController
监听和格式化。 - 状态反馈:倒计时、提交中 loading 状态。
- 用户体验:按钮禁用、错误提示。
- 可扩展性:支持密码/验证码切换。
🌟 场景题 3:实现一个“聊天界面”,支持文本、图片、语音消息
需求:
- 消息列表(左:别人,右:自己)。
- 支持文本、图片、语音消息。
- 图片可点击放大。
- 语音消息可播放/暂停。
- 输入框支持文字输入和发送。
✅ 高级回答要点:
dart
深色版本
// 1. 定义消息类型
sealed class ChatMessage {final String sender;final DateTime timestamp;
}class TextMessage extends ChatMessage {final String text;TextMessage({required this.text, required super.sender, required super.timestamp});
}class ImageMessage extends ChatMessage {final String imageUrl;ImageMessage({required this.imageUrl, required super.sender, required super.timestamp});
}class VoiceMessage extends ChatMessage {final String audioUrl;final int duration;VoiceMessage({required this.audioUrl, required this.duration, required super.sender, required super.timestamp});
}// 2. 消息项 Widget
Widget buildMessage(ChatMessage message) {final isMe = message.sender == 'me';return Row(mainAxisAlignment: isMe ? MainAxisAlignment.end : MainAxisAlignment.start,children: [Container(padding: const EdgeInsets.all(8),margin: const EdgeInsets.symmetric(vertical: 4),decoration: BoxDecoration(color: isMe ? Colors.blue : Colors.grey[300],borderRadius: BorderRadius.circular(8),),child: _buildMessageContent(message),),],);
}Widget _buildMessageContent(ChatMessage message) {if (message is TextMessage) {return Text(message.text, style: TextStyle(color: message.sender == 'me' ? Colors.white : Colors.black));} else if (message is ImageMessage) {return GestureDetector(onTap: () => showImageDialog(message.imageUrl),child: Image.network(message.imageUrl, width: 150),);} else if (message is VoiceMessage) {return VoicePlayer(audioUrl: message.audioUrl, duration: message.duration);}return const SizedBox();
}
💡 考察点:
- 数据建模:使用
sealed class
(Dart 3.0)或enum + factory
区分消息类型。 - UI 复用:
_buildMessageContent
分类型渲染。 - 交互:图片点击放大(
showDialog
+PhotoView
)。 - 第三方库:
audioplayers
播放语音。 - 性能:图片懒加载、缓存。
🌟 场景题 4:实现一个“商品详情页”,包含轮播图、标题、价格、规格选择、购买按钮
需求:
- 顶部轮播图(支持自动播放、指示器)。
- 商品标题、价格、销量。
- 规格选择(颜色、尺寸),选择后更新价格。
- “加入购物车”和“立即购买”按钮。
- 页面滑动时,标题栏颜色渐变。
✅ 高级回答要点:
dart
深色版本
class ProductDetailPage extends StatefulWidget {@override_ProductDetailPageState createState() => _ProductDetailPageState();
}class _ProductDetailPageState extends State<ProductDetailPage> {final ScrollController _scrollController = ScrollController();double _appBarOpacity = 0;@overridevoid initState() {_scrollController.addListener(() {final offset = _scrollController.offset;setState(() {_appBarOpacity = offset.clamp(0.0, 1.0);});});super.initState();}@overrideWidget build(BuildContext context) {return Scaffold(body: Stack(children: [CustomScrollView(controller: _scrollController,slivers: [SliverAppBar(pinned: true,backgroundColor: Colors.white.withOpacity(_appBarOpacity),title: Text('商品详情', style: TextStyle(color: _appBarOpacity > 0.5 ? Colors.black : Colors.white)),),SliverToBoxAdapter(child: Column(children: [CarouselSlider(...), // 轮播图ProductInfo(), // 标题、价格SpecSelector(), // 规格选择BuyButtons(), // 购买按钮],),),],),],),);}
}
💡 考察点:
- 复杂布局:
CustomScrollView
+SliverAppBar
实现渐变标题。 - 状态联动:规格选择 → 价格更新(
Provider
或Bloc
)。 - 第三方库:
carousel_slider
实现轮播。 - 用户体验:平滑滚动、视觉反馈。
🌟 场景题 5:实现一个“后台定位服务”(Android/iOS)
需求:
- 应用在后台时持续获取用户位置。
- 位置变化超过 100 米上报一次。
- 低功耗模式(不影响电池)。
- 前台服务显示通知(Android)。
✅ 高级回答要点:
- 插件选择:
geolocator
+workmanager
(定时任务)或flutter_background_service
。 - 权限:
- Android:
ACCESS_FINE_LOCATION
,ACCESS_BACKGROUND_LOCATION
- iOS:
NSLocationAlwaysAndWhenInUseUsageDescription
- Android:
- 实现:dart
深色版本
final service = FlutterBackgroundService(); service.startForeground(onStart: (service) {service.on('location').listen((data) {Geolocator.getPositionStream(distanceFilter: 100, // 100米变化desiredAccuracy: LocationAccuracy.low,).listen((position) {// 上报位置sendToServer(position);});});}, );
- 保活:Android 前台服务 + 通知,iOS
BGTaskScheduler
。
💡 考察点:
- 平台特性:了解 Android/iOS 后台限制。
- 插件能力:选择合适的插件。
- 功耗优化:低精度、距离过滤。
- 合规性:隐私权限、用户提示。
总结:如何应对 Flutter 场景题?
步骤 | 说明 |
---|---|
1. 理解需求 | 问清楚边界条件、异常情况 |
2. 设计数据模型 | 定义 class 、enum 、state |
3. 选择状态管理 | Provider 、Bloc 、Riverpod |
4. 构建 UI 结构 | Widget 拆分、复用 |
5. 处理交互 | 手势、动画、表单 |
6. 考虑性能 | const 、ListView.builder 、Isolate |
7. 异常与加载 | loading 、error 、retry |
8. 内存与生命周期 | dispose() 、controller 释放 |
记住:面试官想看到的是你的思考过程,边说边写,展示架构思维和工程素养。