Files
flutter_learning/scripts/data/set-flutter-dev.json
T
2026-08-10 00:15:44 +08:00

374 lines
17 KiB
JSON
Raw Blame History

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