{ "name": "Flutter 开发实战", "lvNames": [ "Widget 基础", "布局与组件", "状态管理", "路由与导航", "数据与网络", "实战综合" ], "questions": [ { "lv": 1, "q": "新建 Flutter 项目后,把应用真正跑起来的入口代码是?", "opts": [ "void main() { MyApp(); }", "void main() => runApp(MyApp());", "runApp() { MyApp }", "main() { MaterialApp(); }" ], "ans": 1, "exp": "Dart 程序入口是 main(),Flutter 通过 runApp(Widget) 把根组件挂载到引擎上。注意 MyApp() 是创建 Widget 实例,不能少了括号。" }, { "lv": 1, "q": "实现一个\"3 秒倒计时\"按钮,每秒刷新显示剩余秒数,应使用?", "opts": [ "StatefulWidget", "Container", "Scaffold", "StatelessWidget" ], "ans": 0, "exp": "有动态变化的数据(倒计时数字)就需要 StatefulWidget:数据保存在 State 里,变化时调 setState() 刷新。没有可变状态时应优先用 StatelessWidget。" }, { "lv": 1, "q": "界面上要显示一行固定文案\"登录成功\",最合适的组件是?", "opts": [ "RichText", "Paragraph", "Label", "Text" ], "ans": 3, "exp": "Text 是最常用的文本组件,可通过 style 配置字号、颜色、字重等。RichText 用于一段文字中混排多种样式的富文本场景。" }, { "lv": 1, "q": "点击\"确认\"按钮后分数要 +1 并立即显示到界面,正确流程是?", "opts": [ "修改 Widget 的构造参数", "重启整个 App", "直接修改字段就行", "修改 State 字段后调用 setState()" ], "ans": 3, "exp": "State 里的字段只是普通数据,必须调用 setState() 通知框架\"数据变了\",框架才会重新执行 build 刷新 UI。注意 setState 只重建当前 State 的子树,不是整棵树。" }, { "lv": 1, "q": "要展示 10000 条商品记录,性能最优的做法是?", "opts": [ "GridView.count 全部构建", "Column 里循环放 10000 个组件", "SingleChildScrollView 包 Column", "ListView.builder(按需构建可见项)" ], "ans": 3, "exp": "ListView.builder 只构建当前可见的列表项(懒加载),滚动时回收复用;Column + for 会一次性构建全部组件,万条数据直接卡死。数据量大优先 builder 系列。" }, { "lv": 2, "q": "登录页要垂直排列\"输入框、输入框、登录按钮\",用哪个布局组件?", "opts": [ "Column", "Wrap", "Stack", "Row" ], "ans": 0, "exp": "Column 垂直方向排列子组件,Row 水平方向,Stack 叠加并支持定位,Wrap 在空间不足时自动换行。" }, { "lv": 2, "q": "Row 里想让一个按钮占满剩余宽度(剩余空间全给它),按钮外层套?", "opts": [ "Padding", "Expanded", "Center", "Spacer" ], "ans": 1, "exp": "Expanded 让子组件占据主轴的剩余空间(Row 的主轴是水平方向)。多个 Expanded 可通过 flex 参数按比例分配空间。" }, { "lv": 2, "q": "Column 里有几个按钮,想让它们在水平方向居中,应该设置?", "opts": [ "mainAxisAlignment: MainAxisAlignment.center", "textAlign: TextAlign.center", "crossAxisAlignment: CrossAxisAlignment.center", "alignment: Alignment.center" ], "ans": 2, "exp": "Column 的主轴是垂直方向、交叉轴是水平方向。水平居中=沿交叉轴居中=crossAxisAlignment.center;mainAxisAlignment 控制的是垂直方向。主/交叉轴搞反是新手最常踩的坑。" }, { "lv": 2, "q": "封面图要铺满整个区域且不拉伸变形(多出的部分裁掉),fit 参数应该用?", "opts": [ "BoxFit.scaleDown", "BoxFit.none", "BoxFit.fill", "BoxFit.cover" ], "ans": 3, "exp": "cover:等比缩放填满、超出裁剪,常用于头像、封面;fill 会拉伸变形;contain 完整显示但可能留白;none 不缩放。" }, { "lv": 2, "q": "商品名过长时希望单行显示,超出部分显示省略号,Text 的配置是?", "opts": [ "softWrap: false 就够了", "只设 overflow: TextOverflow.clip", "只设 maxLines: 3", "maxLines: 1, overflow: TextOverflow.ellipsis" ], "ans": 3, "exp": "maxLines 限制最大行数 + overflow: TextOverflow.ellipsis 指定超出的表现(省略号/裁剪/fade),两者配合才能出现\"…\"效果。" }, { "lv": 3, "q": "输入框需要\"预填默认值 + 按钮点击后读取内容 + 随时清空\",推荐用?", "opts": [ "GlobalKey 临时查找", "无法实现", "每次读 onChanged 参数", "TextEditingController" ], "ans": 3, "exp": "TextEditingController 是输入框的\"数据源\":TextEditingController(text: \"预填\") 初始化,controller.text 读取,controller.clear() 清空。用完后在 dispose 里释放。" }, { "lv": 3, "q": "页面销毁时,取消监听、释放 Controller 的代码应该写在哪里?", "opts": [ "build()", "initState()", "dispose()", "didChangeDependencies()" ], "ans": 2, "exp": "dispose() 是 State 被移除时的清理钩子:取消 Timer、Stream 订阅、释放 Controller。不清理会造成内存泄漏和 \"setState called after dispose\" 报错。" }, { "lv": 3, "q": "登录状态、用户信息要被多个页面共享,又不想层层传构造参数,项目常用方案是?", "opts": [ "每个页面重新请求接口", "Provider / InheritedWidget", "把状态塞进路由参数", "定义全局变量直接读写" ], "ans": 1, "exp": "Flutter 官方的底层方案是 InheritedWidgetProvider 是对它的优雅封装:共享数据放顶层,子组件 context.watch() 取用并自动刷新。全局变量没有通知机制,页面不会自动更新。" }, { "lv": 3, "q": "商品列表页要支持下拉刷新,应该把 ListView 包进哪个组件?", "opts": [ "AnimatedList", "Scrollbar", "GestureDetector", "RefreshIndicator" ], "ans": 3, "exp": "RefreshIndicator 包住可滚动组件,配合 onRefresh 返回的 Future 实现下拉刷新动画。这是 Material 规范自带的下拉刷新方案。" }, { "lv": 3, "q": "App 切到后台、再回到前台时,需要暂停/恢复计时器,通过什么监听?", "opts": [ "WidgetsBindingObserver", "MediaQuery", "NavigatorObserver", "RouteObserver" ], "ans": 0, "exp": "给 State 混入 with WidgetsBindingObserver,在 didChangeAppLifecycleState() 中监听 AppLifecycleState(paused / resumed / inactive),即可感知前后台切换。" }, { "lv": 4, "q": "列表页点击商品跳转详情页,标准写法是?", "opts": [ "Navigator.go(DetailPage(id: id))", "DetailPage(id: id).show()", "Route.open(DetailPage())", "Navigator.push(context, MaterialPageRoute(builder: (_) => DetailPage(id: id)))" ], "ans": 3, "exp": "Navigator.push 把新路由压入导航栈,MaterialPageRoute 提供转场动画,返回上一页用 Navigator.pop(context)。" }, { "lv": 4, "q": "详情页编辑完要把结果回传给列表页,详情页返回时应该写?", "opts": [ "Navigator.back(result)", "Navigator.pop(context, result)", "context.popAndPush(result)", "Navigator.pushResult(result)" ], "ans": 1, "exp": "pop(context, 返回值) 的第二个参数就是回传的数据,列表页 await push(...) 的返回值即可收到。注意 pop 的参数是\"返回给上一页的数据\",不是新路由。" }, { "lv": 4, "q": "跳转时需要传递商品 id、名称等参数,团队协作中最推荐的传参方式是?", "opts": [ "static 静态字段存", "URL 字符串拼接", "全局变量临时存", "DetailPage(id: 1, name: \"商品\") 构造参数传递" ], "ans": 3, "exp": "构造参数显式、类型安全、可读性好——跳转处一眼看到传了什么。全局变量/静态字段是隐藏依赖,页面多起来后非常难维护。" }, { "lv": 4, "q": "在 App 里注册命名路由(如 /login/home),配置位置是?", "opts": [ "MaterialApp(routes: {...})", "main() 函数里", "每个页面里各自声明", "runApp() 参数" ], "ans": 0, "exp": "命名路由集中配置在 MaterialApproutes 表,之后用 Navigator.pushNamed(context, \"/login\") 跳转;需要传参时配合 arguments 参数。" }, { "lv": 4, "q": "跳到选择页,用户选完返回后要根据选择结果继续处理,列表页怎么写?", "opts": [ "跳转时把回调函数传过去", "final r = await Navigator.push(...); 拿到返回值再处理", "用 setState 轮询", "无法等待结果" ], "ans": 1, "exp": "push 返回 Future<T>,await 后拿到 pop 回传的值,再执行后续逻辑(如刷新选中项)。这是\"页面间取结果\"的标准姿势。" }, { "lv": 5, "q": "接口返回 JSON 字符串,执行 var data = jsonDecode(text); 后 data 的类型是?", "opts": [ "Map<String, dynamic>", "dynamic(实际可能是 Map 或 List)", "String", "Object" ], "ans": 1, "exp": "jsonDecode 的返回类型是 dynamic:JSON 对象变 Map<String, dynamic>、数组变 List<dynamic>、数字/布尔原样返回。通常需要手动转换:jsonDecode(text) as Map<String, dynamic>。" }, { "lv": 5, "q": "要把用户对象 User(id: 1, name: \"张三\") 转成 JSON 字符串发给服务器,正确流程是?", "opts": [ "JSON.stringify(user)", "给 User 写 toJson(),再 jsonEncode(user.toJson())", "jsonEncode(user) 直接序列化", "user.toString()" ], "ans": 1, "exp": "Dart 默认不会自动序列化任意对象,惯例是模型类自己实现 Map<String, dynamic> toJson(),再用 jsonEncode 转字符串;反序列化则对应 User.fromJson(Map) 工厂构造。" }, { "lv": 5, "q": "网络请求可能超时/断网,用户点\"登录\"时抛出了异常,正确的处理是?", "opts": [ "用 try-catch 包裹 await 请求,捕获异常并提示用户", "不做处理", "打印日志后忽略", "延迟 3 秒自动重试" ], "ans": 0, "exp": "try { await api.login(...) } catch (e) { 提示\"网络异常,请重试\" } 是最基础的错误处理;生产环境还会区分 SocketException(断网)、TimeoutException(超时)等做分类提示。" }, { "lv": 5, "q": "请求进行中需要展示\"加载中\"状态,最合适用?", "opts": [ "AnimatedContainer 闪烁", "CircularProgressIndicator", "Opacity 半透明遮罩", "Text(\"加载中...\")" ], "ans": 1, "exp": "Material 标准的加载指示器是 CircularProgressIndicator(转圈)和 LinearProgressIndicator(条),配合 loading 状态变量做条件渲染。" }, { "lv": 5, "q": "http 请求要设置 10 秒超时,正确写法是?", "opts": [ "Timer(Duration(seconds: 10), ...)", "await http.get(url).timeout(Duration(seconds: 10))", "Future.any 自动超时", "http.get(url, timeout: 10)" ], "ans": 1, "exp": "Future 自带 .timeout():超时抛 TimeoutException,可再接 onTimeout 参数兜底。http 包本身没有 timeout 参数,这是常见的误解。" }, { "lv": 6, "q": "首页同时请求\"轮播图、公告、商品列表\"三个接口,全部完成再渲染页面,用?", "opts": [ "三个 await 串行", "Future.wait([...]) 并发等待", "依次 catch 分别显示", "用 Future.any" ], "ans": 1, "exp": "并发场景用 Future.wait,总耗时约等于最慢的请求而不是三个相加。生产上可配合记录单个请求失败做局部降级(某块失败不影响整页)。" }, { "lv": 6, "q": "商品列表滑到底部要自动加载下一页,监听滚动的方案是?", "opts": [ "ScrollController 监听 pixels 接近 maxScrollExtent 时触发", "GestureDetector 监听手势", "用 Timer 每 3 秒加载", "下拉刷新代替" ], "ans": 0, "exp": "给 ListView 挂 ScrollController,在 addListener 里判断 position.pixels >= position.maxScrollExtent - 200(预留阈值)就加载下一页,配合 loading 状态防止重复触发。" }, { "lv": 6, "q": "商品标题中要红色高亮\"热卖\"二字、其余部分灰色,用?", "opts": [ "用换行符拼接字符串", "拆成多个 Text 排一排", "RichText + TextSpan 组合不同样式", "TextStyle 做不到,只能整段同色" ], "ans": 2, "exp": "RichTextTextSpan 支持树状嵌套:每个 span 可以有自己的 style,是\"一段文字多种样式\"的标准方案。" }, { "lv": 6, "q": "界面出现黄黑条纹溢出警告(RenderFlex overflowed),说明?", "opts": [ "内存泄漏", "子组件超出了父组件给定的约束空间", "主题颜色配置错误", "字体加载失败" ], "ans": 1, "exp": "Row/Column 空间不够时子组件溢出,debug 模式显示黄黑条纹并打印 \"RenderFlex overflowed by XX pixels\"。解决思路:Expanded/Flexible 让出空间、SingleChildScrollView 滚动、FittedBox 缩放。" }, { "lv": 6, "q": "瀑布流商品图列表很卡、图片解码占内存高,优先优化手段是?", "opts": [ "cacheWidth / ResizeImage 限制解码尺寸", "减少列表项数量", "给图片加圆角", "提高图片的清晰度" ], "ans": 0, "exp": "Image.network 默认按原图尺寸解码,一张 4000px 大图在手机上就是内存杀手。用 cacheWidth: 300ResizeImage 让引擎按需解码成小尺寸位图,内存占用和滚动性能立竿见影。" } ] }