☰
Flutter-Notebook 之 flutter_widget_of_the_week:30+ 个官方 Widget 的源码级实战解析
2026/10/15 14:20:29 网站建设 项目流程
  • 示例工程
  • 移动开发

【免费下载链接】Flutter-Notebook

FlutterDemo合集,今天你fu了吗

项目地址:https://gitcode.com/gh_mirrors/fl/Flutter-Notebook
点击查看免费下载

本文以 Flutter-Notebook 仓库中的 flutter_widget_of_the_week 示例项目 为骨架,逐周拆解其同步自 Google "Flutter Widget of the Week" 系列视频的 30 余个 Widget 演示:既有每个 Widget 的核心用途、关键参数与最小可运行示例,又结合仓库内 31 个独立 demo 源码(lib/widget 目录)给出实现级佐证。读完本文,你将掌握 SafeArea、AnimatedContainer、FutureBuilder、SliverAppBar、Hero、InheritedModel 等常用 Widget 的实战姿势,并了解如何在这个工程中快速切换、验证每一个 Widget 的演示效果。

一、项目背景:官方 Widget of the Week 的本土化同步

按项目 README 的说明,flutter_widget_of_the_week是一个同步 Google "Flutter Widget of the Week" 系列视频的 demo 工程,并"持续更新中"。官方的 Widget of the Week 是 Google 面向 Flutter 开发者推出的每周一个 Widget 短讲解系列,本期对应的视频清单为:

  • YouTube 播放列表:PLOU2XLYxmsIL0pH0zWe_ZOHgGhZ7UasUE
  • B 站搬运版(带中文字幕):av40410258

该工程把视频中讲解的每一个 Widget 都落地为一个独立、可单独运行的 Dart 页面文件,配上中文注释说明其用途、关键参数与注意事项,是学习 Flutter 常用 Widget 的极佳中文参考资料。与仓库根目录的 README 将 每个 Widget 的演示截图 汇集展示不同,示例工程内部更强调"一 Widget 一文件、源码即文档"的组织方式。

二、工程结构:如何组织 30 个 Widget 演示

示例工程是一个标准的 Flutter 应用骨架,核心代码集中在lib/下:

路径作用
lib/main.dart应用入口,通过home:切换当前要展示的 Widget 页面
lib/widget/31 个week*_*.dart演示页面源码
lib/widget/widgets.dart统一export全部演示页面的汇总文件
assets/演示用素材(如myimage.jpg,被 week5/13/17/24 等复用)
test/widget_test.dartFlutter 模板自带的 smoke test

2.1 入口与切换方式

main.dart 中使用MaterialApp+ThemeData(primarySwatch: Colors.blue)构建应用,home字段通过注释切换的方式在Week1()到Week30()之间选择,当前默认展示的是Week31()(Draggable 演示),例如想看 SafeArea 就把home: Week31()改为home: Week1():

void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData(primarySwatch: Colors.blue), // home: Week1(), ... home: Week30(), home: Week31(), ); } }

main.dart通过import 'widget/widgets.dart'引入全部页面,而 widgets.dart 则用 31 行export语句将每个week*.dart批量导出,保证入口文件极简。这是 Flutter 工程中"批量导出页面模块"的一种实用组织手法。

2.2 依赖与环境

pubspec.yaml 中声明:

  • Dart SDK 环境约束:sdk: ">=2.0.0-dev.68.0 <3.0.0"(早期 Flutter 2.x 之前的版本区间,若在较新环境运行需按提示升级约束);
  • 依赖:flutterSDK 与cupertino_icons: ^0.1.2;开发依赖flutter_test;
  • 资源声明:assets: - assets/,因此代码中可直接以AssetImage('assets/myimage.jpg')引用图片。

由于 test/widget_test.dart 仍是脚手架默认的计数器 smoke test(Counter increments smoke test),它验证的是模板计数器逻辑而非上述周 Widget,可推断测试与演示页面尚未打通,运行验证以真机/模拟器直接flutter run为主。

三、布局类 Widget 逐个拆解

这一组 Widget 解决"子组件怎么摆"的问题,覆盖了 Flutter 布局体系中最常用的几类约束与排列方式。

Week1 SafeArea:自动规避异型屏遮挡

