flutter 使用Bloc+PageView+BottomNavigationBar实现传统首页布局

目录

  • 前言
  • 一、目录结构
  • 二、具体步骤
    • 1. 使用state定义BottomNavigationBar切换状态
    • 2.使用Cubit定义简单的事件处理
    • 3.封装PageView带缓存子类,listview也可以使用(需要慎重)
    • 4.实际的布局, 直接将布局放到main中即可看到效果
  • 总结


前言

本篇文章主要记录首页框架搭配bloc的使用示例,本篇文章将会使用上一篇文章中的代码,有兴趣的朋友可以去参考一下实现,除了使用pageview还有另外一种实现,但是最后发现那种方式有两个问题,一个是进入首页后会加载所有PageWidget,第二个是每次切换PageWidget时都会走一遍build方法,这显然不符合实际使用场景,所以这里参考部分文章对PageView的使用,也引入了PageView的页面缓存与懒加载完美的符合实际使用,下面看示例。


一、目录结构

├── bloc
│   ├── cubit.dart
│   └── state.dart
└── view.dart

二、具体步骤

1. 使用state定义BottomNavigationBar切换状态

代码如下:

part of 'cubit.dart';

enum HomeTab {
  home('首页'),
  classification('分类'),
  dynamics('动态'),
  me('我的');

  final String title;
  const HomeTab(this.title);
}
final class HomeState extends Equatable {
  final HomeTab tab;

  const HomeState({this.tab = HomeTab.home});

  
  List<Object?> get props => [tab];

  HomeState copyWith({
    HomeTab? tab,
  }) {
    return HomeState(
      tab: tab ?? this.tab,
    );
  }
}

2.使用Cubit定义简单的事件处理

代码如下:

import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

part 'state.dart';

class HomeCubit extends Cubit<HomeState> {
  HomeCubit() : super(const HomeState());

  void setTab(HomeTab tab) => emit(state.copyWith(tab: tab));
}

3.封装PageView带缓存子类,listview也可以使用(需要慎重)

代码如下:

part of lib_rock_utils;

/// {@template keep_alive_wrapper}
/// 帮助 PageView或ListView等滑动控件实现缓存
/// 使用方式,只需要将需要缓存的子项用该类包裹即可
///
/// {@endtemplate}
final class KeepAliveWrapper extends StatefulWidget {
  /// {@macro keep_alive_wrapper}
  const KeepAliveWrapper({
    super.key,
    this.keepAlive = true,
    required this.child,
  });

  /// 是否缓存, 默认缓存
  final bool keepAlive;
  /// 布局
  final Widget child;

  
  State<KeepAliveWrapper> createState() => _KeepAliveWrapperState();
}

class _KeepAliveWrapperState extends State<KeepAliveWrapper>
    with AutomaticKeepAliveClientMixin {
  
  Widget build(BuildContext context) {
    super.build(context); // 必须调用
    return widget.child;
  }

  
  void didUpdateWidget(covariant KeepAliveWrapper oldWidget) {
    if (oldWidget.keepAlive != widget.keepAlive) {
      // keepAlive 状态需要更新,实现在 AutomaticKeepAliveClientMixin 中
      updateKeepAlive();
    }
    super.didUpdateWidget(oldWidget);
  }

  
  bool get wantKeepAlive => widget.keepAlive; // 是否需要缓存
}

4.实际的布局, 直接将布局放到main中即可看到效果

代码如下:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:lib_rock_utils/lib_rock_utils.dart';

import '../test_http2/view.dart';
import 'bloc/cubit.dart';

class HomePage extends StatelessWidget {
  /// 首页
  const HomePage({super.key});

  
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (BuildContext context) => HomeCubit(),
      child: const _HomePageView(),
    );
  }
}

class _HomePageView extends StatefulWidget {
  const _HomePageView();

  
  State<_HomePageView> createState() => _HomePageViewState();
}

