资讯详情

Flutter - BottomNavigationBarItem 自定义点击动画及按钮样式

场景:

在实际的项目开发过程中,app底部tabbar每个公司经常有不同的项目。,或自定义动画,或;但是系统自带BottomNavigationBarItem和pub.dev中间的第三方库,不符合实际用途。

本文主要给BottomNavigationBarItem点击添加,并调整了BottomNavigationBarItem的主要。 可以参考,添加自己的专属动画,也可以参考BottomNavigationBarItem的修改为自己的


先贴效果图:

粘贴完整代码的原因是为了方便大家懒得下载和直接复制。我希望它能帮助你,并为你提供各种修改flutter灵感来自原始组件。 点击下载demo完成源码

正文

1.重写系统BottomNavigationBarItem

新建lo_bottom_navigation_bar_item.dart, 这里增加了两个属性animationindex

import 'package:flutter/cupertino.dart';  class BottomNavigationBarItem { 
            BottomNavigationBarItem({ 
             required this.icon,     this.label,     Widget? activeIcon,     this.backgroundColor,     this.tooltip,   }) : activeIcon = activeIcon ?? icon,         assert(icon != null);    Animation<double>? animation;    late int index;    late Widget icon;    final Widget activeIcon;    final String? label;    final Color? backgroundColor;    final String? tooltip; } 

2.重写系统BottomNavigationBar

新建lo_bottom_navigation_bar.dart,, 这里主要有变化InkResponse中的child,改变结构,添加动画;

import 'dart:collection' show Queue; import 'dart:math' as math;

import 'package:flutter/material.dart';
import 'package:vector_math/vector_math_64.dart' show Vector3;
import 'lo_bottom_navigation_bar_item.dart' as MyBarItem;

enum MyBottomNavigationBarType { 
        
  fixed,
  shifting,
}

enum MyBottomNavigationBarLandscapeLayout { 
        
  spread,
  centered,
  linear,
}

class MyBottomNavigationBar extends StatefulWidget { 
        
  MyBottomNavigationBar({ 
        
    Key? key,
    required this.items,
    this.onTap,
    this.currentIndex = 0,
    this.elevation,
    this.type,
    Color? fixedColor,
    this.backgroundColor,
    this.iconSize = 24.0,
    Color? selectedItemColor,
    this.unselectedItemColor,
    this.selectedIconTheme,
    this.unselectedIconTheme,
    this.selectedFontSize = 14.0,
    this.unselectedFontSize = 12.0,
    this.selectedLabelStyle,
    this.unselectedLabelStyle,
    this.showSelectedLabels,
    this.showUnselectedLabels,
    this.mouseCursor,
    this.enableFeedback,
    this.landscapeLayout,
  }) : assert(items != null),
        assert(items.length >= 2),
        assert(
        items.every((MyBarItem.BottomNavigationBarItem item) => item.label != null),
        'Every item must have a non-null label',
        ),
        assert(0 <= currentIndex && currentIndex < items.length),
        assert(elevation == null || elevation >= 0.0),
        assert(iconSize != null && iconSize >= 0.0),
        assert(
        selectedItemColor == null || fixedColor == null,
        'Either selectedItemColor or fixedColor can be specified, but not both',
        ),
        assert(selectedFontSize != null && selectedFontSize >= 0.0),
        assert(unselectedFontSize != null && unselectedFontSize >= 0.0),
        selectedItemColor = selectedItemColor ?? fixedColor,
        super(key: key);

  final List<MyBarItem.BottomNavigationBarItem> items;

  final ValueChanged<int>? onTap;

  final int currentIndex;

  final double? elevation;

  final MyBottomNavigationBarType? type;

  Color? get fixedColor => selectedItemColor;

  final Color? backgroundColor;

  final double iconSize;

  final Color? selectedItemColor;

  final Color? unselectedItemColor;

  final IconThemeData? selectedIconTheme;

  final IconThemeData? unselectedIconTheme;

  final TextStyle? selectedLabelStyle;

  final TextStyle? unselectedLabelStyle;

  final double selectedFontSize;

  final double unselectedFontSize;

  final bool? showUnselectedLabels;

  final bool? showSelectedLabels;

  final MouseCursor? mouseCursor;

