面试复习题-Flutter场景题

发布于:2025-09-07 ⋅ 阅读:(21) ⋅ 点赞:(0)

🌟 场景题 1:实现一个“下拉刷新 + 上拉加载更多”的商品列表

需求

  • 商品列表支持下拉刷新。
  • 滑动到底部自动加载更多。
  • 加载中显示“加载中...”Footer。
  • 加载失败显示“点击重试”按钮。
  • 网络请求失败有重试机制。
✅ 高级回答要点:

dart

深色版本

class ProductList extends StatefulWidget {
  @override
  _ProductListState createState() => _ProductListState();
}

class _ProductListState extends State<ProductList> {
  final ProductBloc _bloc = ProductBloc();
  final ScrollController _scrollController = ScrollController();

  @override
  void initState() {
    _bloc.fetchProducts();
    _scrollController.addListener(_onScroll);
    super.initState();
  }

  void _onScroll() {
    if (_scrollController.position.pixels >=
        _scrollController.position.maxScrollExtent - 200) {
      _bloc.loadMore();
    }
  }

  @override
  Widget 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]);
            },
          );
        },
      ),
    );
  }

  @override
  void dispose() {
    _bloc.dispose();
    _scrollController.dispose();
    super.dispose();
  }
}
💡 考察点:
  1. 状态管理:使用 Bloc 或 Riverpod 管理复杂状态(加载中、失败、是否有更多)。
  2. 性能ListView.builder 懒加载。
  3. 用户体验:Footer 显示加载/失败状态。
  4. 内存管理ScrollController 和 Bloc 正确释放。
  5. 可维护性:逻辑与 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;

  @override
  void 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);
      }
    }
  }

  @override
  Widget 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('登录'),
          ),
        ],
      ),
    );
  }
}
💡 考察点:
  1. 表单管理GlobalKey<FormState> + TextFormField
  2. 输入控制TextEditingController 监听和格式化。
  3. 状态反馈:倒计时、提交中 loading 状态。
  4. 用户体验:按钮禁用、错误提示。
  5. 可扩展性:支持密码/验证码切换。

🌟 场景题 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();
}
💡 考察点:
  1. 数据建模:使用 sealed class(Dart 3.0)或 enum + factory 区分消息类型。
  2. UI 复用_buildMessageContent 分类型渲染。
  3. 交互:图片点击放大(showDialog + PhotoView)。
  4. 第三方库audioplayers 播放语音。
  5. 性能:图片懒加载、缓存。

🌟 场景题 4:实现一个“商品详情页”,包含轮播图、标题、价格、规格选择、购买按钮

需求

  • 顶部轮播图(支持自动播放、指示器)。
  • 商品标题、价格、销量。
  • 规格选择(颜色、尺寸),选择后更新价格。
  • “加入购物车”和“立即购买”按钮。
  • 页面滑动时,标题栏颜色渐变。
✅ 高级回答要点:

dart

深色版本

class ProductDetailPage extends StatefulWidget {
  @override
  _ProductDetailPageState createState() => _ProductDetailPageState();
}

class _ProductDetailPageState extends State<ProductDetailPage> {
  final ScrollController _scrollController = ScrollController();
  double _appBarOpacity = 0;

  @override
  void initState() {
    _scrollController.addListener(() {
      final offset = _scrollController.offset;
      setState(() {
        _appBarOpacity = offset.clamp(0.0, 1.0);
      });
    });
    super.initState();
  }

  @override
  Widget 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(),      // 购买按钮
                  ],
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}
💡 考察点:
  1. 复杂布局CustomScrollView + SliverAppBar 实现渐变标题。
  2. 状态联动:规格选择 → 价格更新(Provider 或 Bloc)。
  3. 第三方库carousel_slider 实现轮播。
  4. 用户体验:平滑滚动、视觉反馈。

🌟 场景题 5:实现一个“后台定位服务”(Android/iOS)

需求

  • 应用在后台时持续获取用户位置。
  • 位置变化超过 100 米上报一次。
  • 低功耗模式(不影响电池)。
  • 前台服务显示通知(Android)。
✅ 高级回答要点:
  1. 插件选择geolocator + workmanager(定时任务)或 flutter_background_service
  2. 权限
    • Android: ACCESS_FINE_LOCATIONACCESS_BACKGROUND_LOCATION
    • iOS: NSLocationAlwaysAndWhenInUseUsageDescription
  3. 实现
     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);
          });
        });
      },
    );
  4. 保活:Android 前台服务 + 通知,iOS BGTaskScheduler
💡 考察点:
  1. 平台特性:了解 Android/iOS 后台限制。
  2. 插件能力:选择合适的插件。
  3. 功耗优化:低精度、距离过滤。
  4. 合规性:隐私权限、用户提示。

总结:如何应对 Flutter 场景题?

步骤 说明
1. 理解需求 问清楚边界条件、异常情况
2. 设计数据模型 定义 classenumstate
3. 选择状态管理 ProviderBlocRiverpod
4. 构建 UI 结构 Widget 拆分、复用
5. 处理交互 手势、动画、表单
6. 考虑性能 constListView.builderIsolate
7. 异常与加载 loadingerrorretry
8. 内存与生命周期 dispose()controller 释放

记住:面试官想看到的是你的思考过程,边说边写,展示架构思维和工程素养。


网站公告

今日签到

点亮在社区的每一天
去签到