class _HomePageViewState extends State<_HomePageView> {
  final PageController _pageController = PageController();
  // 记录两次点击返回键的时间,如果在1秒内点击两次就退出
  DateTime? lastPopTime;
  // 当前需要切换的页面,需要与BottomNavigationBar个数对应
  final List<Widget> pageList = [
    KeepAliveWrapper(child: HomeTwoPage()),
    KeepAliveWrapper(
        child: TestHomeItemPage(txt: HomeTab.classification.title)),
    KeepAliveWrapper(child: TestHomeItemPage(txt: HomeTab.dynamics.title)),
    KeepAliveWrapper(child: TestHomeItemPage(txt: HomeTab.me.title)),
  ];

  
  Widget build(BuildContext context) {
    final selectedTab = context.select((HomeCubit cubit) => cubit.state.tab);

    return Scaffold(
      body: WillPopScope(
        onWillPop: () async {
          // 如果当前tab不在首页, 则按返回触发切换到首页
          if (selectedTab != HomeTab.home) {
            _pageController.jumpToPage(0);
            context.read<HomeCubit>().setTab(HomeTab.home);
          } else {
            // 双击退出判断
            lastPopTime ??= DateTime.now();
            if (DateTime.now().difference(lastPopTime!) <= const Duration(seconds: 1)) {
              lastPopTime = DateTime.now();
              LogUtil.error('再按一次退出APP');
            } else {
              lastPopTime = DateTime.now();
              // 退出app
              await SystemChannels.platform.invokeMethod('SystemNavigator.pop');
            }
          }
          return false;
        },
        child: PageView(
          // 禁止左右滑动
          physics: const NeverScrollableScrollPhysics(),
          controller: _pageController,
          children: pageList,
        ),
      ),
      // 底部导航
      bottomNavigationBar: _buildBottomNavigationBar(context, selectedTab),
    );
  }

  // 底部导航栏布局
  BottomNavigationBar _buildBottomNavigationBar(
      BuildContext context, HomeTab selectedTab) {
    return BottomNavigationBar(
      currentIndex: selectedTab.index,
      type: BottomNavigationBarType.fixed,
      fixedColor: Theme.of(context).colorScheme.inversePrimary,
      onTap: (index) {
        // 切换页面
        _pageController.jumpToPage(index);
        final tab = HomeTab.values[index];
        // 发送切换指令
        context.read<HomeCubit>().setTab(tab);
      },
      items: [
        BottomNavigationBarItem(
          label: HomeTab.home.title,
          icon: const Icon(Icons.home),
        ),
        BottomNavigationBarItem(
          label: HomeTab.classification.title,
          icon: const Icon(Icons.my_library_books),
        ),
        BottomNavigationBarItem(
          label: HomeTab.dynamics.title,
          icon: const Icon(Icons.cloud),
        ),
        BottomNavigationBarItem(
          label: HomeTab.me.title,
          icon: const Icon(Icons.person),
        ),
      ],
    );
  }
}

// 其他布局,用于演示使用
class TestHomeItemPage extends StatelessWidget {
  final String txt;

  const TestHomeItemPage({super.key, required this.txt});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(txt),
      ),
      body: Center(
        child: Text(txt),
      ),
    );
  }
}

总结

在开始写这个页面之前,也踩了一些坑,比如大多数文章都是使用简单的方式实现了效果,可以用吗?可以用,但是我认为该控件的使用场景并不在首页,也许做轮播合适,毕竟以目前的app来看,首页是比较复杂的功能,也不太可能出现一次加载多个布局,每次显示都重绘一次,这样对于复杂的功能来讲性能堪忧,所以当时写完测试之后发现了不对,然后想到也许flutter有类似于ViewPage这样的组件,按照这个思路确实很容易就实现这个页面的简单效果,当然由于项目使用了bloc,所以对bloc的依赖比较高,如果项目中使用的其它状态管理框架,照着修改即可,如果有更好的思路欢迎一起讨论与学习。

你可能感兴趣的:(flutter,flutter,bloc,BottomBar,PageView)