  final bool? enableFeedback;

  final MyBottomNavigationBarLandscapeLayout? landscapeLayout;

  @override
  State<MyBottomNavigationBar> createState() => _MyBottomNavigationBarState();
}

// This represents a single tile in the bottom navigation bar. It is intended
// to go into a flex container.
class _BottomNavigationTile extends StatelessWidget { 
        
  const _BottomNavigationTile(
      this.type,
      this.item,
      this.animation,
      this.iconSize, { 
        
        this.onTap,
        this.colorTween,
        this.flex,
        this.selected = false,
        required this.selectedLabelStyle,
        required this.unselectedLabelStyle,
        required this.selectedIconTheme,
        required this.unselectedIconTheme,
        required this.showSelectedLabels,
        required this.showUnselectedLabels,
        this.indexLabel,
        required this.mouseCursor,
        required this.enableFeedback,
        required this.layout,
      }) : assert(type != null),
        assert(item != null),
        assert(animation != null),
        assert(selected != null),
        assert(selectedLabelStyle != null),
        assert(unselectedLabelStyle != null),
        assert(mouseCursor != null);

  final MyBottomNavigationBarType type;
  final MyBarItem.BottomNavigationBarItem item;
  final Animation<double> animation;
  final double iconSize;
  final VoidCallback? onTap;
  final ColorTween? colorTween;
  final double? flex;
  final bool selected;
  final IconThemeData? selectedIconTheme;
  final IconThemeData? unselectedIconTheme;
  final TextStyle selectedLabelStyle;
  final TextStyle unselectedLabelStyle;
  final String? indexLabel;
  final bool showSelectedLabels;
  final bool showUnselectedLabels;
  final MouseCursor mouseCursor;
  final bool enableFeedback;
  final MyBottomNavigationBarLandscapeLayout layout;

  @override
  Widget build(BuildContext context) { 
        
    final int size;

    final double selectedFontSize = selectedLabelStyle.fontSize!;

    final double selectedIconSize = selectedIconTheme?.size ?? iconSize;
    final double unselectedIconSize = unselectedIconTheme?.size ?? iconSize;

    final double selectedIconDiff = math.max(selectedIconSize - unselectedIconSize, 0);

    final double unselectedIconDiff = math.max(unselectedIconSize - selectedIconSize, 0);

    final String? effectiveTooltip = item.tooltip == '' ? null : item.tooltip ?? item.label;

    double bottomPadding;
    double topPadding;
    if (showSelectedLabels && !showUnselectedLabels) { 
        
      bottomPadding = Tween<double>(
        begin: selectedIconDiff / 2.0,
        end: selectedFontSize / 2.0 - unselectedIconDiff / 2.0,
      ).evaluate(animation);
      topPadding = Tween<double>(
        begin: selectedFontSize + selectedIconDiff / 2.0,
        end: selectedFontSize / 2.0 - unselectedIconDiff / 2.0,
      ).evaluate(animation);
    } else if (!showSelectedLabels && !showUnselectedLabels) { 
        
      bottomPadding = Tween<double>(
        begin: selectedIconDiff / 2.0,
        end: unselectedIconDiff / 2.0,
      ).evaluate(animation);
      topPadding = Tween<double>(
        begin: selectedFontSize + selectedIconDiff / 2.0,
        end: selectedFontSize + unselectedIconDiff / 2.0,
      ).evaluate(animation);
    } else { 
        
      bottomPadding = Tween<double>(
        begin: selectedFontSize / 2.0 + selectedIconDiff / 2.0,
        end: selectedFontSize / 2.0 + unselectedIconDiff / 2.0,
      ).evaluate(animation);
      topPadding = Tween<double>(
        begin: selectedFontSize / 2.0 + selectedIconDiff / 2.0,
        end: selectedFontSize / 2.0 + unselectedIconDiff / 2.0,
      ).evaluate(animation);
    }

    switch (type) { 
        
      case MyBottomNavigationBarType.fixed:
        size = 1;
        break;
      case MyBottomNavigationBarType.shifting:
        size = (flex! * 1000.0).round();
        break;
    }

    Widget result = InkResponse(
      onTap: onTap,
      mouseCursor: mouseCursor,
      enableFeedback: enableFeedback,
      child: Padding(
        padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding),
        child: ScaleTransition(
          scale: item.animation!,
          child:  _Tile(
            layout: layout,
            icon: _TileIcon(
              colorTween: colorTween!,
              animation: animation,
              iconSize: iconSize,
              selected: selected,
              item: item,
              selectedIconTheme: selectedIconTheme,
              unselectedIconTheme: unselectedIconTheme,
            ),
            label: _Label(
              colorTween: colorTween!,
              animation: animation,
              item: item,
              selectedLabelStyle: selectedLabelStyle,
              unselectedLabelStyle: unselectedLabelStyle,
              showSelectedLabels: showSelectedLabels,
              showUnselectedLabels: showUnselectedLabels,
            ),
          ),
        ),
      ),
    );

