Flutter自定义选项卡指示器与设计不匹配
下面是我想实现的效果:


截图来自iPhone 15 Pro:


我一直在研究如何创建一个自定义容器,我希望构建一个看起来与第一张截图相似的小部件。我正在使用一个Container,配合AnimatedPositioned和 Row来实现相同的设计。
代码:
final mainTabs = ["Text1", "Text2"];
Container(
height: 40,
color: AppColors.primaryDark,
padding: const EdgeInsets.symmetric(horizontal: 5),
child: LayoutBuilder(
builder: (context, constraints) {
final tabWidth = constraints.maxWidth / mainTabs.length;
return Stack(
children: [
AnimatedPositioned(
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
left: tabWidth * mainTab,
width: tabWidth,
top: 5,
bottom: 0,
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 2),
decoration: const BoxDecoration(
color: AppColors.extraDarkYellow,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(14),
topRight: Radius.circular(14),
),
),
),
),
Row(
children: List.generate(mainTabs.length, (index) {
final isSelected = mainTab == index;
return SizedBox(
width: tabWidth,
child: GestureDetector(
onTap: () {
setState(() {
mainTab = index;
subTab = 0;
});
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
),
child: Center(
child: Text(
mainTabs[index],
style: AppTextStyles.regular(15).copyWith(
height: 1.0,
color: isSelected
? Colors.black
: Colors.white,
),
),
),
),
),
);
}),
),
],
);
},
),
)
如何实现与第一张截图所示相同的设计?
解决方案
我用一个小型的Flutter示例来尝试复现你的UI,并在选中的标签指示器上使用 CustomPainter,而不仅仅使用 BorderRadius。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class AppColors {
static const primaryDark = Color(0xFF1E2A36);
static const extraDarkYellow = Color(0xFFE0B23C);
}
class AppTextStyles {
static TextStyle regular(double size) {
return TextStyle(
fontSize: size,
fontWeight: FontWeight.w500,
);
}
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
backgroundColor: Colors.white,
body: Center(
child: Padding(
padding: EdgeInsets.all(24),
child: MainTabs(),
),
),
),
);
}
}
class MainTabs extends StatefulWidget {
const MainTabs({super.key});
@override
State<MainTabs> createState() => _MainTabsState();
}
class _MainTabsState extends State<MainTabs> {
int mainTab = 0;
final mainTabs = ["Text1", "Text2"];
@override
Widget build(BuildContext context) {
return Container(
height: 44,
color: AppColors.primaryDark,
padding: const EdgeInsets.symmetric(horizontal: 5),
child: LayoutBuilder(
builder: (context, constraints) {
final tabWidth = constraints.maxWidth / mainTabs.length;
return Stack(
children: [
AnimatedPositioned(
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
left: tabWidth * mainTab,
top: 6,
bottom: 0,
width: tabWidth,
child: CustomPaint(
painter: SelectedTabPainter(
color: AppColors.extraDarkYellow,
),
),
),
Row(
children: List.generate(mainTabs.length, (index) {
final isSelected = mainTab == index;
return SizedBox(
width: tabWidth,
height: 44,
child: InkWell(
onTap: () {
setState(() {
mainTab = index;
});
},
child: Center(
child: Text(
mainTabs[index],
style: AppTextStyles.regular(15).copyWith(
height: 1.0,
color: isSelected ? Colors.black : Colors.white,
),
),
),
),
);
}),
),
],
);
},
),
);
}
}
class SelectedTabPainter extends CustomPainter {
final Color color;
SelectedTabPainter({required this.color});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..style = PaintingStyle.fill;
const radius = 14.0;
final path = Path();
path.moveTo(0, size.height);
path.lineTo(0, radius);
path.quadraticBezierTo(0, 0, radius, 0);
path.lineTo(size.width - radius, 0);
path.quadraticBezierTo(size.width, 0, size.width, radius);
path.lineTo(size.width, size.height);
path.close();
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(covariant SelectedTabPainter oldDelegate) {
return oldDelegate.color != color;
}
}
关键部分是 BoxDecoration 与 BorderRadius.only(topLeft, topRight) 只能创建一个简单的圆角矩形。如果你希望选中的标签与更自定义的设计相匹配,使用 CustomPainter 绘制形状会提供更好的控制。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。