week1_safearea.dart 的注释点明了 SafeArea 的核心价值:自动检测异型屏(刘海屏/挖孔屏),并添加边距保证 child 不被遮挡。

return SafeArea( child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Container(height: 200, color: Colors.pinkAccent), Container(height: 200, color: Colors.pinkAccent), ], ), );

实战要点(来自源码注释):

  • top、left、right、bottom四个方向默认都为true,即四边都做规避;
  • 想取消某一边的规避,只需把对应属性设为false;
  • minimum属性可为规避后的区域再追加一个默认边距,用于留白更充裕的场景。

Week2 Expanded:按比例撑满主轴剩余空间

week2_expanded.dart 演示了 Row/Column/Flex 中的空间分配逻辑:Row 与 Column 会先把固定大小的子组件放入,再处理被 Expanded 包裹的组件,后者沿主轴展开、默认撑满剩余空间,用flex控制展开比例。

Column( children: <Widget>[ Container(height: 200, color: Colors.pinkAccent, child: Text('01')), Expanded(flex: 3, child: Container(color: Colors.greenAccent, child: Text('02'))), Expanded(flex: 1, child: Container(color: Colors.pinkAccent, child: Text('03'))), ], )

这里flex: 3与flex: 1意味着剩余高度按 3:1 分配,是"自适应填满剩余空间"最常用的手段。

Week3 Wrap:空间不足自动换行

week3_wrap.dart 用 10 个 100x100 的彩色 Container 演示自动换行。源码注释完整列出了参数语义:

参数作用
direction布局方向,即主轴方向(Axis.horizontal/Axis.vertical)
alignment主轴对齐方式(WrapAlignment.*)
runAlignment副轴(换行后的行/列)对齐方式
spacing主轴上 child 之间的间距
runSpacing副轴上 child 之间的间距
textDirection决定排列顺序(示例用TextDirection.rtl从右往左排)
Wrap( direction: Axis.horizontal, alignment: WrapAlignment.center, runAlignment: WrapAlignment.center, spacing: 10, runSpacing: 10, textDirection: TextDirection.rtl, children: <Widget>[ /* 10 个彩色 Container */ ], )

当主轴方向空间放不下时,Wrap 会自动向副轴方向扩充,这是与 Row/Column 最大的区别。

Week10 Table:行列都不滚动时的表格布局

week10_table.dart 的注释给出使用前提:当行和列同时存在且不需要滚动时,可以使用 Table。其核心配置:

  • children接收一组TableRow代表每行;
  • columnWidths接收Map<int, TableColumnWidth>控制每列占比,默认 1.0 即平均分(示例中0: FractionColumnWidth(0.2)、1: FractionColumnWidth(0.3));
  • defaultVerticalAlignment控制行内垂直对齐(示例TableCellVerticalAlignment.bottom);
  • border可设置TableBorder(示例画了 48px 的黑色顶边);
  • 单个TableRow内可通过decoration: BoxDecoration(...)设置整行背景色。
Table( defaultVerticalAlignment: TableCellVerticalAlignment.bottom, border: TableBorder(top: BorderSide(color: Colors.black, width: 48)), columnWidths: { 0: FractionColumnWidth(0.2), 1: FractionColumnWidth(0.3), }, children: [ /* TableRow(...) x 3 */ ], )

Week20 FittedBox:让子组件按指定模式适配父容器

week20_fitted_box.dart 在 200x100 的外层 Container 里放一个 1x1 的紫色 Container,并通过BoxFit.fitHeight与Alignment.center让子组件按高填满、居中适配:

Container( height: 100, width: 200, color: Colors.deepOrangeAccent, child: FittedBox( fit: BoxFit.fitHeight, alignment: Alignment.center, child: Container(height: 1, width: 1, color: Colors.deepPurpleAccent), ), )

fit可取值contain、cover、fill、fitWidth、fitHeight等,是处理"子组件尺寸与父容器不匹配"的第一选择。

Week21 LayoutBuilder:按父约束响应式切换布局

week21_layout_builder.dart 是响应式布局的经典示范:根据constraints.maxWidth决定使用列表还是网格。

