Reputation: 43
I am build apps in Flutter. Now I want to create custom Tab Bar. I want a tab bar according to the picture.
Upvotes: 3
Views: 3687
Reputation: 8393
Here is a Solution using a CustomPainter
.
Very basic Path painter to draw the background of the tabs.
class TabPainter extends CustomPainter {
final Color color;
TabPainter({this.color});
@override
void paint(Canvas canvas, Size size) {
var paint = Paint()
..color = color
..style = PaintingStyle.fill;
var path = Path();
path.moveTo(0, size.height);
path.lineTo(0, .5 * size.height);
path.quadraticBezierTo(0, 0, .1 * size.width, 0);
path.lineTo(.48 * size.width, 0);
path.quadraticBezierTo(
.512 * size.width, 0, .52 * size.width, .1 * size.height);
path.lineTo(.57 * size.width, .83 * size.height);
path.quadraticBezierTo(
.58 * size.width, .9 * size.height, .59 * size.width, .9 * size.height);
path.lineTo(size.width, .9 * size.height);
path.lineTo(size.width, size.height);
path.lineTo(0, size.height);
path.close();
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
This class uses our TabPainter
to draw the background of the Tab and a TextButton
for the foreground with onPressed
functionality.
MyTab
is defined by:
backgroundColor
, foregroundColor
, activeBackgroundColor
, activeForegroundColor
label
and its fontSize
active
of notreversed
or not (for the right tab)When the users tap on the tab, onTap
is called.
class MyTab extends StatelessWidget {
final Color backgroundColor;
final Color foregroundColor;
final Color activeBackgroundColor;
final Color activeForegroundColor;
final double fontSize;
final bool active;
final bool reversed;
final String label;
final VoidCallback onTap;
Color get bgColor =>
active ? activeBackgroundColor ?? foregroundColor : backgroundColor;
Color get fgColor =>
active ? activeForegroundColor ?? backgroundColor : foregroundColor;
const MyTab({
Key key,
this.active,
this.reversed = false,
this.label,
this.backgroundColor,
this.foregroundColor,
this.activeBackgroundColor,
this.activeForegroundColor,
this.fontSize,
this.onTap,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned.fill(
child: IgnorePointer(
child: reversed
? Transform(
alignment: Alignment.center,
transform: Matrix4.rotationY(math.pi),
child: CustomPaint(painter: TabPainter(color: bgColor)),
)
: CustomPaint(painter: TabPainter(color: bgColor)),
),
),
Align(
alignment: reversed ? Alignment.centerRight : Alignment.centerLeft,
child: FractionallySizedBox(
widthFactor: .5,
heightFactor: 1,
child: TextButton(
onPressed: active ? null : onTap,
child: Text(
label,
style: TextStyle(color: fgColor, fontSize: fontSize),
),
),
),
),
],
);
}
}
This Widget provides a TabBar of two Tabs. Besides the configuration of the tabs, it also takes a TabController to manage the tab transitions.
class MyTabBar extends HookWidget {
final TabController controller;
final List<String> labels;
final Color backgroundColor;
final Color foregroundColor;
final Color activeBackgroundColor;
final Color activeForegroundColor;
final double fontSize;
const MyTabBar({
Key key,
@required this.controller,
@required this.labels,
@required this.backgroundColor,
@required this.foregroundColor,
this.activeBackgroundColor,
this.activeForegroundColor,
this.fontSize,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final _ids = useState([1, 0]);
return AspectRatio(
aspectRatio: 100 / 18,
child: ColoredBox(
color: Theme.of(context).primaryColor,
child: Stack(
fit: StackFit.expand,
children: _ids.value.map((id) {
final active = controller.index == id;
return MyTab(
backgroundColor: backgroundColor,
foregroundColor: foregroundColor,
activeBackgroundColor: activeBackgroundColor,
activeForegroundColor: activeForegroundColor,
fontSize: fontSize,
active: active,
reversed: id == 1,
label: labels[id],
onTap: () {
controller.animateTo(id);
_ids.value = _ids.value.reversed.toList();
});
}).toList(),
),
),
);
}
}
Basic page using our MyTabBar
and a TabView
:
class SettingsPage extends HookWidget {
const SettingsPage({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final _controller = useTabController(initialLength: 2);
return Scaffold(
appBar: AppBar(
title: Text(kPageTitle),
elevation: 0,
),
body: Column(
children: [
MyTabBar(
controller: _controller,
labels: kLabels,
backgroundColor: kTabBgColor,
foregroundColor: kTabFgColor,
),
Expanded(
child: TabBarView(
controller: _controller,
children: [
Center(child: Text(kLabels[0])),
Center(child: Text(kLabels[1])),
],
),
),
],
),
);
}
}
For easy copy-paste, here is the full source code:
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
const kPageTitle = 'Settings';
const kLabels = ["Edit Profile", "Accounts"];
const kTabBgColor = Color(0xFF8F32A9);
const kTabFgColor = Colors.white;
void main() {
runApp(TabBarDemo());
}
class TabBarDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(primaryColor: Color(0xFF5046AF)),
home: SettingsPage(),
);
}
}
class SettingsPage extends HookWidget {
const SettingsPage({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final _controller = useTabController(initialLength: 2);
return Scaffold(
appBar: AppBar(
title: Text(kPageTitle),
elevation: 0,
),
body: Column(
children: [
MyTabBar(
controller: _controller,
labels: kLabels,
backgroundColor: kTabBgColor,
foregroundColor: kTabFgColor,
),
Expanded(
child: TabBarView(
controller: _controller,
children: [
Center(child: Text(kLabels[0])),
Center(child: Text(kLabels[1])),
],
),
),
],
),
);
}
}
class MyTabBar extends HookWidget {
final TabController controller;
final List<String> labels;
final Color backgroundColor;
final Color foregroundColor;
final Color activeBackgroundColor;
final Color activeForegroundColor;
final double fontSize;
const MyTabBar({
Key key,
@required this.controller,
@required this.labels,
@required this.backgroundColor,
@required this.foregroundColor,
this.activeBackgroundColor,
this.activeForegroundColor,
this.fontSize,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final _ids = useState([1, 0]);
return AspectRatio(
aspectRatio: 100 / 18,
child: ColoredBox(
color: Theme.of(context).primaryColor,
child: Stack(
fit: StackFit.expand,
children: _ids.value.map((id) {
final active = controller.index == id;
return MyTab(
backgroundColor: backgroundColor,
foregroundColor: foregroundColor,
activeBackgroundColor: activeBackgroundColor,
activeForegroundColor: activeForegroundColor,
fontSize: fontSize,
active: active,
reversed: id == 1,
label: labels[id],
onTap: () {
controller.animateTo(id);
_ids.value = _ids.value.reversed.toList();
});
}).toList(),
),
),
);
}
}
class MyTab extends StatelessWidget {
final Color backgroundColor;
final Color foregroundColor;
final Color activeBackgroundColor;
final Color activeForegroundColor;
final double fontSize;
final bool active;
final bool reversed;
final String label;
final VoidCallback onTap;
Color get bgColor =>
active ? activeBackgroundColor ?? foregroundColor : backgroundColor;
Color get fgColor =>
active ? activeForegroundColor ?? backgroundColor : foregroundColor;
const MyTab({
Key key,
this.active,
this.reversed = false,
this.label,
this.backgroundColor,
this.foregroundColor,
this.activeBackgroundColor,
this.activeForegroundColor,
this.fontSize,
this.onTap,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned.fill(
child: IgnorePointer(
child: reversed
? Transform(
alignment: Alignment.center,
transform: Matrix4.rotationY(math.pi),
child: CustomPaint(painter: TabPainter(color: bgColor)),
)
: CustomPaint(painter: TabPainter(color: bgColor)),
),
),
Align(
alignment: reversed ? Alignment.centerRight : Alignment.centerLeft,
child: FractionallySizedBox(
widthFactor: .5,
heightFactor: 1,
child: TextButton(
onPressed: active ? null : onTap,
child: Text(
label,
style: TextStyle(color: fgColor, fontSize: fontSize),
),
),
),
),
],
);
}
}
class TabPainter extends CustomPainter {
final Color color;
TabPainter({this.color});
@override
void paint(Canvas canvas, Size size) {
var paint = Paint()
..color = color
..style = PaintingStyle.fill;
var path = Path();
path.moveTo(0, size.height);
path.lineTo(0, .5 * size.height);
path.quadraticBezierTo(0, 0, .1 * size.width, 0);
path.lineTo(.48 * size.width, 0);
path.quadraticBezierTo(
.512 * size.width, 0, .52 * size.width, .1 * size.height);
path.lineTo(.57 * size.width, .83 * size.height);
path.quadraticBezierTo(
.58 * size.width, .9 * size.height, .59 * size.width, .9 * size.height);
path.lineTo(size.width, .9 * size.height);
path.lineTo(size.width, size.height);
path.lineTo(0, size.height);
path.close();
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
Upvotes: 8