做者:何凱俊html
github
不要冒然評價我,你只知道個人名字,殊不知道個人故事,你只是聽聞我作了什麼,殊不知我經歷過什麼。git
俗話說得好,產品有三寶,彈窗浮層加引導。github
上圖截圖自我司 App 曉黑板中的口算模塊,相信每一個 App 開發在工做中都碰到這種場景,爲了不用戶對新功能產生困惑,會對一些功能加一些引導操做。在原生開發中,例如 Android 開發中,咱們可使用 NewbieGuide 等開源庫來實現。可是很遺憾的是,在 Dart packages 中找了一圈,一無所得。api
可是咱們仍是很快就解決了問題,既然解決不了問題,咱們就要學會讓這個問題不存在,這時候開發一寶就顯得尤爲有用了。 瀏覽器
本文完,你們下期再見👋markdown
~ide
~oop
~ui
開個玩笑。真的猛男,勇於直面慘淡的人生,也勇於正視淋漓的鮮血,這區區需求怎麼能打倒咱們。接下來咱們開始琢磨一下這個引導操做要怎麼實現,相信各位小夥伴接到這個需求第一個想到的就是,這玩意兒不就是在整個頁面上面蓋一個蒙層,而後把中間再摳一塊出來。最後再加一些文字和一個下一步按鈕就好了嘛。你要是把這個需求想得這麼簡單,那你可就真是大對特對了。因此咱們就按前面說的三步來實現這個東西。固然,你若是不想接着往下看的話,能夠直接點擊這裏使用咱們開發完成的引導組件庫來快速在你的 Flutter 項目中接入引導功能。spa
那麼如何在 Flutter 頁面上蓋一個浮層呢?翻了一下 Flutter 的 API 文檔,找到了兩個法寶,分別是 Overlay 和 OverlayEntry。二者的使用方法以下。
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
。
並且我又欠了大家一篇介紹 ColorFiltered
的文章。
咱們是好將來·曉黑板 Flutter 團隊,期待下次再見👋。