You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter开发:实现带特殊中间按钮的BottomNavigationBar并解决滑动空白页问题

解决Flutter底部导航栏滑动切换时出现空白中间页的问题

问题场景

实现包含三个图标的底部导航栏:

  • 左侧图标对应TimelineScreen,右侧图标对应ProfileScreen
  • 中间图标点击后通过CupertinoFullscreenDialogTransition打开图片上传对话框

当前功能基本实现,但滑动切换页面时会出现空白的中间页面。

问题原因

PageView中额外添加了空白的中间页面,滑动时会切换到该空白页,而实际业务中中间图标仅触发弹窗,不需要对应页面。

解决方案

调整PageView的页面数量,移除空白中间页,并修正底部导航索引与PageView页面的映射关系:

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _currentIndex = 0;
  final PageController _pageController = PageController(
    initialPage: 0,
  );

  void _onTabTapped(int index) {
    if (index == 0) {
      setState(() {
        _currentIndex = index;
        _pageController.animateToPage(
          0,
          duration: const Duration(milliseconds: 300),
          curve: Curves.easeInOut,
        );
      });
    } else if (index == 2) {
      setState(() {
        _currentIndex = index;
        _pageController.animateToPage(
          1,
          duration: const Duration(milliseconds: 300),
          curve: Curves.easeInOut,
        );
      });
    } else {
      Navigator.of(context).push(
        PageRouteBuilder(
          opaque: false,
          pageBuilder: (context, _, __) {
            return const FullDialogPage();
          },
        ),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView(
        controller: _pageController,
        onPageChanged: (pageIndex) {
          setState(() {
            // 将PageView的索引映射到底部导航的索引
            _currentIndex = pageIndex == 0 ? 0 : 2;
          });
        },
        children: const [
          TimelineScreen(),
          ProfileScreen(),
        ],
      ),
      bottomNavigationBar: CupertinoTabBar(
        items: const [
          BottomNavigationBarItem(
            icon: Icon(CupertinoIcons.home),
          ),
          BottomNavigationBarItem(
            icon: Icon(CupertinoIcons.search),
          ),
          BottomNavigationBarItem(
            icon: Icon(CupertinoIcons.person),
          ),
        ],
        currentIndex: _currentIndex,
        onTap: _onTabTapped,
      ),
    );
  }
}

class TimelineScreen extends StatelessWidget {
  const TimelineScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Timeline'),
      ),
      backgroundColor: Colors.white,
      body: const Center(
        child: Text('Timeline Screen'),
      ),
    );
  }
}

// 弹窗页面
class FullDialogPage extends StatefulWidget {
  const FullDialogPage({super.key});

  @override
  FullDialogPageState createState() => FullDialogPageState();
}

class FullDialogPageState extends State<FullDialogPage>
    with TickerProviderStateMixin {
  late AnimationController _primary, _secondary;
  late Animation<double> _animationPrimary, _animationSecondary;

  @override
  void initState() {
    _primary = AnimationController(
        vsync: this, duration: const Duration(milliseconds: 600));
    _animationPrimary = Tween<double>(begin: 0, end: 1)
        .animate(CurvedAnimation(parent: _primary, curve: Curves.easeInOut));
    
    _secondary = AnimationController(
        vsync: this, duration: const Duration(milliseconds: 600));
    _animationSecondary = Tween<double>(begin: 0, end: 1)
        .animate(CurvedAnimation(parent: _secondary, curve: Curves.easeInOut));
    
    _primary.forward();
    super.initState();
  }

  @override
  void dispose() {
    _primary.dispose();
    _secondary.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return CupertinoFullscreenDialogTransition(
      primaryRouteAnimation: _animationPrimary,
      secondaryRouteAnimation: _animationSecondary,
      linearTransition: false,
      child: Scaffold(
        appBar: AppBar(
          backgroundColor: Colors.indigo[900],
          title: const Text("Upload an image"),
          leading: IconButton(
            icon: const Icon(Icons.arrow_back),
            onPressed: () {
              _primary.reverse();
              Future.delayed(const Duration(milliseconds: 600), () {
                Navigator.of(context).pop();
              });
            },
          ),
        ),
      ),
    );
  }
}

class ProfileScreen extends StatelessWidget {
  const ProfileScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Profile'),
      ),
      backgroundColor: Colors.white,
      body: const Center(
        child: Text('Profile Screen'),
      ),
    );
  }
}

关键修改点

  • 移除PageView中的空白中间页面,仅保留TimelineScreen和ProfileScreen两个页面
  • 在onPageChanged回调中,将PageView的索引(0和1)映射为底部导航对应的索引(0和2),确保底部导航选中状态正确
  • 调整_onTabTapped方法,点击底部导航的0或2时,对应跳转到PageView的0或1页面

内容的提问来源于stack exchange,提问作者Harlan Erskine

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 06:37:10