自定义Flutter Tabbar


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);
}