当前位置:首页 > 移动端开发 > 正文内容

【Flutter】怎么美丽地完成一个悬浮NavigationBar

邻居的猫1个月前 (12-09)移动端开发580

【Flutter】怎么美丽地完结一个悬浮NavigationBar

最近写代码的时分遇到了一个如下的需求:

image

全体来说,底部的条是一个起浮的悬浮窗,有如下的三个按钮:

  • 点击左面的要进入“主页”
  • 点击中心的按钮要进行页面跳转,能够进入“创造页”
  • 点击右边的按钮切换到“个人中心”页

运用Overlay来完结悬浮作用

首先是这个窗口该怎么创立的问题,明显需求Overlay悬浮在整个窗口顶部。

可是不能直接写在initState内,这样会触发“Build时重绘”的过错。所以咱们能够运用WidgetsBinding,来监听Callback,这样能够确保在主页Build完结时能够马上制作这个悬浮的窗口。

/rootpage
@override
  void didChangeDependencies() {
    print('root didChangeDependencies');
    super.didChangeDependencies();
    var widgetsBinding = WidgetsBinding.instance;
    widgetsBinding.addPostFrameCallback((callback) {
      print('addPostFrameCallback');
      PNavigationBar.show(context, _tabController);
    });
  }

我将这个放入到了didChangeDependencies内,首要是想经过混入TickerProviderStateMixin能够在路由回来时从头触发didChangeDependencies,不过抱负很饱满。终究在试验的过程中反倒没有触发,没有找到原因,期望有感爱好的大佬能够点拨一下。

理论参阅:Flutter 小而美系列|TickerProviderStateMixin 对生命周期的影响 - 掘金 (juejin.cn)


运用TabBar+TabView来完结NavigationBar的作用

首先说最简略的TabView部分

@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: TabBarView(
        controller: _tabController,
        children: [
          HomePage(),
          UserPage(),
        ],
      ),
    );
  }

这儿需求一个TabController,信任比较了解的朋友们也知道,需求混入TickerProviderStateMixin,才能够声明

image

画框的部分是首要部分。


自界说完结一个PNavigationBar

image

(详细的代码在本文终究)

整个PNavigationBar的完结十分简略,界说了一个show,一个remove,一个refresh办法,这样能够确保任何组件任何页面都能够随时操控PNavigationBar的出现和消失。

图标的切换

由于NavigationBar是存在切换图标的功用的,而咱们经过Image.asset获取的图标却没办法更新,所以咱们需求手动调用overlayEntry.markNeedsBuild办法,来对整个底部组件进行重绘

image

中心按钮的完结

信任咱们也会有开始跟我相同的疑问,由于TabBar与TabView,还有TabController的数有必要共同,而咱们中心有一个自界说的加号按钮,我在这儿的完结十分简略粗犷,当然如果有更好的办法欢迎大佬指导。

image

我这儿仅仅经过简略的运算,来将两个组件别离操控在左面和右边,之后加号按钮在中心。

当然整个TabBar的烘托逻辑其实是有问题的,想要更深化地改TabBar的摆放办法,有必要需求自己手写一个TabBar。默许的摆放办法便是放到Expanded内的,详细参阅了以下这篇博客:

Flutter系列之设置TabBar的tab紧凑摆放_flutter tabbar距离-CSDN博客


关于页面路由的问题

最难的部分便是这儿,首要在于怎么操控路由到其他界面就能够消失,再pop回来就能够显现。

咱们期望这些功用都能够在RootPage这一层完结,而不在各种子页面的push和pop中增加代码担负。

详细完结起来开始我的测验是didChangeDependencies,可是终究试验下来并没有成果,我自己也并不知道原因。(小白是这样的)

而我终究决议选用原始的NavigationObserver办法,这儿感谢这个组件替我完结了这个功用:

lifecycle_lite | Flutter Package (pub.dev)

所以能够经过简略的onShow和onHide就能够完结啦!


代码出现

