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

相关推荐
Moment几秒前
面试官:一个接口使用postman这些测试很快,但是页面加载很慢怎么回事 😤😤😤
前端·后端·面试
诗书画唱4 分钟前
【前端面试题】JavaScript 核心知识点解析(第二十二题到第六十一题)
开发语言·前端·javascript
excel11 分钟前
前端必备:从能力检测到 UA-CH,浏览器客户端检测的完整指南
前端
前端小巷子18 分钟前
Vue 3全面提速剖析
前端·vue.js·面试
悟空聊架构24 分钟前
我的网站被攻击了,被干掉了 120G 流量,还在持续攻击中...
java·前端·架构
CodeSheep26 分钟前
国内 IT 公司时薪排行榜。
前端·后端·程序员
尖椒土豆sss29 分钟前
踩坑vue项目中使用 iframe 嵌套子系统无法登录,不报错问题!
前端·vue.js
遗悲风30 分钟前
html二次作业
前端·html
江城开朗的豌豆34 分钟前
React输入框优化:如何精准获取用户输入完成后的最终值?
前端·javascript·全栈
CF14年老兵34 分钟前
从卡顿到飞驰:我是如何用WebAssembly引爆React性能的
前端·react.js·trae