LayoutBuilder(builder: (context, constraints) { if (constraints.maxWidth < 500) return ListView.builder(itemBuilder: (context, index) => Center(child: Text('$index'))); else return GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2), itemBuilder: (context, index) => Center(child: Text('$index'))); })

constraints携带来自父级的最大/最小宽高约束,据此即可在手机与平板(或横竖屏)间切换不同的布局形态。

Week25 Align:在父容器内定位子组件

week25_align.dart 极简:Align(alignment: Alignment(1.0, 1.0), child: Text('Align'))。Alignment(x, y)中(-1,-1)为左上、(1,1)为右下、(0,0)为中心,取值超出 [-1,1] 时子组件还会超出边界摆放。

Week26 Positioned:Stack 中的精确坐标定位

week26_position.dart 在 200x200 的黄色 Container(内含 Stack)里组合使用三种 Positioned:

  • Positioned(left: -50, top: -50):负坐标把 FlutterLogo 拉出边界,配合Stack(overflow: Overflow.visible)允许溢出显示;
  • 不带定位参数的Positioned:默认从左上角开始;
  • Positioned.fill:填满 Stack 全部区域(示例中覆盖了一层 teal 色的 FlutterLogo)。

Week29 SizedBox:固定尺寸、拉伸按钮与扩展填充

week29_sizedbox.dart 展示了 SizedBox 的三种典型用法:

  • SizedBox(height: 200):仅限定高度,作为纵向留白/占位;
  • SizedBox(height: 200, width: double.infinity):让按钮横向撑满,配合FlatButton做出全宽按钮;
  • SizedBox.expand(child: ...):填满父容器剩余空间(示例中在 200 高的 Container 内把按钮拉伸铺满)。

四、动画类 Widget:从隐式动画到显式动画

Week4 AnimatedContainer:一条语句触发补间动画

week4_animated_container.dart 演示了 Flutter 的隐式动画:只改变 Container 的属性值并setState,框架就会自动在旧值和新值之间创建补间动画。示例维护了颜色、尺寸、圆角三个状态,通过三个按钮切换:

AnimatedContainer( duration: Duration(seconds: 1), curve: Curves.fastOutSlowIn, height: _size, width: _size, decoration: BoxDecoration( borderRadius: BorderRadius.circular(_radius), color: _color, ), )

要点:

  • duration必填,决定动画时长;
  • curve可选,控制动画曲线(Curves.fastOutSlowIn为 Material 默认缓动之一);
  • 颜色在deepPurpleAccent/deepOrange、尺寸在 200/100、圆角在 0/120 之间切换,每次切换都会触发平滑动画。

Week5 Opacity:透明度的三种状态与性能差异

week5_opacity.dart 在一个Stack(fit: StackFit.expand)中叠放图片与半透明渐变层:

Opacity( opacity: 0.5, child: Container( height: 300, width: 300, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.white, Colors.black], )), ), )

源码注释点出的关键语义:

  • opacity: 0时 child 直接不显示,但仍然占位;
  • opacity: 1时完全显示;
  • 取值在 0~1 之间时,Opacity 会先把 child绘制到中间缓存区再合成,因此通常比 0/1 两个极端值消耗更多性能——需要频繁变化的透明度优先考虑AnimatedOpacity或FadeTransition。

Week7 FadeTransition:用 Animation 驱动透明度

week7_fade_transition.dart 是显式动画的入门范例:用AnimationController+Tween(begin: 0.5, end: 1.0)+CurvedAnimation(curve: Curves.fastOutSlowIn)生成Animation<double>,交给FadeTransition(opacity: _animation)消费:

_controller = AnimationController(vsync: this, duration: Duration(seconds: 2)); _animation = Tween(begin: 0.5, end: 1.0) .animate(CurvedAnimation(parent: _controller, curve: Curves.fastOutSlowIn)); // 点击时 if (fadeIn) _controller.forward(); else _controller.reverse();

配套注意点:SingleTickerProviderStateMixin提供vsync,dispose()中必须释放_controller,避免内存泄漏。

Week27 AnimatedBuilder:把动画值映射到任意 Widget

week27_animated_builder.dart 演示了 AnimatedBuilder 的经典范式:AnimationController驱动Tween(begin: 0.0, end: 2*pi),在 builder 中把动画值转成旋转角度,child参数缓存不随动画变化的子树以提升性能:

