Flutter仿Boss-5.Lottie实现的Tab切换

效果

Lottie引入

根据自己项目适配的Flutter版本引入对应的Lottie版本。

lottie: ^3.1.0

实现

  • lottie 文件,直接下载Boss APK,解压出来就可以拿到。
  • 代码:

LottieBottomBarItem

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:lottie/lottie.dart';

/// Lottie BottomBarItem
class LottieBottomBarItem extends StatefulWidget {
  // Tab 名字
  final String tabName;

  // Tab 图标
  final String tabIcon;

  // 默认颜色
  final Color tabTextColor;

  // 选中颜色
  final Color tabTextSelectedColor;

  // Tab对应索引
  final int tabIndex;

  // 点击回调
  final Function(int) onTap;

  // 是否选中
  final bool isChecked;

  // 角标
  final int badger;

  const LottieBottomBarItem({
    Key? key,
    required this.tabName,
    required this.tabIcon,
    required this.onTap,
    required this.tabIndex,
    this.tabTextColor = Colors.grey,
    this.tabTextSelectedColor = Colors.black,
    this.isChecked = false,
    this.badger = 0,
  }) : super(key: key);

  @override
  State<LottieBottomBarItem> createState() => _BottomBarItemState();
}

class _BottomBarItemState extends State<LottieBottomBarItem>
    with TickerProviderStateMixin {
  AnimationController? _animationController;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
        vsync: this, duration: const Duration(milliseconds: 500));
    if (widget.isChecked) {
      _animationController?.forward();
    }
  }

  @override
  void didUpdateWidget(covariant LottieBottomBarItem oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (!widget.isChecked && oldWidget != widget) {
      _animationController?.reset();
    }
  }

  @override
  void dispose() {
    _animationController?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return InkWell(
      child: Stack(
        alignment: Alignment.bottomCenter,
        children: [
          Positioned(
            child: Column(
              children: [
                Lottie.asset(
                  widget.tabIcon,
                  repeat: false,
                  controller: _animationController,
                  width: 35.w,
                  height: 30.w,
                ),
                Text(
                  widget.tabName,
                  style: TextStyle(
                    color: widget.isChecked
                        ? widget.tabTextSelectedColor
                        : widget.tabTextColor,
                    fontSize: 12.sp,
                  ),
                )
              ],
            ),
          ),
          Visibility(
            visible: widget.badger > 0,
            child: Positioned(
              right: 30.w,
              top: 10.w,
              child: ClipOval(
                child: Container(
                  alignment: Alignment.center,
                  color: Colors.red,
                  width: 8,
                  height: 8,
                ),
              ),
            ),
          )
        ],
      ),
      onTap: () {
        widget.onTap(widget.tabIndex);
        _animationController?.forward();
      },
    );
  }
}

HomePage

class HomePage extends StatefulWidget with RouteQueryMixin {
  HomePage({Key? key}) : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return HomeState();
  }
}

class HomeState extends PageState<HomePage> with AutomaticKeepAliveClientMixin {
  final logic = Get.put(HomeLogic());

  @override
  void initState() {
    super.initState();
    logic.handleCurrentIndex(params: widget.routeParams);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _homeBodyWidget(context),
    );
  }

  Widget _homeBodyWidget(BuildContext context) {
    return Scaffold(
      body: Stack(
        alignment: Alignment.bottomCenter,
        children: <Widget>[
          // 子布局
          PageView(
            controller: logic.pageController,
            physics: const NeverScrollableScrollPhysics(),
            children: _bodyContentWidget(),
            onPageChanged: (index) {
              logic.state.currentIndex.value = index;
            },
          ),
        ],
      ),

      // 底部栏
      bottomNavigationBar: Obx(
        () => BottomAppBar(
          elevation: 5.0,
          height: 65,
          child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: List.generate(
                logic.state.homeBottomBar.length,
                (index) => Expanded(
                  child: LottieBottomBarItem(
                    tabName: logic.state.homeBottomBar[index].tabName,
                    tabIcon: logic.state.homeBottomBar[index].tabIcon,
                    tabIndex: index,
                    onTap: (index) {
                      logic.state.currentIndex.value = index;
                      logic.pageController.jumpToPage(index);
                    },
                    isChecked: logic.state.currentIndex.value == index,
                  ),
                ),
              )),
        ),
      ),
    );
  }

  @override
  bool get wantKeepAlive => true;

  /// 子布局集合
  List<Widget> _bodyContentWidget() {
    return logic.state.homeBottomBar.map((item) => item.child).toList();
  }
}

HomeState

class HomeTab {
  HomeTab({
    required this.tabName,
    required this.tabIcon,
    required this.child,
    this.badger = 0,
  });

  String tabName;
  String tabIcon;
  Widget child;
  int badger;
}

class HomeState {
  ///当前索引
  RxInt currentIndex = 0.obs;

  ///底部按钮
  final List<HomeTab> homeBottomBar = [
    HomeTab(
        tabName: '职位',
        tabIcon: 'assets/lottie/tab/zhiwei.json',
        child: const WorkPage()),
    HomeTab(
        tabName: '有了',
        tabIcon: 'assets/lottie/tab/youle.json',
        child: const YoulePage()),
    HomeTab(
        tabName: '消息',
        tabIcon: 'assets/lottie/tab/xiaoxi-c.json',
        child: const MessagePage()),
    HomeTab(
        tabName: '我的',
        tabIcon: 'assets/lottie/tab/wode-c.json',
        child: const MinePage()),
  ];
}

HomeLogic

class HomeLogic extends GetxController with HttpApi {
  final HomeState state = HomeState();
  late PageController pageController;


  /// 处理tab默认显示索引
  void handleCurrentIndex({required Map<String, dynamic> params}) {
    int size = state.homeBottomBar.length;
    if (params != null) {
      int tabIndex = params["tabIndex"] ?? 0;
      // 默认加载页面
      if (tabIndex >= size) {
        state.currentIndex.value = size - 1;
      } else {
        state.currentIndex.value = tabIndex;
      }
    }
    // 初始化tab控制器
    pageController = PageController(initialPage: state.currentIndex.value, keepPage: true);
  }
}

详情:github.com/yixiaolunhui/flutter_project

相关推荐
迷雾漫步者21 分钟前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-1 小时前
验证码机制
前端·后端
燃先生._.2 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖3 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235243 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240254 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar4 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人4 小时前
前端知识补充—CSS
前端·css
GISer_Jing5 小时前
2025前端面试热门题目——计算机网络篇
前端·计算机网络·面试