当然还有许多细节都没有说到,写这个功用时遇到的问题也有不少,自己技能有限,才能有限。等代码再优化的时分能够作为库开源给咱们。现在就暂时以这种博客的方式共享组件和代码。

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:picturebook/pages/test/test_page.dart';

import '../color_utils.dart';

class PNavigationBar {
  static OverlayEntry? overlayEntry;

  static show(BuildContext context, TabController tabController) {
    var overlayState = Overlay.of(context);
    overlayEntry = OverlayEntry(
      maintainState: true,
      builder: (BuildContext context) {
        final size = MediaQuery.of(context).size;
        final height = size.height;
        final width = size.width;
        final boxWidth = width * 0.46;
        final boxHeight = 60.h;
        final iconHeight = 45.h;
        return Positioned(
          bottom: height * 0.06,
          left: (width - boxWidth) / 2,
          right: (width - boxWidth) / 2,
          child: Stack(
            children: [
              Container(
                  decoration: BoxDecoration(
                    color: ColorUtils.orange,
                    borderRadius: BorderRadius.circular(boxHeight / 2),
                  ),
                  width: boxWidth,
                  height: boxHeight,
                  child: TabBar(
                    controller: tabController,
                    indicatorColor: Colors.transparent,
                    padding: EdgeInsets.zero,
                    onTap: (index) {
                      tabController.animateTo(index);
                      overlayEntry?.markNeedsBuild();
                    },
                    tabs: [
                      Padding(
                        padding: EdgeInsets.only(right: iconHeight / 3),
                        child: Container(
                          width: iconHeight,
                          height: iconHeight,
                          decoration: BoxDecoration(
                            color: Colors.white30,
                            borderRadius: BorderRadius.circular(iconHeight / 2),
                          ),
                          child: Center(
                              child: Image.asset(
                            tabController.index == 0
                                ? 'assets/home_1.png'
                                : 'assets/home_0.png',
                            width: iconHeight * 0.5,
                          )),
                        ),
                      ),
                      Padding(
                        padding: EdgeInsets.only(left: iconHeight / 3),
                        child: Container(
                          width: iconHeight,
                          height: iconHeight,
                          decoration: BoxDecoration(
                            color: Colors.white30,
                            borderRadius: BorderRadius.circular(iconHeight / 2),
                          ),
                          child: Center(
                              child: Image.asset(
                            tabController.index == 1
                                ? 'assets/user_1.png'
                                : 'assets/user_0.png',
                            width: iconHeight * 0.5,
                          )),
                        ),
                      ),
                    ],
                  )),
              Align(
                alignment: Alignment.center,
                child: Padding(
                  padding: EdgeInsets.only(top: (boxHeight - iconHeight) / 2),
                  child: InkWell(
                    onTap: () {
                      print('push');
                      Navigator.push(
                        context,
                        MaterialPageRoute(builder: (context) => TestPage()),
                      );
                    },
                    child: Container(
                      width: iconHeight,
                      height: iconHeight,
                      decoration: BoxDecoration(
                        color: Colors.white,
                        borderRadius: BorderRadius.circular(iconHeight / 2),
                      ),
                      child: Center(
                          child: Image.asset(
                        'assets/add.png',
                        width: iconHeight * 0.5,
                      )),
                    ),
                  ),
                ),
              ),
            ],
          ),
        );
      },
    );
    overlayState.insert(overlayEntry!);
  }

  static remove() {
    if (overlayEntry != null) {
      overlayEntry!.remove();
    }
  }

  static refresh(){
    overlayEntry?.markNeedsBuild();
  }

}

下面是运用的实例,十分美丽简练:

import 'package:flutter/material.dart';
import 'package:lifecycle_lite/lifecycle_mixin.dart';
import 'package:picturebook/pages/home_page.dart';
import 'package:picturebook/pages/user_page.dart';
import 'package:picturebook/utils/navigation/navigation_util.dart';

class RootPage extends StatefulWidget {
  const RootPage({super.key});

