likes
comments
collection
share

flutter tabbar点击拦截处理(基于persistent_bottom_nav_bar_v2 )

作者站长头像
站长
· 阅读数 13

前言

先说一下,这里没有采用router的处理方式,感觉有点麻烦。

  • tabbar item 的特殊交互事件,例如发布按钮、功能集合底部弹窗,这些情况下是不需要进行tabbar页面切换的,或者说按钮不需要去实现对应的tabbar页面

插件创建页面时提供了一个特殊的构建方法 PersistentTabConfig.noScreen,该方法不需要传入tabbar页面,但必须额外实现一个点击事件,该事件就是我们需要做特殊操作的触发点。

  PersistentTabConfig.noScreen({
    required this.item,
    required void Function(BuildContext) this.onPressed,
    this.navigatorConfig = const NavigatorConfig(),
    this.onSelectedTabPressWhenNoScreensPushed,
  }) : screen = Container();

  • 非强制登录模式下,用户点击tabbar切换到 个人中心 时需要跳转至登录页;

对于目前绝大多数项目来说,App整体架构无非分为两种模式:

  1. 强制登录:这种情况最简单,用户必须有token令牌才能进行内部操作;

  2. 静默登录/游客模式:允许用户浏览大多数非用户操作相关信息,例如资讯类、商城等,对这类App的个人中心常规操作也只有两种。第一种,默认展示个人中心缺省信息,即可以正常进入操作页;第二种,在点击交互时对事件进行拦截,进行特殊化操作,通常是跳转登录页

flutter tabbar点击拦截处理(基于persistent_bottom_nav_bar_v2 )

这里开贴主要也是说说第二种处理方式和思路。

通过 persistent_bottom_nav_bar_v2 插件Api的查找发现作者并没有开放 NavBarConfig.onItemSelected 对应的拦截事件,而后通过源码查找,发现在构建PersistentTabView的过程中有如下实现:

  .../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 实现如下:

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(),
        ),
      );
}

效果图前面贴过了,使用方式和插件官方一致:


  @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模板项目命令