本篇文章主要记录首页框架搭配bloc的使用示例,本篇文章将会使用上一篇文章中的代码,有兴趣的朋友可以去参考一下实现,除了使用pageview还有另外一种实现,但是最后发现那种方式有两个问题,一个是进入首页后会加载所有PageWidget,第二个是每次切换PageWidget时都会走一遍build方法,这显然不符合实际使用场景,所以这里参考部分文章对PageView的使用,也引入了PageView的页面缓存与懒加载完美的符合实际使用,下面看示例。
├── bloc
│ ├── cubit.dart
│ └── state.dart
└── view.dart
代码如下:
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,
);
}
}
代码如下:
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));
}
代码如下:
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; // 是否需要缓存
}
代码如下:
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的依赖比较高,如果项目中使用的其它状态管理框架,照着修改即可,如果有更好的思路欢迎一起讨论与学习。