Flutter中如何实现带自定义动画效果的TabView组件设计?
效果参考:
这个效果不需要引入第三方依赖,用Flutter原生能力就能实现,核心是靠TabController自带的连续滑动偏移值驱动所有动画,手势和动画完全同步,不会有脱节感。
基础联动框架
首先在State中初始化TabController,传入对应Tab数量,页面销毁时调用dispose释放资源。整体用Column布局,上半部分放自定义Tab导航栏,下半部分放TabBarView,给TabBarView设置physics: const BouncingScrollPhysics(),可以匹配动图里的弹性滑动手感。
核心点是监听TabController.animation的变化:这个值是0到「Tab总长度-1」的连续浮点数,不管是点击Tab切换,还是手指左右滑动页面,这个值都会实时跟随滑动进度更新,所有切换动画都基于这个值计算,不需要额外单独写动画控制器。弹性拉扯指示器实现
不要用TabBar自带的indicator属性,直接在Tab栏下方叠一层自定义组件做指示器:- 拿到当前动画值,计算出滑动起始索引、目标索引、滑动进度:比如动画值是1.3,就代表从索引1往索引2滑动,当前进度0.3
- 指示器宽度分两段变化:滑动进度0-0.5时,宽度从单个Tab宽度逐步拉宽到1.5倍左右,位置停在两个Tab中间;进度0.5-1时,宽度从峰值收缩回单个Tab宽度,位置平滑移动到目标Tab正下方,变化曲线用
Curves.easeOutBack就能做出动图里的Q弹拉扯效果 - 指示器颜色可以跟着滑动进度,在前后两个Tab对应的主题色之间做线性渐变,过渡更自然。
Tab项切换动效
不要用TabBar默认的Tab文字组件,自己根据动画值计算每个Tab的状态:
对索引为i的Tab,先计算它和当前动画值的差的绝对值offset = (i - currentAnimValue).abs()- offset为0(完全选中态):图标、文字缩放1.1倍,透明度1,用高亮主题色
- offset为1(相邻未选中态):图标、文字缩放0.9倍,透明度0.6,用浅灰色
滑动过程中缩放、透明度、颜色都做连续插值,不要硬切状态,过渡会非常丝滑。
页面切换动效
默认TabBarView是平推切换效果,要实现动图里的缩放淡入效果,不要直接把页面内容塞到TabBarView的children里:
用AnimatedBuilder包裹页面内容,根据当前动画偏移给每个页面做变换:滑出的页面随滑动进度缩小到0.9倍、透明度降到0、加15像素左右的向下偏移;滑入的页面随进度从0.9倍放大到1倍、透明度从0升到1、向下偏移从20像素回到0,和动图效果完全一致。如果要更强的层次感,可以给页面内部的卡片、图片加少量反向视差偏移,滑动时内容往滑动反方向挪3-5像素即可。
核心代码结构参考:
late final TabController _tabController = TabController(length: 3, vsync: this); @override void dispose() { _tabController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _tabController.animation!, builder: (context, child) { final double tabProgress = _tabController.animation!.value; return Column( children: [ // 传入tabProgress,自定义绘制Tab栏和弹性指示器 _buildCustomTabBar(tabProgress), // 传入tabProgress,实现页面缩放淡入切换效果 Expanded(child: _buildCustomTabView(tabProgress)), ], ); }, ); }
如果不想用CustomPaint绘制指示器,直接用Container+Positioned实现也可以,根据tabProgress动态计算容器的left位置和width属性就行,性能没有差异。
内容的提问来源于stack exchange,提问作者Rajaghaneshan

