GitHub 源码
- ListItemExposeManager.dart 封装了获取 Coordinate、Dimension,判断是否漏出的逻辑
- ScrollViewPage.dart 演示页面
需求
在类似 ScrollView 的滑动布局中,当某个 子View 露出后进行上报
方法
- 给 滑动布局 (ScrollView、ListView)、子View 设置 Key,采用GlobalKey
- GlobalKey 始于跨组件查询
- 根据 key 算出全局坐标、尺寸
- 剩下的就是小学算术题了。
RenderBox scrollViewRenderBox = scrollViewRenderObject;
/// offset.dy 就是 widget左上角的 y轴坐标
Offset scrollViewPosition = scrollViewRenderBox.localToGlobal(Offset.zero);
/// size就是 widget 的尺寸
Size scrollViewSize = scrollViewRenderBox.size;
注意
- 根据 Key 获取 context (其实是 Element) 获取 RenderObject,有可能 context、RenderObject 为null。
- widget 不可见时,Element是null,做好非空判断。
- widget 可见时,Element也可能是null。我遇到了这个case,换个父布局设置key就行了(我的情况中,两者 布局没有差异)
代码
import 'package:flutter/cupertino.dart';
class ListItemExposeManager {
bool isExpose = false;
GlobalKey listItemViewKey;
GlobalKey scrollViewKey;
Function(bool isExpose) exposeCallback;
ScrollController scrollViewController;
/// Be attention, not every widget will mapping to RenderObject, Element.
/// Sometime the widget is just constraint, Change another widget to set key if RenderObject is null.
ListItemExposeManager(this.listItemViewKey, this.scrollViewKey,
this.scrollViewController, this.exposeCallback) {
scrollViewController.addListener(() => updateStatus());
WidgetsBinding.instance?.addPostFrameCallback((_) => updateStatus());
}
void updateStatus() {
RenderObject? renderObject =
listItemViewKey.currentContext?.findRenderObject();
if (renderObject is! RenderBox) {
return;
}
RenderBox listItemRenderBox = renderObject;
/// 全局坐标系 坐标
Offset listItemPosition = listItemRenderBox.localToGlobal(Offset.zero);
Size listItemSize = listItemRenderBox.size;
if (listItemPosition == null || listItemSize == null) {
return;
}
RenderObject? scrollViewRenderObject =
scrollViewKey.currentContext?.findRenderObject();
if (scrollViewRenderObject is! RenderBox) {
return;
}
RenderBox scrollViewRenderBox = scrollViewRenderObject;
Offset scrollViewPosition = scrollViewRenderBox.localToGlobal(Offset.zero);
Size scrollViewSize = scrollViewRenderBox.size;
if (scrollViewPosition == null || scrollViewSize == null) {
return;
}
double minScrollDy = listItemPosition.dy +
listItemSize.height -
scrollViewPosition.dy -
scrollViewSize.height;
bool afterListView = minScrollDy > 0;
bool beforeListView = listItemPosition.dy + listItemSize.height < scrollViewPosition.dy;
bool currentIsExpose = !beforeListView && !afterListView;
if (currentIsExpose != isExpose) {
exposeCallback(currentIsExpose);
}
isExpose = currentIsExpose;
}
}