接续上一篇 【第二五天 - Flutter 知名外送平台画面练习(上)】~~。
今日的程序码 => GITHUB
我们建立好 FappBar 後。再 HomePage 来使用它。这里将会介绍用到的套件。和整个 TabBarController 和 ScrollController 的互动方式
scroll_to_index: ^2.0.0
rect_getter: ^1.0.0
更多资讯请参考 => 官方文件
AutoScrollController scrollController = AutoScrollController();
AutoScrollTag(
key: ValueKey(index),
controller: controller,
index: index,
child: child
)
controller.scrollToIndex(index, preferPosition: AutoScrollPosition.begin)
globalKey
。RectGetter
来观测 childRect rect = RectGetter.getRectFromKey(globalKey);
来取得 rect资料参考来自 官方文件
// Import package
import 'package:rect_getter/rect_getter.dart';
// Instantiate it
var globalKey = RectGetter.createGlobalKey();
var rectGetter = new RectGetter(
key: globalKey,
child: _child,
);
or
var rectGetter = new RectGetter.defaultKey(
child: _child,
);
// and add it to your layout .
// then you can get rect by
Rect rect = rectGetter.getRect();
or
Rect rect = RectGetter.getRectFromKey(globalKey);
大致讲一下逻辑,和思路。
wholePage
的 RectGetter
,用来关注整个画面的大小NotificationListener
来管理 CustomScrollView
和 TabBar
的互动,换句话说,就是当点击、听直、滑动等...,一系列的 ScrollNotification
事件触发时,我就要去计算我的画面,然後坐我想要做的事情SliverScrollView
也就是包含 AppBar(SliverAppBar)、Body(SliverList)
。FAppBar
,可以参考前一篇 【第二五天 - Flutter 知名外送平台画面练习(上)】。onCollapsed
,来让 AppBar 操作。SliverList
,并且在 SliverChildListDelegate
,里面使用 List.generate
,并在 List.generate
里面回传 item
样式。AutoScrollTag
来达到 scroll_to_index
。NotificationListener
的 onScrollNotification
。getVisibleItemsIndex
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen>
with SingleTickerProviderStateMixin {
/// 使否展开
bool isCollapsed = false;
late AutoScrollController scrollController;
late TabController tabController;
/// 展开高度
final double expandedHeight = 500.0;
/// 页面资料
final PageData data = ExampleData.data;
/// 折叠高度
final double collapsedHeight = kToolbarHeight;
/// Instantiate RectGetter
final wholePage = RectGetter.createGlobalKey();
Map<int, dynamic> itemKeys = {};
/// prevent animate when press on tab bar
/// 避免当我们点击 tab bar 时,动画还在动,还在计算。
bool pauseRectGetterIndex = false;
@override
void initState() {
/// tabController 出使话
tabController = TabController(length: data.categories.length, vsync: this);
scrollController = AutoScrollController();
super.initState();
}
@override
void dispose() {
scrollController.dispose();
tabController.dispose();
super.dispose();
}
/// 取得萤幕可看到的 index 有哪些
List<int> getVisibleItemsIndex() {
// get ListView Rect
Rect? rect = RectGetter.getRectFromKey(wholePage);
List<int> items = [];
if (rect == null) return items;
itemKeys.forEach((index, key) {
Rect? itemRect = RectGetter.getRectFromKey(key);
if (itemRect == null) return;
// y 轴座越大,代表越下面
// 如果 item 上方的座标 比 listView 的下方的座标 的位置的大 代表不在画面中。
// bottom meaning => The offset of the bottom edge of this widget from the y axis.
// top meaning => The offset of the top edge of this widget from the y axis.
if (itemRect.top > rect.bottom) return;
// 如果 item 下方的座标 比 listView 的上方的座标 的位置的小 代表不在画面中。
if (itemRect.bottom < rect.top) return;
items.add(index);
});
return items;
}
/// 用来传递给 appBar 的 function
void onCollapsed(bool value) {
if (this.isCollapsed == value) return;
setState(() => this.isCollapsed = value);
}
/// true表示消费掉当前通知不再向上一级NotificationListener传递通知,false则会再向上一级NotificationListener传递通知;
bool onScrollNotification(ScrollNotification notification) {
// 不想让上一层知道,无需做动作。
if (pauseRectGetterIndex) return true;
// 取得标签的长度
int lastTabIndex = tabController.length - 1;
// 取得现在画面上可以看得到的 Items Index
List<int> visibleItems = getVisibleItemsIndex();
bool reachLastTabIndex = visibleItems.isNotEmpty &&
visibleItems.length <= 2 &&
visibleItems.last == lastTabIndex;
// 如果到达最後一个 index 就跳转到最後一个 index
if (reachLastTabIndex) {
tabController.animateTo(lastTabIndex);
} else {
// 取得画面中的 item 的中间值。例:2,3,4 中间的就是 3
// 求一个数字列表的乘积
int sumIndex = visibleItems.reduce((value, element) => value + element);
// 5 ~/ 2 = 2 => Result is an int 取整数
int middleIndex = sumIndex ~/ visibleItems.length;
if (tabController.index != middleIndex)
tabController.animateTo(middleIndex);
}
return false;
}
/// TabBar 的动画。
void animateAndScrollTo(int index) {
pauseRectGetterIndex = true;
tabController.animateTo(index);
// Scroll 到 index 并使用 begin 的模式,结束後,把 pauseRectGetterIndex 设为 false 暂停执行 ScrollNotification
scrollController
.scrollToIndex(index, preferPosition: AutoScrollPosition.begin)
.then((value) => pauseRectGetterIndex = false);
}
@override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true, //是否延伸body至顶部。
backgroundColor: scheme.background,
body: RectGetter(
key: wholePage,
/// NotificationListener 是一个由下往上传递通知,true 阻止通知、false 传递通知,确保指监听滚动的通知
/// ScrollNotification => https://www.jianshu.com/p/d80545454944
child: NotificationListener<ScrollNotification>(
child: buildSliverScrollView(),
onNotification: onScrollNotification,
),
),
);
}
/// CustomScrollView + SliverList + SliverAppBar
Widget buildSliverScrollView() {
return CustomScrollView(
controller: scrollController,
slivers: [
buildAppBar(),
buildBody(),
],
);
}
/// AppBar
SliverAppBar buildAppBar() {
return FAppBar(
data: data,
context: context,
expandedHeight: expandedHeight,
// 期许展开的高度
collapsedHeight: collapsedHeight,
// 折叠高度
isCollapsed: isCollapsed,
onCollapsed: onCollapsed,
tabController: tabController,
onTap: (index) => animateAndScrollTo(index),
);
}
/// Body
SliverList buildBody() {
return SliverList(
delegate: SliverChildListDelegate(List.generate(
data.categories.length,
(index) {
return buildCategoryItem(index);
},
)),
);
}
/// ListItem
Widget buildCategoryItem(int index) {
// 建立 itemKeys 的 Key
itemKeys[index] = RectGetter.createGlobalKey();
Category category = data.categories[index];
return RectGetter(
// 传GlobalKey,之後可以 RectGetter.getRectFromKey(key) 的方式获得 Rect
key: itemKeys[index],
child: AutoScrollTag(
key: ValueKey(index),
index: index,
controller: scrollController,
child: CategorySection(category: category),
),
);
}
}
<<: Day 26:53. Maximum Subarray (2)
SceneDelegate 从 iOS 13 开始,SceneDelegate 承担了 AppDel...
大家好! 我们进入今天的主题吧! 物件方法 如果要推入项目至阵列,我们会使用原型方法。 但是,为什麽...
相信有许多人很讨厌网页广告,会在网页上安装广告拦截器如AdBlock、AdGuard等。但有很多装置...
介绍: 贝式分类器(Bayesian Classifier)是一种基於机率模型的机器学习模型。它有很...
来到CMoney近两个月 不到两星期就要发表我们游戏专题了 或许会有人不知道做游戏专题有什麽好处 比...