import 'dart:math';
import 'package:flutter/material.dart';
class UiTabBar extends StatefulWidget {
UiTabBar({
Key? key,
this.width,
this.height,
this.padding = const EdgeInsets.symmetric(horizontal: 8),
this.direction = Axis.horizontal,
this.physics = const BouncingScrollPhysics(),
this.style,
this.unselectedStyle,
required this.tabs,
required this.currentIndex,
}) : super(key: key);
final List<UiTab> tabs;
final Axis? direction;
final double? height;
final double? width;
final EdgeInsets? padding;
final TextStyle? style;
final TextStyle? unselectedStyle;
final ScrollPhysics physics;
final int currentIndex;
@override
_UiTabBarState createState() => _UiTabBarState();
}
class _UiTabBarState extends State<UiTabBar> with TickerProviderStateMixin {
ScrollController _scroll = ScrollController();
late AnimationController _animation;
late Point _size;
@override
void initState() {
super.initState();
_animation =
AnimationController(vsync: this, duration: Duration(milliseconds: 200));
_animation.value = 1.0;
_animation.addListener(() {
setState(() {});
});
}
@override
void dispose() {
_scroll.dispose();
_animation.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
_size = Point(size.width / 2, size.height / 2);
return Container(
width: widget.width,
height: widget.height,
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return ListView.builder(
scrollDirection: widget.direction!,
physics: widget.physics,
cacheExtent: 1.0,
itemCount: widget.tabs.length,
controller: _scroll,
itemBuilder: (context, index) {
return _UiTabBarItem(
index: index,
currentIndex: widget.currentIndex,
tab: widget.tabs[index],
padding: widget.padding,
style: widget.style,
unselectedStyle: widget.unselectedStyle,
animation: _animation,
point: (point) =>
_handleAnimateTo(point, _size, _scroll, widget.direction!),
);
},
);
},
),
);
}
}
class _UiTabBarItem extends StatelessWidget {
_UiTabBarItem({
Key? key,
this.point,
this.padding,
this.style,
this.unselectedStyle,
required this.animation,
required this.tab,
required this.index,
required this.currentIndex,
}) : super(key: key);
final PointCallback? point;
final UiTab tab;
final EdgeInsets? padding;
final int index;
final TextStyle? style;
final TextStyle? unselectedStyle;
final int currentIndex;
final AnimationController animation;
@override
Widget build(BuildContext context) {
final double animationValue;
if (index == currentIndex)
animationValue = animation.value;
else
animationValue = 0;
final TextStyle? textStyle = TextStyle.lerp(
unselectedStyle ?? TextStyle(color: Colors.black87),
style ?? TextStyle(color: Colors.blue),
animationValue);
return GestureDetector(
onTap: () {
_handleScroll(context, point, tab);
animation.reset();
animation.forward();
},
child: AbsorbPointer(
child: Center(
child: Padding(
padding: padding!,
child: Text(
tab.text,
style: textStyle,
),
),
),
),
);
}
}
class UiTab {
String text;
VoidCallback? onTap;
UiTab({Key? key, required this.text, this.onTap});
}
typedef PointCallback = Function(Point point);
void _handleScroll(BuildContext context, PointCallback? content, UiTab tab) {
RenderObject renderObject = context.findRenderObject()!;
Rect rect = renderObject.paintBounds;
var vector = renderObject.getTransformTo(null).getTranslation();
if (content != null) {
content(
Point(vector.x + rect.size.width / 2, vector.y + rect.size.height / 2),
);
}
if (tab.onTap != null) {
tab.onTap!();
}
}
void _handleAnimateTo(Point<num> point, Point<num> size,
ScrollController scroll, Axis direction) {
var offset = scroll.offset;
if (direction == Axis.horizontal) {
offset += (point.x - size.x);
} else {
offset += (point.y - size.y);
}
if (offset < 0) {
offset = 0;
} else if (offset > scroll.position.maxScrollExtent) {
offset = scroll.position.maxScrollExtent;
}
scroll.animateTo(offset,
duration: Duration(milliseconds: 250), curve: Curves.ease);
}