如何为自定义Tab Bar组件实现滑动切换动画?
自定义Tab Bar滑动背景动画实现方案
完全可以基于现有代码调整实现滑动背景效果,核心是将白色背景改为独立的可动画容器,通过Stack和AnimatedPositioned实现平滑过渡。以下是修改后的完整代码及说明:
修改后的代码
import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { MyApp({Key? key}) : super(key: key); final GlobalKey headerKey = GlobalKey(); @override Widget build(BuildContext context) { return const MaterialApp(home: HomePage()); } } class HomePage extends StatefulWidget { const HomePage({super.key}); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { int active = 1; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('App bar')), body: Column( children: [ const SizedBox(height: 20), const Text('Select one'), const SizedBox(height: 20), const SelectOne(), ], ), ); } } class SelectOne extends StatefulWidget { const SelectOne({super.key}); @override State<SelectOne> createState() => _SelectOneState(); } class _SelectOneState extends State<SelectOne> { final List<String> values = ['one', 'two']; late String activeValue; int activeIndex = 0; // 改用索引跟踪选中项,方便计算位置 @override void initState() { super.initState(); activeValue = values[0]; } @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { final tabWidth = constraints.maxWidth / values.length; return Container( decoration: BoxDecoration( color: Theme.of(context).primaryColor, borderRadius: BorderRadius.circular(100), border: Border.all( color: Theme.of(context).primaryColor, width: 2, ), ), child: Stack( children: [ // 滑动的白色背景容器 AnimatedPositioned( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, left: activeIndex * tabWidth, width: tabWidth, height: double.infinity, child: Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(100), ), ), ), // Tab按钮层(置于背景上层) Row( children: values.asMap().entries.map((entry) { final index = entry.key; final value = entry.value; final isSelected = index == activeIndex; return Expanded( child: GestureDetector( onTap: () { setState(() { activeIndex = index; activeValue = value; }); }, child: Container( alignment: Alignment.center, padding: const EdgeInsets.all(6), child: Text( value, style: Theme.of(context).textTheme.bodyMedium!.copyWith( fontWeight: isSelected ? FontWeight.w500 : null, color: isSelected ? Colors.black : Colors.white, ), ), ), ), ); }).toList(), ), ], ), ); }, ); } }
关键修改点
- 改用索引跟踪选中状态:用
activeIndex替代直接存储activeValue,便于计算背景容器的位置 - 布局重构为Stack结构:将白色背景与Tab按钮分层,背景在下层,按钮在上层,避免背景被按钮遮挡
- 动态计算Tab宽度:通过
LayoutBuilder获取父容器宽度,自动计算每个Tab的宽度,适配任意数量的Tab - 添加滑动动画:使用
AnimatedPositioned实现背景容器的平滑位移,通过duration和curve控制动画速度和轨迹 - 移除原Tab背景色:统一由滑动背景容器处理选中状态的视觉反馈,避免多个背景层冲突
内容的提问来源于stack exchange,提问作者dipansh
相关产品推荐
相关产品推荐

