不要冒然評價我,你只知道個人名字,殊不知道個人故事,你只是聽聞我作了什麼,殊不知我經歷過什麼。html
俗話說得好,產品有三寶,彈窗浮層加引導。前端
上圖截圖自我司 App 曉黑板中的口算模塊,相信每一個 App 開發在工做中都碰到這種場景,爲了不用戶對新功能產生困惑,會對一些功能加一些引導操做。在原生開發中,例如 Android 開發中,咱們可使用 NewbieGuide 等開源庫來實現。可是很遺憾的是,在 Dart packages 中找了一圈,一無所得。git
可是咱們仍是很快就解決了問題,既然解決不了問題,咱們就要學會讓這個問題不存在,這時候開發一寶就顯得尤爲有用了。
github
本文完,你們下期再見👋segmentfault
~api
~瀏覽器
~electron
開個玩笑。真的猛男,勇於直面慘淡的人生,也勇於正視淋漓的鮮血,這區區需求怎麼能打倒咱們。接下來咱們開始琢磨一下這個引導操做要怎麼實現,相信各位小夥伴接到這個需求第一個想到的就是,這玩意兒不就是在整個頁面上面蓋一個蒙層,而後把中間再摳一塊出來。最後再加一些文字和一個下一步按鈕就好了嘛。你要是把這個需求想得這麼簡單,那你可就真是大對特對了。因此咱們就按前面說的三步來實現這個東西。固然,你若是不想接着往下看的話,能夠直接點擊這裏使用咱們開發完成的引導組件庫來快速在你的 Flutter 項目中接入引導功能。ide
那麼如何在 Flutter 頁面上蓋一個浮層呢?翻了一下 Flutter 的 API 文檔,找到了兩個法寶,分別是 Overlay 和 OverlayEntry。二者的使用方法以下。ui
class _MyWidgetState extends State<MyWidget> { OverlayEntry overlayEntry; @override Widget build(BuildContext context) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ RaisedButton( onPressed: () { /// 1. 建立一個 overlayEntry 實例,builder 方法返回一個 Widget /// 該 Widget 會被渲染到頁面頂層 overlayEntry = OverlayEntry( builder: (context) => Container( color: Colors.white.withOpacity(.4), child: Center( child: RaisedButton( onPressed: () { /// 3. 執行 remove 方法銷燬 overlayEntry 實例 overlayEntry.remove(); }, child: Text('點我關閉 OverlayEntry'), ), ), ), ); /// 2. 使用 OverlayState.insert 方法來顯示 overlayEntry Overlay.of(context).insert(overlayEntry); }, child: Text('點我康康 Overlay 的用法'), ), ], ), ); } }
若是你不嫌煩的話,還能夠點擊這裏親自試一試效果。
上面解決了浮層問題,下面咱們就來看一下如何讓蒙層中間某一塊區域亮起來的問題,這個問題很簡單,爲何會亮,由於有光,怎麼有光。我想到了上帝,由於上帝說要有光,因而就有了光。
接下來就是如何成爲上帝的第一步,咱們要找到須要高亮的那個組件的大小和位置,通過我縝密的調查,發如今 Flutter 中能夠經過 GlobalKey
來獲取一個元素的大小和位置,核心代碼以下:
/// 1. 聲明一個 globalKey final globalKey = GlobalKey(); RaisedButton( /// 2. 將 globalKey 綁定到組件上 key: globalKey, onPressed: () { /// }, child: Text( '點我康康控制檯輸出', ), ); /// 3. 經過下面的代碼來獲取組件的尺寸和位置 RenderBox renderBox = globalKey.currentContext.findRenderObject(); Size size = renderBox.size; Offset offset = renderBox.localToGlobal(Offset.zero); print(size); print(offset);
其中 Size
中有 width
和 height
屬性,分別表示高亮組件的寬高屬性,Offset
中有 dx
和 dy
屬性,分別表示組件左上角距離屏幕左側和頂部的距離。相信作 Web 開發的同窗對這個都很熟悉了。
若是你不嫌煩的話,還能夠點擊這裏親自試一試效果。
咱們已經在代碼層面定位到這個組件的位置了,接下來就是對該區域進行精準打擊,讓這塊區域不被浮層的顏色所覆蓋,請當作爲上帝的第二步。
先看結果,能夠看到中間的組件沒有被遮罩層遮住,可是有眼睛的同窗可能會發現,爲啥上下還會各有一段也沒被遮住,那是由於 RaisedButton
上下自帶一個 margin,因此代碼獲取 RaisedButton
的實際佔位比看起來要大,有興趣的同窗能夠去研究一下怎麼去掉這個 margin。又有同窗會說了,爲啥這個截圖是移動端的截圖,不是 Web 瀏覽器上的截圖。這個問題問得好,請看下面核心代碼。
OverlayEntry( builder: (context) => Stack( children: [ /// 咱們使用了 ColorFiltered 來實現這個功能 ColorFiltered( colorFilter: ColorFilter.mode( /// 遮罩層顏色 Colors.red.withOpacity(.4), BlendMode.srcOut, ), child: Stack( children: [ Container( decoration: BoxDecoration( /// 任何顏色都可 color: Colors.white, backgroundBlendMode: BlendMode.dstOut, ), ), Positioned( /// 和須要高亮組件的大小和位置均一致 child: Container( /// 任何顏色都可 color: Colors.white, width: size.width, height: size.height, ), left: offset.dx, top: offset.dy, ), ], ), ), ], ), );
上面能夠看到,咱們使用了 Stack
和 Positioned
來實現將組件放到咱們想要的位置,而後實現高亮的核心組件是 ColorFiltered
,ColorFiltered 可太好了,我可太喜歡這個組件了,它能作的事情也頗有趣,後面咱們還再出一篇文章去單獨地介紹它。敬請期待叭~
而後說一說爲啥咱們的截圖是移動端,再也不是 Web 端,由於 ColorFiltered
這哥們太強大,以致於 Flutter 團隊在 Flutter Web 上尚未徹底實現它。你能夠在 Flutter 倉庫裏面的隨便找到很多關於 ColorFiltered 在 Web 上表現異常的 Issue。
固然若是你不嫌煩的話,並且也願意在 Web 上試一下沒有效果的效果,我也很貼心的爲你準備了在線連接。
終於到了最後一步,加上下一步按鈕和文字,這就不用說了,建立 overlayEntry
的時候你願意在 builder
方法裏面返回啥都行。
那麼我還漏掉了什麼沒說呢?認真思考的同窗可能想到,我要怎麼更新 overlayEntry
呢,引導頁通常有多個呀,我不能每次都 remove 掉當前的,而後再 insert 一個新的吧,那樣頁面確定會有閃爍。其實若是認真看了OverlayEntry文檔的話確定不會錯過這個 markNeedsBuild
方法。這裏就再也不舉個例子了,我太懶了。總之就是若是 builder 的內容有變化,你對 overlayEntry 執行一次 overlayEntry.markNeedsBuild()
就能夠了,Flutter 就會從新渲染一次 builder 返回的內容。以此來作到無閃爍切換引導頁。
相信完成了上面三步,咱們即便沒有成爲上帝,也能作到有光,讓指定區域高亮起來了。一句話總結:
咱們使用了 Overlay 和 ColorFiltered 即完成了引導頁的製做
耐心看完了的小夥伴確定都以爲做者誠不欺我,確實很簡單,這兩個組件我都用過。可是:
So,咱們貼心地開發了一個小的庫,並借鑑了 Web 端的知名引導庫 Intro.js 的名字,給它取名爲 flutter_intro
。
少廢話,先看東西。
上圖即爲使用 flutter_intro
的默認主題能夠快速實現的引導效果。那麼我要怎麼使用呢?首先在項目依賴文件 pubspec.yaml 中引入 flutter_intro
。點擊這裏查看最新版本。
import 'package:flutter_intro/flutter_intro.dart'; /// 1. 引入 Intro,實例化一個對象,傳入必傳的 stepCount 參數 /// 和 widgetBuilder 參數,其中 widgetBuilder 可使用庫內置的 /// 方法,這樣就只需傳入須要顯示的文本便可。 /// 固然若是你不嫌煩的話,也能夠本身實現 widgetBuilder 方法 Intro intro = Intro( /// 總共的引導頁數量,必傳 stepCount: 4, /// 高亮區域與 widget 的內邊距 padding: EdgeInsets.all(8), /// 高亮區域的圓角半徑 borderRadius: BorderRadius.all(Radius.circular(4)), /// 使用庫默認提供的 useDefaultTheme 能夠快速構建引導頁 /// 須要自定義引導頁樣式和內容,須要本身實現 widgetBuilder 方法 widgetBuilder: StepWidgetBuilder.useDefaultTheme( /// 提示文本 texts: [ '你好呀,我是 Flutter Intro。', '我能夠幫你在 Flutter 項目中快讀實現 Step By Step 引導。', '個人用法也十分簡單,你能夠經過 example 和 api 文檔快速掌握和使用。', '爲了快速實現引導,我也默認提供了一套樣式,開箱即用,祝你們使用愉快,再見!', ], /// 按鈕文字 btnLabel: '我知道了', /// 是否在按鈕後顯示當前步驟 showStepLabel: true, ), );
下圖爲 flutter_intro
支持的一些參數配置所對應的位置介紹:
好熟悉的操做,和上面介紹的如出一轍。
固然,這裏爲了方便你們使用,庫的內部爲你們建立好了 globalKey,使用的時候只須要經過 intro.keys[下標]
獲取就好了。
Placeholder( /// 2. 第一個引導頁即綁定 keys 中的第一項,以此內推 key: intro.keys[0] )
真是太方便了!
好了,咱們已經作好所有的準備了。輸入如下指令,點擊運行。
intro.start(context);
沒了,是否是很簡單。
若是你嫌個人默認主題醜,想要本身實現 widgetBuilder
方法,我也能夠接受。
final Widget Function(StepWidgetParams params) widgetBuilder;
該方法會在引導頁出現時由 flutter_intro
內部調用,並會將當前頁面上的一些數據經過參數的形式 StepWidgetParams
傳進來,最終渲染在屏幕上的爲此方法返回的組件。
class StepWidgetParams { /// 返回前一個引導頁方法,若是沒有,則爲 null final VoidCallback onPrev; /// 進入下一個引導頁方法,若是沒有,則爲 null final VoidCallback onNext; /// 結束全部引導頁方法 final VoidCallback onFinish; /// 當前執行到第幾個引導頁,從 0 開始 final int currentStepIndex; /// 引導頁的總數 final int stepCount; /// 屏幕的寬高 final Size screenSize; /// 高亮組件的的寬高 final Size size; /// 高亮組件左上角座標 final Offset offset; }
StepWidgetParams
提供了生成引導頁所須要的全部參數,默認提供的主題也是基於此參數生成引導頁。
天下沒有不散的筵席,可是若是你請客,我能夠多陪你吃一下子。
這篇文章主要介紹瞭如何在 Flutter 中實現操做引導,而且咱們基於此封裝了一個咱們眼裏東半球最好用的 flutter_intro
。
對 Electron 感興趣?請關注咱們的開源項目 Electron Playground,帶你極速上手 Electron。
咱們每週五會精選一些有意思的文章和消息和你們分享,來掘金關注咱們的 曉前端週刊。
咱們是好將來 · 曉黑板前端技術團隊。
咱們會常常與你們分享最新最酷的行業技術知識。
歡迎來 知乎、掘金、Segmentfault、CSDN、簡書、開源中國、博客園 關注咱們。