flutter_page_tracker
簡介
FlutterPageTracker是一個用於監聽頁面露出
、離開
的plugin。它具有以下特性:
- 1.監聽普通頁面的
露出
和離開
事件(PageRoute),- 當前頁面入棧會觸發當前頁面的
曝光事件
和前一個頁面的離開事件
。 - 當前頁面出棧會觸發當前頁面的
離開事件
和前一個頁面的曝光事件
。
- 當前頁面入棧會觸發當前頁面的
- 2.監聽對話方塊的
露出
和離開
(PopupRoute),- 它和PageRoute的區別是,當前對話方塊的露出和關閉不會觸發前一個頁面的
露出
、離開
事件
- 它和PageRoute的區別是,當前對話方塊的露出和關閉不會觸發前一個頁面的
- 3.監聽PageView、TabView元件的
切換
事件- 當一個PageView或者TabView
入棧
時,前一個頁面會觸發頁面離開事件
- 當一個PageView或者TabView
出棧
時,前一個頁面會觸發頁面曝光事件
- 當焦點頁面發生變化時,舊的頁面觸發頁面露出,新的頁面觸發PageView
- PageView元件
- TabView元件
- 當一個PageView或者TabView
- 4.PageView和TabView巢狀使用
- 我們可以將這兩種元件巢狀在一起使用,不限制巢狀的層次
- 發生焦點變化的PageView(或者TabView)以及它的子級都會受到
曝光事件
和離開事件
- 5.滑動曝光事件
- 如果你對列表的滑動露出事件感興趣,你可以參考flutter_sliver_tracker外掛
https://github.com/SBDavid/flutter_sliver_tracker
執行Demo程式
- 克隆程式碼到本地: git clone git@github.com:SBDavid/flutter_page_tracker.git
- 切換工作路徑: cd flutter_page_tracker/example/
- 啟動模擬器
- 執行: flutter run
使用
1. 安裝
dependencies:
flutter_page_tracker: ^1.2.2
複製程式碼
2. 引入flutter_page_tracker
import 'package:flutter_page_tracker/flutter_page_tracker.dart';
複製程式碼
3. 傳送普通頁面埋點事件
3.1 新增路由監聽
void main() => runApp(
TrackerRouteObserverProvider(
child: MyApp(),
)
);
複製程式碼
3.2 在元件中傳送埋點事件
必須使用PageTrackerAware
和TrackerPageMixin
這兩個mixin
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();
// 傳送頁面離開事件
}
}
複製程式碼
3.3 Dialog的埋點
class PopupPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SimpleDialog(
children: <Widget>[
TrackerDialogWrapper(
didPageView: () {
// 傳送頁面曝光事件
},
didPageExit: () {
// 傳送頁面離開事件
},
child: Container(),
),
],
);
}
}
複製程式碼
3.3 TabView傳送埋點事件(PageView參考example)
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
],
),
),
);
}
}
複製程式碼
3.4 TabView中巢狀PageView(PageView也可以巢狀TabView,TabView也可以巢狀TabView)
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
],
),
)
);
}
}
複製程式碼
原理篇
1.概述
頁面的埋點追蹤通常處於業務開發的最後一環,留給埋點的開發時間通常並不充裕,但是埋點資料對於後期的產品調整有重要的意義,所以一個穩定高效的埋點框架是非常重要的。
2. 我們期望埋點框架所具備的功能
2.1 PageView,PageExit事件
我們期望當呼叫Navigator.of(context).pushNamed("XXX Page");
時,首先對之前的頁面傳送PageExit
,然後對當前頁面傳送PageView
事件。當呼叫Navigator.of(context).pop();
時則,首先傳送當前頁面的PageExit
事件,再傳送之前頁面的PageView
事件。
我們首先想到的是使用RouteObserver,但是PageView
和PageExit
傳送的順序相反。並且PopupRoute型別的路由會影響前一個頁面的埋點事件傳送,例如我們入棧的順序是 A頁面 -> A頁面上的彈窗 -> B頁面,但是在這個過程中A頁面的PageExit
事件沒有傳送。
所以我們必須自己管理路由棧,這樣判斷不同路由的型別,並控制事件的順序。詳細實現方案在後面展開。
2.2 TagView元件於PageView元件
這兩個元件雖然與Flutter的路由無關,但是在產品經理眼中它們任屬於頁面。並且當Tab發生首次曝光和切換的時候我們都需要傳送埋點事件。
例如當Tab頁A首次曝光時,我們首先傳送上一個頁面的PageExit
事件,然後傳送TabA的PageView
事件。當我們從TabA切換到TabB的時候,先傳送TabA的PageExit
事件,然後傳送TabB的PageView
事件。當我們push一個新的路由時,需要傳送TabB的PageExit
事件。
這套流程需要Tab頁和普通頁面之間通過事件機制來互動,如果直接把這套機制搬到業務程式碼中,那麼業務程式碼中就會包含大量與業務無關並且重複的程式碼。詳細的抽象方案見後文。
3. 解決這些問題
3.1 解決PageView,PageExit的順序問題
RouteObserver給了我們一個不錯的起點,我們重寫其中的didPop
和didPush
方法就並調整事件傳送的順序就可以解決這個問題。詳見TrackerStackObserver,在didpop
方法中我們先觸發上一個路由的PageExit
事件,然後再觸發當前路由的PageView
事件。
3.2 避免彈窗的干擾(例如Dialog)
在RouteObserver.didPop(Route route, Route previousRoute)中,我們可以通過previousRoute找到上一個路由,並更具它來傳送上一個路由的PageView事件。但是如果上一個路由是Dialog
,就會造成錯誤,因為我們實際想要的是包含這個Dialog
的路由。
要解決這個問題我們必須自己維護一個路由棧,這樣當didPop
觸發時我們就可以找到真正的上一個路由。請參考這一段程式碼,這裡的routes
是當前的路由棧。
3.3 如何上報TabView中的埋點事件,並和其它頁面串聯起來
這個問題可以分解為兩個小問題:
-
- 如何把TabView頁面和普通的路由進行串聯?
-
- 當Tab發生切換時如何傳送埋點事件?
為了解決這兩個問題,我們需要一個容器來管理tab頁面的狀態並且承載事件轉發的任務。詳見下圖:
。其中TabsWrapper會監聽來自Flutter的路由事件,並轉發給當前曝光的Tab,這就可以解決了問題一。
同時TabsWrappe也會包含一個TabController
和上一個被開啟的Tab索引,TabsWrappe會監聽來自TabController
的onChange(index)事件,並把事件轉發給對應的tab,這就解決了問題二。