  @override
  State<RootPage> createState() => _RootPageState();
}

class _RootPageState extends State<RootPage>
    with TickerProviderStateMixin, LifecycleStatefulMixin {
  late TabController _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 2, vsync: this)..addListener(() {
      PNavigationBar.refresh();
    });
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    var widgetsBinding = WidgetsBinding.instance;
    widgetsBinding.addPostFrameCallback((callback) {
      PNavigationBar.show(context, _tabController);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: TabBarView(
        controller: _tabController,
        children: [
          HomePage(),
          UserPage(),
        ],
      ),
    );
  }

  @override
  void whenHide() {
    PNavigationBar.remove();
  }

  @override
  void whenShow() {
    PNavigationBar.show(context, _tabController);
  }
}

扫描二维码推送至手机访问。

版权声明:本文由51Blog发布,如需转载请注明出处。

本文链接:https://www.51blog.vip/?id=497

分享给朋友:

“【Flutter】怎么美丽地完成一个悬浮NavigationBar” 的相关文章

鸿蒙NEXT开发中怎么保证运用 PersistentStorage 存储的数据安全?

鸿蒙NEXT开发中怎么保证运用 PersistentStorage 存储的数据安全?

大家好,我是 V 哥,在鸿蒙 NEXT 开发中,咱们会运用 PersistentStorage 来存储一些数据,那问题来了,怎么保证运用 PersistentStorage 存储的数据安全呢,今日的内容来聊一聊这个论题。 首要,保证PersistentStorage存储的数据安全,咱们可以考虑以下...

iOS开发-多线程编程

iOS开发-多线程编程

OC中常用的多线程编程技术: 1. NSThread NSThread是Objective-C中最根本的线程笼统,它答应程序员直接办理线程的生命周期。 NSThread *myThread = [[NSThread alloc] initWithTarget:self selector:@selec...

Flutter 编写收音机开源

Flutter 编写收音机开源

之前写的一个 Flutter 收音机,支撑桌面端和手机端,在https://www.cnblogs.com/imlgc/p/17536481.html ,写完之后就不怎样管了。后边陆陆续续有人邮件索要验证码,不是常常运用的邮箱,一切也不常常翻开,也导致许多人没有收到回复。 所以,爽性将这个东西开源了...

鸿蒙os系统下载,体验华为全新操作系统

鸿蒙OS系统(HarmonyOS)是华为推出的一款面向万物互联的全场n 访问华为官网的鸿蒙OS NEXT页面:。2. 华为开发者联盟官网: 访问华为开发者联盟官网的下载中心,获取鸿蒙OS开发者相关资源和工具:。3. 其他资源: 访问CSDN博客获取鸿蒙OS的安装使用及下载地址:。...

鸿蒙元仙,探寻鸿蒙元仙的奇幻世界

鸿蒙元仙,探寻鸿蒙元仙的奇幻世界

《鸿蒙元仙》是一部由莫问前缘创作的仙侠类型网络小说。小说情节跌宕起伏、扣人心弦,主要讲述了主角在修仙道路上的经历和成长。以下是关于《鸿蒙元仙》的一些详细信息:1. 作者:莫问前缘。2. 简介:小说通过“琉璃伞罗撑万界,三口古剑斩诸天。修来永寿不灭体,证就鸿蒙第一仙”的描述,展现了主角在修仙世界中的传...

鸿蒙寄生诀,洪荒世界的神秘力量

鸿蒙寄生诀,洪荒世界的神秘力量

《鸿蒙寄生诀》是梦入神机创作的仙侠小说《阳神》中的一种神秘且强大的功法。以下是对该功法的详细介绍:1. 功法背景: 《鸿蒙寄生诀》的来历可以追溯到鸿蒙初开之时,是天地间至高无上的生命法则之一。通过修炼它可以掌握生命的奥秘和力量,达到寄生的效果。2. 功法特点: 《鸿蒙寄生诀》是一种极其霸...