    if (effectiveTooltip != null) { 
        
      result = Tooltip(
        message: effectiveTooltip,
        preferBelow: false,
        verticalOffset: selectedIconSize + selectedFontSize,
        excludeFromSemantics: true,
        child: result,
      );
    }

    result = Semantics(
      selected: selected,
      container: true,
      child: Stack(
        children: <Widget>[
          result,
          Semantics(
            label: indexLabel,
          ),
        ],
      ),
    );

    return Expanded(
      flex: size,
      child: result,
    );
  }
}

class _Tile extends StatelessWidget { 
        
  const  _Tile({ 
        
    Key? key,
    required this.layout,
    required this.icon,
    required this.label
  }) : super(key: key);

  final MyBottomNavigationBarLandscapeLayout layout;
  final Widget icon;
  final Widget label;

  @override
  Widget build(BuildContext context) { 
        
    final MediaQueryData data = MediaQuery.of(context);
    if (data.orientation == Orientation.landscape && layout == MyBottomNavigationBarLandscapeLayout.linear) { 
        
      return Align(
        heightFactor: 1,
        child: Row(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[icon, const SizedBox(width: 8), label],
        ),
      );
    }
    return Column(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      mainAxisSize: MainAxisSize.min,
      children: <Widget>[icon, label],
    );
  }
}

class _TileIcon extends StatelessWidget { 
        
  const _TileIcon({ 
        
    Key? key,
    required this.colorTween,
    required this.animation,
    required this.iconSize,
    required this.selected,
    required this.item,
    required this.selectedIconTheme,
    required this.unselectedIconTheme,
  }) : assert(selected != null),
        assert(item != null),
        super(key: key);

  final ColorTween colorTween;
  final Animation<double> animation;
  final double iconSize;
  final bool selected;
  final MyBarItem.BottomNavigationBarItem item;
  final IconThemeData? selectedIconTheme;
  final IconThemeData? unselectedIconTheme;

  @override
  Widget build(BuildContext context) { 
        
    final Color? iconColor = colorTween.evaluate(animation);
    final IconThemeData defaultIconTheme = IconThemeData(
      color: iconColor,
      size: iconSize,
    );
    final IconThemeData iconThemeData = IconThemeData.lerp(
      defaultIconTheme.merge(unselectedIconTheme),
      defaultIconTheme.merge(selectedIconTheme),
      animation.value,
    );

    return Align(
      alignment: Alignment.topCenter,
      heightFactor: 1.0,
      child: IconTheme(
        data: iconThemeData,
        child: selected ? item.activeIcon : item.icon,
      ),
    );
  }
}

class _Label extends StatelessWidget { 
        
  const _Label({ 
        
    Key? key,
    required this.colorTween,
    required this.animation,
    required this.item,
    required this.selectedLabelStyle,
    required this.unselectedLabelStyle,
    required this.showSelectedLabels,
    required this.showUnselectedLabels,
  }) : assert(colorTween != null),
        assert(animation != null),
        assert(item != null),
        assert(selectedLabelStyle != null),
        assert(unselectedLabelStyle != null),
        assert(showSelectedLabels != null),
        assert(showUnselectedLabels != null),
        super(key: key);

  final ColorTween colorTween;
  final Animation<double> animation;
  final MyBarItem.BottomNavigationBarItem item;
  final TextStyle selectedLabelStyle;
  final TextStyle unselectedLabelStyle;
  final bool showSelectedLabels;

标签: 高压电阻器5w500m

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台