AnimatedBuilder( animation: _animation, builder: (context, child) => Transform.rotate(angle: _animation.value, child: child), child: SizedBox(height: 200, width: 200, child: FlutterLogo()), )

点击后执行_animationController.forward().then((_) => _animationController.reverse()),完成一次"正转-反转"的往返动画。

Week17 Hero:跨页面共享元素过渡

week17_hero.dart 实现"点击图片 → 跳转详情页 → 图片飞入放大"的过渡:两个页面各自用相同tag: 'myHero'包裹同一张图片(assets/myimage.jpg),Navigator.push(MaterialPageRoute(...))时 Flutter 自动执行 Hero 动画。

GestureDetector( onTap: () => Navigator.of(context).push( MaterialPageRoute(builder: (context) => SecondPage())), child: SizedBox( height: 200, width: 200, child: Hero(tag: 'myHero', child: Image.asset('assets/myimage.jpg')), ), )

第二页同样用Hero(tag: 'myHero', child: Image.asset('assets/myimage.jpg'))包裹,点击后Navigator.pop()即可反向过渡。注意 tag 在同一路由栈中必须唯一,否则会抛"multiple heroes have the same tag"错误。

五、异步数据类 Widget:Future 与 Stream 的声明式渲染

Week6 FutureBuilder:一次性异步任务的三种状态

week6_future_builder.dart 注释总结了 FutureBuilder 的完整用法:

  • 接收一个future,通过builder生成AsyncSnapshot<T>;
  • 建议显式指定泛型类型(如AsyncSnapshot<String>),避免后期类型推断失败;
  • 通过snapshot.connectionState判断状态,从而渲染不同 UI;
  • 通过initialData给 snapshot 一个初始值(示例为"initdata")。
FutureBuilder( initialData: "initdata", future: getWords(), builder: (BuildContext context, AsyncSnapshot<String> snapshot) { switch (snapshot.connectionState) { case ConnectionState.waiting: return CircularProgressIndicator(); case ConnectionState.done: if (snapshot.hasError) return Text('出错啦'); return Text('${snapshot.data}'); default: return null; } }, )

getWords()用Future.delayed(Duration(seconds: 1))模拟 1 秒后返回"hello world",演示了 loading → 结果/错误 的完整切换。

Week14 StreamBuilder:持续推送的数据流渲染

week14_stream_builder.dart 对应 Stream 场景:用async*生成器每秒yield一个自增整数(0~9999),StreamBuilder监听并实时刷新文本:

StreamBuilder( stream: count(), builder: (BuildContext context, AsyncSnapshot<int> snapshot) { if (snapshot.hasError) print(snapshot.error); if (!snapshot.hasData) return Center(child: Text("waiting for data", style: TextStyle(fontSize: 24))); return Center(child: Text('${snapshot.data}', style: TextStyle(fontSize: 24))); }, ) Stream<int> count() async* { int i = 0; while (i < 10000) { await Future.delayed(Duration(seconds: 1)); yield i++; } }

与 FutureBuilder 的关键差异:FutureBuilder 只处理"一次结果",StreamBuilder 处理"多次推送";判断是否就绪应优先用snapshot.hasData而非connectionState(Stream 的 done 状态只会在流关闭后出现)。

六、滚动与 Sliver 类 Widget

Week9 PageView:可翻页的页面容器

week9_page_view.dart 用 5 个纯色 Container 演示翻页效果,注释给出两个要点:需要一个PageController来控制,可用initPage指定初始页。示例额外设置viewportFraction: 0.5,让每页只占半屏、露出相邻页边缘:

_controller = PageController(viewportFraction: 0.5); PageView( controller: _controller, children: <Widget>[ /* 5 个纯色 Container */ ], )

注意在initState创建 controller、在dispose中释放,且PageController(viewportFraction: ...)是"卡片式翻页"的常用手法。

Week11 SliverAppBar:可折叠的酷炫顶部栏

week11_sliver_app_bar.dart 的注释指出:SliverAppBar 需要放在CustomScrollView或NestedScrollView中使用,expandedHeight与flexibleSpace通常配合使用,以实现"不同高度显示不同样式":

