埋点方案-页面和视图的进入和离开
一、整个页面进入、离开的监控方案
视图创建基类 LifeCyclePage,使用 route 方法,并提供以下方法
1 2 3 4 5
| void viewDidAppear(AppearBecause appearBecause)
void viewDidDisappear(DisAppearBecause disAppearBecause);
|
二、页面内部分视图进入、离开的监控方案
场景:运行需要知道页面下的tab、页面下的tab的《子tab/子视图》的浏览时间。
方案1:点击–view_show/hide–不推荐
使用点击 view_show/hide 方案,实现步骤如下:
点击tab1:记tab1 view_show 事件,并记下当前显示的tab,备后续切换的时候为本tab添加 view_hide 事件。
点击tab2:记tab1 view_hide 事件,tab2 view_show 事件,并记下当前显示的tab,备后续切换的时候为本tab添加 view_hide 事件。
缺点:tab点击时候,记下当前显示的tab,备后续切换的时候为本tab添加 view_hide 事件。
方案2:视图是否显示监控–visibility–推荐方案
视图创建基类 LifeCycleView,使用 visibility 方案,并提供以下方法
1 2 3 4 5
| void viewDidAppear(AppearBecause appearBecause)
void viewDidDisappear(DisAppearBecause disAppearBecause);
|
2.1、原理:创建 VisibilityDetector 视图类,并设置 onVisibilityChanged 回调方法;
1 2 3 4 5 6 7
| const VisibilityDetector({ required Key key, required Widget child, required this.onVisibilityChanged, }) : assert(key != null), assert(child != null), super(key: key, child: child);
|
优点:不用每个视图(如tab页)去处理。
总结
页面和视图的进入和离开方案,总结如下:
| 序号 |
类型 |
方案 |
优点与不足 |
| 1 |
页面 |
LifeCyclePage + route |
能比view增加具体显示和离开的原因 |
| 2 |
页面内的视图 |
LifeCycleView + visibility |
只能知道显示、还是离开 |
附一、Flutter 曝光埋点
1、使用 VisibilityDetector 监听子组件可见性的变化(什么时候触发变化)
2、
附二、iOS tableview的曝光埋点
其他参考文档:iOS-曝光埋点
- 使用
UIView的window属性:可以通过检查视图的window属性是否为空来判断视图是否在屏幕上可见。如果window属性为非空值,表示视图正在显示。
- 使用
CGRect和CGRectIntersectsRect函数:可以通过将视图的frame转换到屏幕坐标系中,然后与屏幕的bounds进行比较,来确定视图是否在屏幕上可见以及可见的比例。
- **使用
CADisplayLink**:可以通过CADisplayLink来定期检查视图的可见性。在每次屏幕刷新时,都可以检查视图的window属性和frame,从而实现对视图可见性的实时监控。
- 使用
isDisplayedInScreen类别方法:可以通过为UIView添加一个类别,并在该类别中实现isDisplayedInScreen方法,来判断视图是否在屏幕上可见。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
- (void)startMonitoringViewVisibility:(UIView *)view { CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(checkViewVisibility:)]; [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; }
- (void)checkViewVisibility:(CADisplayLink *)displayLink { }
- (BOOL)isViewVisible:(UIView *)view { CGRect viewRect = [view convertRect:view.bounds toView:nil]; CGRect screenRect = [UIScreen mainScreen].bounds; CGRect intersectionRect = CGRectIntersection(viewRect, screenRect); CGFloat visibleArea = CGRectGetArea(intersectionRect); CGFloat viewArea = CGRectGetArea(view.bounds); return (visibleArea / viewArea) > 0.5; }
|
1、Flutter visibility_detector 原理解析
使用方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @override Widget build(BuildContext context) { return VisibilityDetector( key: Key('exposure_$hashCode'), onVisibilityChanged: (visibilityInfo) { var visiblePercentage = visibilityInfo.visibleFraction * 100; var inScreen = visiblePercentage >= widget.exposeFactor; if (inScreen != show) { if (inScreen) { _onExpose(); } else { _onHide(); } } }, child: widget.child, ); }
|
底层核心逻辑代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| class VisibilityDetector extends SingleChildRenderObjectWidget
// 2、 重写其 createRenderObject(BuildContext context) 方法 返回RenderVisibilityDetector @override RenderVisibilityDetector createRenderObject(BuildContext context) { return RenderVisibilityDetector( key: key, onVisibilityChanged: onVisibilityChanged, ); } }
class RenderVisibilityDetector extends RenderProxyBox { RenderVisibilityDetector({ RenderBox child, @required this.key, @required VisibilityChangedCallback onVisibilityChanged, }) : assert(key != null), _onVisibilityChanged = onVisibilityChanged, super(child);
@override void paint(PaintingContext context, Offset offset) { if (onVisibilityChanged == null) { VisibilityDetectorLayer.forget(key); super.paint(context, offset); return; }
final layer = VisibilityDetectorLayer( key: key, widgetSize: semanticBounds.size, paintOffset: offset, onVisibilityChanged: onVisibilityChanged); context.pushLayer(layer, super.paint, offset); } }
|
在 RenderVisibilityDetector 中,在绘制方法 paint() 里 push 了一个 Layer,这个Layer就是 VisibilityDetector 用于监听可见状态的关键。
End