上次说到了模版项目 flutter 基于get的模版项目(可通过.bat自定义)tabbar的修改,今天扩展一下 persistent_bottom_nav_bar_v2
的一些特殊处理。
先说一下,这里没有采用router
的处理方式,感觉有点麻烦。
tabbar item
的特殊交互事件,例如发布按钮、功能集合底部弹窗,这些情况下是不需要进行tabbar页面
切换的,或者说按钮不需要去实现对应的tabbar页面
;
插件创建页面时提供了一个特殊的构建方法 PersistentTabConfig.noScreen
,该方法不需要传入tabbar页面
,但必须额外实现一个点击事件,该事件就是我们需要做特殊操作的出发点。
dart
PersistentTabConfig.noScreen({
required this.item,
required void Function(BuildContext) this.onPressed,
this.navigatorConfig = const NavigatorConfig(),
this.onSelectedTabPressWhenNoScreensPushed,
}) : screen = Container();
- 非强制登录模式下,用户点击tabbar切换到
个人中心
时需要跳转至登录页;
对于目前绝大多数项目来说,App整体架构无非分为两种模式:
-
强制登录:这种情况最简单,用户必须有token令牌才能进行内部操作;
-
静默登录/游客模式:允许用户浏览大多数非用户操作相关信息,例如资讯类、商城等,对这类App的个人中心常规操作也只有两种。第一种,默认展示个人中心缺省信息,即可以正常进入操作页;第二种,在点击交互时对事件进行拦截,进行特殊化操作,通常是跳转登录页。

这里开贴主要也是说说第二种处理方式和思路。
通过 persistent_bottom_nav_bar_v2
插件Api的查找发现作者并没有开放 NavBarConfig.onItemSelected
对应的拦截事件,而后通过源码查找,发现在构建PersistentTabView
的过程中有如下实现:
dart
.../persistent_bottom_nav_bar_v2-5.1.0->lib->components->persistent_tab_view.dart
Widget navigationBarWidget() => PersistentTabViewScaffold(
...略...
tabBar: widget.navBarBuilder(
NavBarConfig(
selectedIndex: _controller.index,
items: widget.tabs.map((e) => e.item).toList(),
navBarHeight: widget.navBarHeight,
// 这里实现点击方法的触发
onItemSelected: (index) {
if (widget.tabs[index].onPressed != null) {
widget.tabs[index].onPressed!(context);
} else {
if (widget.navigationShell != null) {
widget.navigationShell!.goBranch(
index,
initialLocation: widget.popAllScreensOnTapOfSelectedTab &&
index == widget.navigationShell!.currentIndex,
);
} else if (widget.popAllScreensOnTapOfSelectedTab &&
_controller.index == index) {
popAllScreens();
} else {
// 这里对tabbar页面进行切换
_controller.jumpToTab(index);
}
}
},
),
),
tabBuilder: (context, index) => _buildScreen(index),
animatedTabBuilder: widget.animatedTabBuilder,
navigationShell: widget.navigationShell,
);
所以往上查找可得必传参数widget.navBarBuilder
是我们实现点击事件拦截的关键代码。
插件自定义style 中发现我们可以对navBarBuilder
传入自定义 DecoratedNavBar widget
,在自定义的 tabbar item
创建时,对 navBarConfig.onItemSelected
(前面说到这是拦截点击事件的关键)的调用,所以自定义 redirect_bottom_bar.dart
实现如下:
dart
import 'package:flutter/material.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
/// 重定向底部导航栏
class RedirectBottomNavBar extends StatelessWidget {
const RedirectBottomNavBar({
required this.navBarConfig,
required this.onRedirected,
super.key,
this.navBarDecoration = const NavBarDecoration(),
});
final NavBarConfig navBarConfig;
final NavBarDecoration navBarDecoration;
/// 重定向底部导航栏操作
///
/// onRedirected返回值为true打开重定向, 否则取消重定向(正常触发业务逻辑)
final Future<bool?> Function(int index) onRedirected;
Widget _buildItem(ItemConfig item, bool isSelected) => Column( ...略... );
@override
Widget build(BuildContext context) => DecoratedNavBar(
decoration: navBarDecoration,
filter: navBarConfig.selectedItem.filter,
opacity: navBarConfig.selectedItem.opacity,
height: navBarConfig.navBarHeight,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: navBarConfig.items.map((item) {
final int index = navBarConfig.items.indexOf(item);
return Expanded(
child: InkWell(
onTap: () async {
// 这里对重定向的处理
if (await onRedirected.call(index) == true) {
return;
}
navBarConfig.onItemSelected(index);
},
child: _buildItem(
item,
navBarConfig.selectedIndex == index,
),
),
);
}).toList(),
),
);
}
效果图前面贴过了,使用方式和插件官方一致:
dart
@override
Widget build(BuildContext context) {
return PersistentTabView(
controller: controller.persistent,
screenTransitionAnimation: const ScreenTransitionAnimation.none(),
tabs: TabbarType.values
.map(
(e) => PersistentTabConfig(
screen: e.body,
item: ItemConfig(
icon: _assets('${e.icon}_slt'),
inactiveIcon: _assets('${e.icon}_nor'),
title: e.title,
activeForegroundColor: Colors.blue,
inactiveForegroundColor: Colors.grey,
),
),
)
.toList(),
navBarBuilder: (navBarConfig) {
return RedirectBottomNavBar(
navBarConfig: navBarConfig,
// tabbar点击拦截处理
onRedirected: (index) async {
if (index == TabbarType.values.length - 1 &&
!UserService.to.isLogined) {
Get.dialog(AlertDialog(
title: const Text('鉴权拦截处理'),
content: const Text('点击按钮模拟登录操作,进入`个人中心`'),
actions: [
TextButton(
onPressed: () {
UserService.to.login();
// 关闭dialog
Get.back();
// 切换到个人中心
controller.persistent.jumpToTab(index);
},
child: const Text('登录'),
)
],
));
return true;
}
return null;
},
navBarDecoration: const NavBarDecoration(
color: Colors.white,
// borderRadius: BorderRadius.circular(2),
),
);
},
);
}
总结一下: 采用插件官方推荐的自定义style方式处理拦截事件,
好处:无侵入,保证插件原汁原味,不影响插件后续的升级;
坏处:自定义style仅仅是作为UI层面上自定义处理的一种方式,这里用来做事件拦截多少有点小题大作了,而且如果我们选取插件原有样式(style1~style16)的任意一种,我们要添加拦截的话都需要copy出来进行修改的,总的来说还是有点不尽人意,但是需求大于天,哈哈哈;
代码同步更新到Git:flutter模板项目命令