CustomScrollView( controller: _controller, slivers: <Widget>[ SliverAppBar( floating: true, title: Text('SliverAppBar'), expandedHeight: 200, flexibleSpace: FlexibleSpaceBar( background: Image.network('...', fit: BoxFit.cover), ), ), SliverList(delegate: SliverChildListDelegate([ /* 高度递增的彩色块 */ ])), ], )

滚动时顶部栏会随内容伸缩:floating控制向下滚动时是否立即回弹,pinned/snap则可进一步控制吸顶行为。

Week12 SliverList + SliverGrid:混合 Sliver 布局

week12_sliver_list_grid.dart 在同一个CustomScrollView内先放SliverList(5 个彩色块)再放SliverGrid,并设置physics: BouncingScrollPhysics()获得 iOS 式回弹:

SliverGrid( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 200.0, mainAxisSpacing: 10.0, crossAxisSpacing: 10.0, childAspectRatio: 4.0, ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) => Container(color: Colors.teal[100 * (index % 10)]), childCount: 10, ), )

SliverGridDelegateWithMaxCrossAxisExtent按"每项最大宽度"自动决定列数,配合childAspectRatio控制宽高比,适合瀑布流式网格。

Week28 Dismissible:滑动删除列表项

week28_dismissible.dart 用 20 个ListTile演示滑动删除:background/secondaryBackground定义左滑与右滑露出背景,confirmDismiss回调里根据DismissDirection(endToStart/startToEnd)区分方向并移除数据,key必须是每项唯一值(ValueKey('${list[index]}')):

Dismissible( background: Container(color: Colors.redAccent), secondaryBackground: Container(color: Colors.blue), confirmDismiss: (DismissDirection direction) { if (direction == DismissDirection.endToStart) { } else { print('start to end'); } list.removeAt(index); setState(() {}); }, dragStartBehavior: DragStartBehavior.start, key: ValueKey('${list[index]}'), child: ListTile(title: Text('${list[index]}')), )

七、视觉与绘制类 Widget

Week13 FadeInImage:图片加载淡入过渡

week13_fade_in_image.dart 用本地占位图 + 网络图演示"先占位、加载完成后淡入":

FadeInImage( height: 200, width: 200, fadeInDuration: Duration(seconds: 2), fadeInCurve: Curves.easeIn, placeholder: AssetImage('assets/myimage.jpg'), image: NetworkImage('https://.../cat-2_sq-...jpg'), )

fadeInDuration/fadeInCurve控制淡入时长与曲线,placeholder支持AssetImage/MemoryImage等,是改善弱网图片体验的标准方案。

Week16 ClipRRect:圆角裁剪

week16_clip_r_rect.dart 极简:ClipRRect(borderRadius: BorderRadius.circular(24), clipBehavior: Clip.hardEdge, child: Container(...))。clipBehavior可选Clip.hardEdge(硬裁切、性能好)或Clip.antiAlias(抗锯齿)。

Week18 CustomPaint:用 Canvas 自由绘制

week18_custom_paint.dart 演示了自定义绘制的最小闭环:自定义CustomPainter子类实现paint(Canvas, Size)与shouldRepaint(oldDelegate),再交给CustomPaint(painter: MyPainter())。示例在画布上绘制了直线、矩形(Rect.fromPoints)、圆三种图元:

class MyPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { final myPaint = Paint()..strokeWidth = 10; canvas.drawLine(Offset(-100, -100), Offset(100, 100), myPaint); canvas.drawRect(Rect.fromPoints(Offset(-20, -20), Offset(20, 20)), myPaint); canvas.drawCircle(Offset(100, 0), 12.0, myPaint); } @override bool shouldRepaint(MyPainter oldDelegate) => false; }

shouldRepaint返回false表示不随重建重绘,适合静态图形。

Week23 Transform:旋转、缩放、平移与矩阵变换

week23_transform.dart 用四种写法覆盖 Transform 全家族:

  • Transform.rotate(origin: Offset(100, 100), angle: pi / 4):以指定原点旋转 45°;
  • Transform.scale(origin: Offset(-100, -300), scale: 0.5):缩放并指定变换原点;
  • Transform.translate(offset: Offset(100, -100)):平移;
  • 直接Transform(transform: Matrix4.skew(-0.3, 0))与Matrix4.identity()..setEntry(3, 2, 0.01)..rotateX(0.3):用 Matrix4 做错切与透视旋转。

