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,這就解決了問題二。