GitHub链接git
FlutterPageTracker是一个用于监听页面露出
、离开
的plugin。它具备如下特性:github
露出
和离开
事件(PageRoute),
曝光事件
和前一个页面的离开事件
。离开事件
和前一个页面的曝光事件
。露出
和离开
(PopupRoute),
露出
、离开
事件切换
事件
入栈
时,前一个页面会触发页面离开事件
出栈
时,前一个页面会触发页面曝光事件
曝光事件
和离开事件
https://github.com/SBDavid/flutter_sliver_tracker
dependencies:
flutter_page_tracker: ^1.2.2
复制代码
import 'package:flutter_page_tracker/flutter_page_tracker.dart';
复制代码
void main() => runApp(
TrackerRouteObserverProvider(
child: MyApp(),
)
);
复制代码
必须使用PageTrackerAware
和TrackerPageMixin
这两个mixinapp
class HomePageState extends State<MyHomePage> with PageTrackerAware, TrackerPageMixin {
@override
Widget build(BuildContext context) {
return Container();
}
@override
void didPageView() {
super.didPageView();
// 发送页面露出事件
}
@override
void didPageExit() {
super.didPageExit();
// 发送页面离开事件
}
}
复制代码
class PopupPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SimpleDialog(
children: <Widget>[
TrackerDialogWrapper(
didPageView: () {
// 发送页面曝光事件
},
didPageExit: () {
// 发送页面离开事件
},
child: Container(),
),
],
);
}
}
复制代码
class TabViewPage extends StatefulWidget {
TabViewPage({Key key,}) : super(key: key);
@override
_State createState() => _State();
}
class _State extends State<TabViewPage> with TickerProviderStateMixin {
TabController tabController = TabController(initialIndex: 0, length: 3, vsync: this);
@override
Widget build(BuildContext context) {
return Scaffold(
// 添加TabView的包裹层
body: PageViewWrapper(
// Tab页数量
pageAmount: 3,
// 初始Tab下标
initialPage: 0,
// 监听Tab onChange事件
changeDelegate: TabViewChangeDelegate(tabController),
child: TabBarView(
controller: tabController,
children: <Widget>[
Builder(
builder: (_) {
// 监听由PageViewWrapper转发的PageView,PageExit事件
return PageViewListenerWrapper(
0,
onPageView: () {
// 发送页面曝光事件
},
onPageExit: () {
// 发送页面离开事件
},
child: Container(),
);
},
),
// 第二个Tab
// 第三个Tab
],
),
),
);
}
}
复制代码
class PageViewInTabViewPage extends StatefulWidget {
@override
_State createState() => _State();
}
class _State extends State<PageViewInTabViewPage> with TickerProviderStateMixin {
TabController tabController;
PageController pageController;
@override
void initState() {
super.initState();
tabController = TabController(initialIndex: 0, length: 3, vsync: this);
pageController = PageController();
}
@override
Widget build(BuildContext context) {
return Scaffold(
// 外层TabView
body: PageViewWrapper(
pageAmount: 3, // 子Tab数量
initialPage: 0, // 首个展示的Tab序号
changeDelegate: TabViewChangeDelegate(tabController),
child: TabBarView(
controller: tabController,
children: <Widget>[
Builder(
builder: (BuildContext context) {
// 转发上层的事件
return PageViewListenerWrapper(
0,
// 内层PageView
child: PageViewWrapper(
changeDelegate: PageViewChangeDelegate(pageController),
pageAmount: 3,
initialPage: pageController.initialPage,
child: PageView(
controller: pageController,
children: <Widget>[
PageViewListenerWrapper(
0,
onPageView: () {
// 页面露出事件
},
onPageExit: () {
// 页面离开事件
},
child: Container()
),
// PageView中的第二个页面
// PageView中的第三个页面
],
),
)
);
},
),
// tab2
// tab3
],
),
)
);
}
}
复制代码
页面的埋点追踪一般处于业务开发的最后一环,留给埋点的开发时间一般并不充裕,可是埋点数据对于后期的产品调整有重要的意义,因此一个稳定高效的埋点框架是很是重要的。框架
咱们指望当调用Navigator.of(context).pushNamed("XXX Page");
时,首先对以前的页面发送PageExit
,而后对当前页面发送PageView
事件。当调用Navigator.of(context).pop();
时则,首先发送当前页面的PageExit
事件,再发送以前页面的PageView
事件。less
咱们首先想到的是使用RouteObserver,可是PageView
和PageExit
发送的顺序相反。而且PopupRoute类型的路由会影响前一个页面的埋点事件发送,例如咱们入栈的顺序是 A页面 -> A页面上的弹窗 -> B页面,可是在这个过程当中A页面的PageExit
事件没有发送。ide
因此咱们必须本身管理路由栈,这样判断不一样路由的类型,并控制事件的顺序。详细实现方案在后面展开。ui
这两个组件虽然与Flutter的路由无关,可是在产品经理眼中它们任属于页面。而且当Tab发生首次曝光和切换的时候咱们都须要发送埋点事件。this
例如当Tab页A首次曝光时,咱们首先发送上一个页面的PageExit
事件,而后发送TabA的PageView
事件。当咱们从TabA切换到TabB的时候,先发送TabA的PageExit
事件,而后发送TabB的PageView
事件。当咱们push一个新的路由时,须要发送TabB的PageExit
事件。spa
这套流程须要Tab页和普通页面之间经过事件机制来交互,若是直接把这套机制搬到业务代码中,那么业务代码中就会包含大量与业务无关而且重复的代码。详细的抽象方案见后文。插件
RouteObserver给了咱们一个不错的起点,咱们重写其中的didPop
和didPush
方法就并调整事件发送的顺序就能够解决这个问题。详见TrackerStackObserver,在didpop
方法中咱们先触发上一个路由的PageExit
事件,而后再触发当前路由的PageView
事件。
在RouteObserver.didPop(Route route, Route previousRoute)中,咱们能够经过previousRoute找到上一个路由,并更具它来发送上一个路由的PageView事件。可是若是上一个路由是Dialog
,就会形成错误,由于咱们实际想要的是包含这个Dialog
的路由。
要解决这个问题咱们必须本身维护一个路由栈,这样当didPop
触发时咱们就能够找到真正的上一个路由。请参考这一段代码,这里的routes
是当前的路由栈。
这个问题能够分解为两个小问题:
为了解决这两个问题,咱们须要一个容器来管理tab页面的状态而且承载事件转发的任务。详见下图:
其中TabsWrapper会监听来自Flutter的路由事件,并转发给当前曝光的Tab,这就能够解决了问题一。
同时TabsWrappe也会包含一个TabController
和上一个被打开的Tab索引,TabsWrappe会监听来自TabController
的onChange(index)事件,并把事件转发给对应的tab,这就解决了问题二。