Week24 BackdropFilter:背景模糊(毛玻璃)

week24_back_drop_filter.dart 在图片上层叠加半透明黑色层,再用ImageFilter.blur(sigmaX: 3, sigmaY: 3)把背景模糊成毛玻璃效果:

BackdropFilter( filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3), child: Container(color: Colors.black.withOpacity(0.5)), )

注意需要import 'dart:ui' show ImageFilter;,且 BackdropFilter 只对其下层内容生效。

八、交互与状态共享类 Widget

Week8 FloatingActionButton:悬浮按钮位置与 BottomAppBar 凹槽

week8_floating_action_button.dart 的注释给出两条核心信息:FloatingActionButton 可通过FloatingActionButtonLocation控制位置,配合 BottomAppBar 使用;BottomAppBar 的shape属性可控制是否出现半圆凹槽。示例循环切换四种位置:

List locations = [ FloatingActionButtonLocation.centerDocked, FloatingActionButtonLocation.endDocked, FloatingActionButtonLocation.centerFloat, FloatingActionButtonLocation.endFloat, ]; Scaffold( floatingActionButton: FloatingActionButton(onPressed: switchLocation, child: Icon(Icons.track_changes)), floatingActionButtonLocation: locations[locationIndex], bottomNavigationBar: BottomAppBar( shape: CircularNotchedRectangle(), // 为 FAB 留出半圆凹槽 color: Colors.blue, child: Container(height: 60), ), )

centerDocked/endDocked与CircularNotchedRectangle()组合,正是 Material 风格"悬浮按钮嵌在底栏凹槽"的经典形态。

Week19 Tooltip:长按提示气泡

week19_tooltip.dart 展示了 Tooltip 的最小用法:message定义提示文案,verticalOffset控制气泡与 child 的垂直偏移(示例用MediaQuery.of(context).size.height / 3把气泡移到屏幕中部):

Tooltip( message: 'tooltip', verticalOffset: MediaQuery.of(context).size.height / 3, child: Scaffold(), )

Week22 AbsorbPointer:拦截所有触摸事件

week22_absorb_pointer.dart 用一个RaisedButton演示事件拦截:absorbing: true时按钮的点击不会生效(点击不会打印button tapped),ignoringSemantics: true则同时忽略无障碍语义信息,是"禁用手势但保留布局"的常用手段。

Week15 InheritedModel:按依赖维度精细化通知

week15_inherited_model.dart 是本系列中状态管理进阶的示范。自定义MyAncestor extends InheritedModel<String>携带colorOne/colorTwo两个颜色,两个子组件分别通过InheritedModel.inheritFrom<MyAncestor>(context, aspect: 'one'/'two')只订阅自己关心的维度;父级重写updateShouldNotifyDependent,仅在对应 aspect 变化时通知对应依赖者,实现细粒度、按需重建:

@override bool updateShouldNotifyDependent( MyAncestor oldWidget, Set<String> dependencies) { if (dependencies.contains('one') && colorOne != oldWidget.colorOne) return true; if (dependencies.contains('two') && colorTwo != oldWidget.colorTwo) return true; return false; }

这是 InheritedWidget 的增强形态:状态更新时只有依赖特定 aspect 的子树会重建,避免整棵树无谓刷新。

Week30 ValueListenableBuilder:监听值变化自动重建局部 UI

week30_value_listenable_builder.dart 用ValueNotifier<int>维护计数器、自定义ColorProvider extends ValueNotifier<ColorModel>维护颜色循环,两个ValueListenableBuilder各自监听、局部刷新(见文首动图):

ValueListenableBuilder( valueListenable: _counter, builder: (context, int value, _) => Text('current counter is : $value'), ), ValueListenableBuilder<ColorModel>( valueListenable: _colorProvider, builder: (context, model, _) => Container(height: 100, width: 100, color: model.color), ), // FloatingActionButton 中:_counter.value++; _colorProvider.changeColor();

核心机制:ValueListenableBuilder只监听ValueListenable(如ValueNotifier、AnimationController),值变化时仅重建 builder 返回的子树,是"无状态小范围响应"的首选,尤其适合计数器、开关、颜色这类轻量状态。

Week31 Draggable + DragTarget:拖拽与投放(更新中的额外彩蛋)

虽然 README 的样例列表止于 Week30,但 lib/widget/widgets.dart 已导出第 31 个文件 week31_draggable.dart,且 main.dart 当前默认页正是Week31(),印证了 README 所述"持续更新中"。该 demo 展示了完整的拖拽闭环:

  • Draggable<Color>(data: Colors.deepPurpleAccent, feedback: ..., child: ..., childWhenDragging: ...):feedback是拖拽跟随的幻影,childWhenDragging是原位的占位态;
  • DragTarget<Color>(onWillAccept, onAccept, onLeave, builder):onWillAccept校验可接受的数据(示例要求颜色必须匹配),onAccept在放下时更新状态。
Draggable<Color>( data: Colors.deepPurpleAccent, feedback: Container(height: 100, width: 100, color: Colors.deepPurpleAccent.withOpacity(0.5)), child: Container(height: 100, width: 100, color: Colors.deepPurpleAccent), childWhenDragging: Container(height: 100, width: 100, color: Colors.grey.withOpacity(0.5)), )

九、知识点全景图与实战建议

把上述 31 周 Widget 按能力域归类,即可得到一张 Flutter 常用 Widget 速查表:

能力域对应 Week关键 Widget
布局与约束1/2/3/10/20/21/25/26/29SafeArea、Expanded、Wrap、Table、FittedBox、LayoutBuilder、Align、Positioned、SizedBox
动画4/5/7/17/27AnimatedContainer、Opacity、FadeTransition、Hero、AnimatedBuilder
异步数据6/14FutureBuilder、StreamBuilder
滚动与 Sliver9/11/12/28PageView、SliverAppBar、SliverList/SliverGrid、Dismissible
视觉与绘制13/16/18/23/24FadeInImage、ClipRRect、CustomPaint、Transform、BackdropFilter
交互与状态8/15/19/22/30/31FloatingActionButton、InheritedModel、Tooltip、AbsorbPointer、ValueListenableBuilder、Draggable

实战建议(均可在本工程中直接验证):

  1. 逐个体验:编辑 lib/main.dart 中的home:指向对应WeekN(),flutter run即可单独查看某个 Widget 的真实效果;
  2. 隐式动画优先:只是"改属性加点动效"时,优先用 AnimatedContainer/AnimatedOpacity 这类隐式动画(week4),需要精细控制(如往返、循环、联动)再上 AnimationController + AnimatedBuilder(week27);
  3. 异步 UI 别手写状态机:一次性请求用 FutureBuilder(week6),持续数据流用 StreamBuilder(week14),配合snapshot.connectionState与hasError/hasData做分支渲染;
  4. 大面积滚动页面用 Sliver:需要折叠顶栏、混合列表/网格时,用 CustomScrollView + SliverAppBar + SliverList/SliverGrid(week11/12)替代嵌套 ListView;
  5. 轻量局部状态用 ValueListenableBuilder:无需全局状态管理框架时,ValueNotifier + ValueListenableBuilder(week30)即可实现精准、低开销的局部刷新。

最后提醒一处细节:仓库根目录 README.md 中样例截图与周次的对应存在少量错位(如 week15 处引用的是 week19 的截图、week18 处引用了 week17 的截图,原文还混入了一个绝对路径/workspace/flutter/Flutter-Notebook/image/week17.png),从源码与图片文件可推断这属于记录时的笔误,不影响每个 demo 源码本身的正确性与可运行性——阅读图片时以文首对应周次标题为准即可。若要在本机体验,可git clone https://gitcode.com/gh_mirrors/fl/Flutter-Notebook后进入mecury_project/example/flutter_widget_of_the_week目录运行flutter pub get与flutter run。

  • 示例工程
  • 移动开发

【免费下载链接】Flutter-Notebook

FlutterDemo合集,今天你fu了吗

项目地址:https://gitcode.com/gh_mirrors/fl/Flutter-Notebook
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询