Skip to content

Instantly share code, notes, and snippets.

@theachoem
Last active July 8, 2024 13:09
Show Gist options
  • Save theachoem/d92b0ec4d0b23df7678e13aa0145b623 to your computer and use it in GitHub Desktop.
Save theachoem/d92b0ec4d0b23df7678e13aa0145b623 to your computer and use it in GitHub Desktop.
TabBar with badge that update color on swap - Demo included (Flutter)
// Copyright 2021, Thea Choem, All rights reserved.
import 'package:badges/badges.dart';
import 'package:flutter/material.dart';
class CustomTabBarItem {
final String label;
final String? value;
CustomTabBarItem({
required this.label,
this.value,
});
}
class CustomTabBar extends StatelessWidget implements PreferredSizeWidget {
const CustomTabBar({
Key? key,
this.controller,
required this.items,
this.isScrollable = true,
this.height = kToolbarHeight,
this.onTap,
this.padding,
}) : super(key: key);
/// if `TabController` isn't provided, make sure you have
/// wraped your widget with `DefaultTabController`
final TabController? controller;
final List<CustomTabBarItem> items;
final double height;
final bool isScrollable;
final ValueChanged<int>? onTap;
final EdgeInsetsGeometry? padding;
@override
Widget build(BuildContext context) {
final TabController? tabController = controller ?? DefaultTabController.of(context);
return Theme(
data: Theme.of(context).copyWith(
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
),
child: Container(
width: double.infinity,
padding: padding,
child: TabBar(
isScrollable: isScrollable,
controller: tabController,
onTap: onTap,
tabs: List.generate(
items.length,
(itemIndex) {
var item = items[itemIndex];
return GestureDetector(
onTap: () {
tabController?.animateTo(itemIndex);
if (onTap != null) onTap!(itemIndex);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(item.label),
if (item.value != null && item.value!.isNotEmpty)
_buildValueContainer(
controller: tabController!,
index: itemIndex,
item: item,
),
],
),
);
},
),
),
),
);
}
// this method is used to build the
Widget _buildValueContainer({
required TabController controller,
int? index,
CustomTabBarItem? item,
}) {
return AnimatedBuilder(
animation: controller.animation!,
builder: (context, child) {
var unselectedColor = Theme.of(context).unselectedWidgetColor;
var selectedColor = Theme.of(context).colorScheme.primary;
Color? color = unselectedColor;
final double offset = controller.offset;
final bool isCurrentChild = index == controller.index;
final int currentIndex = controller.index;
bool dragToRight = offset > 0;
if (dragToRight) {
bool inScope = index! <= currentIndex + 1 && index >= currentIndex;
if (inScope) {
color = isCurrentChild
? Color.lerp(selectedColor, unselectedColor, offset)!
: Color.lerp(unselectedColor, selectedColor, offset)!;
}
} else {
bool inScope = index! <= currentIndex && index >= currentIndex - 1;
if (inScope) {
color = isCurrentChild
? Color.lerp(selectedColor, unselectedColor, -offset)!
: Color.lerp(unselectedColor, selectedColor, -offset)!;
}
}
var themeData = Theme.of(context);
var textStyle = themeData.textTheme.overline?.copyWith(color: themeData.backgroundColor);
var containerSize = 16.0;
return Container(
height: containerSize,
margin: const EdgeInsets.only(left: 4.0),
child: Badge(
alignment: Alignment.topRight,
animationType: BadgeAnimationType.scale,
shape: BadgeShape.square,
showBadge: !(item?.value == "0" || item?.value == null),
animationDuration: Duration(milliseconds: 200),
badgeColor: Colors.transparent,
elevation: 0.0,
padding: EdgeInsets.zero,
badgeContent: Container(
height: containerSize,
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 4),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(containerSize), color: color),
child: Text(
item?.value ?? "",
style: textStyle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
),
);
},
);
}
@override
Size get preferredSize => Size.fromHeight(height);
}
@theachoem
Copy link
Author

btn

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment