APP 啟動頁在國內是最常見也是必備的場景,其中啟動頁在 iOS 上算是強制性的要求,其實配置啟動頁挺簡單,因為在 Flutter 里現(xiàn)在只需要:
創(chuàng)新互聯(lián)是一家專注于成都網站設計、成都網站建設與策劃設計,美蘭網站建設哪家好?創(chuàng)新互聯(lián)做網站,專注于網站建設10余年,網設計領域的專業(yè)建站公司;建站業(yè)務涵蓋:美蘭等地區(qū)。美蘭做網站價格咨詢:18982081108
一般只要配置無誤并且圖片尺寸匹配,基本上就不會有什么問題, 那既然這樣,還有什么需要適配的呢?
事實上大部分時候 iOS 是不會有什么問題, 因為 LaunchScreen.storyboard 的流程本就是 iOS 官方用來做應用啟動的過渡;而對于 Andorid 而言,直到 12 之前 windowBackground 這種其實只能算“民間”野路子 ,所以對于 Andorid 來說,這其中就涉及到一個點:
所以下面主要介紹 Flutter 在 Android 上為了這個啟動圖做了哪些騷操作~
在已經忘記版本的“遠古時期” , FlutterActivity 還在 io.flutter.app.FlutterActivity 路徑下的時候,那時啟動頁的邏輯相對簡單,主要是通過 App 的 AndroidManifest 文件里是否配置了 SplashScreenUntilFirstFrame 來進行判斷。
在 FlutterActivity 內部 FlutterView 被創(chuàng)建的時候,會通過讀取 meta-data 來判斷是否需要使用 createLaunchView 邏輯 :
是不是很簡單,那就會有人疑問為什么要這樣做?我直接配置 Activity 的 android:windowBackground 不就完成了嗎?
這就是上面提到的時間差問題, 因為啟動頁到 Flutter 渲染完第一幀畫面中間,會出現(xiàn)概率出現(xiàn)黑屏的情況,所以才需要這個行為來實現(xiàn)過渡 。
經歷了“遠古時代”之后, FlutterActivity 來到了 io.flutter.embedding.android.FlutterActivity , 在到 2.5 版本發(fā)布之前,F(xiàn)lutter 又針對這個啟動過程做了不少調整和優(yōu)化,其中主要就是 SplashScreen 。
自從開始進入 embedding 階段后, FlutterActivity 主要用于實現(xiàn)了一個叫 Host 的 interface ,其中和我們有關系的就是 provideSplashScreen 。
默認情況下它會從 AndroidManifest 文件里是否配置了 SplashScreenDrawable 來進行判斷 。
默認情況下當 AndroidManifest 文件里配置了 SplashScreenDrawable ,那么這個 Drawable 就會在 FlutterActivity 創(chuàng)建 FlutterView 時被構建成 DrawableSplashScreen 。
DrawableSplashScreen 其實就是一個實現(xiàn)了 io.flutter.embedding.android.SplashScreen 接口的類,它的作用就是:
之后 FlutterActivity 內會創(chuàng)建出 FlutterSplashView ,它是個 FrameLayout。
FlutterSplashView 將 FlutterView 和 ImageView 添加到一起, 然后通過 transitionToFlutter 的方法來執(zhí)行動畫,最后動畫結束時通過 onTransitionComplete 移除 splashScreenView 。
所以整體邏輯就是:
當然這里也是分狀態(tài):
當然這個階段的 FlutterActivity 也可以通過 override provideSplashScreen 方法來自定義 SplashScreen 。
看到沒有,做了這么多其實也就是為了彌補啟動頁和 Flutter 渲染之間, 另外還有一個優(yōu)化,叫 NormalTheme 。
通過該配置 NormalTheme ,在 Activity 啟動時,就會首先執(zhí)行 switchLaunchThemeForNormalTheme(); 方法將主題從 LaunchTheme 切換到 NormalTheme 。
大概配置完就是如下樣子, 前面分析那么多其實就是為了告訴你,如果出現(xiàn)問題了,你可以從哪個地方去找到對應的點 。
講了那么多, Flutter 2.5 之后 provideSplashScreen 和 io.flutter.embedding.android.SplashScreenDrawable 就被棄用了,驚不喜驚喜,意不意外,開不開心 ?
通過源碼你會發(fā)現(xiàn),當你設置了 splashScreen 的時候,會看到一個 log 警告:
為什么會棄用?
其實這個提議是在 這個 issue 上,然后通過 這個 pr 完成調整。
大概意思就是: 原本的設計搞復雜了,用 OnPreDrawListener 更精準,而且不需要為了后面 Andorid12 的啟動支持做其他兼容,只需要給 FlutterActivity 等類增加接口開關即可 。
也就是2.5之后 Flutter 使用 ViewTreeObserver.OnPreDrawListener 來實現(xiàn)延遲直到加載出 Flutter 的第一幀。
為什么說默認情況? 因為這個行為在 FlutterActivity 里,是在 getRenderMode() == RenderMode.surface 才會被調用,而 RenderMode 又和 BackgroundMode 有關心 。
所以在 2.5 版本后, FlutterActivity 內部創(chuàng)建完 FlutterView 后就會執(zhí)行一個 delayFirstAndroidViewDraw 的操作。
這里主要注意一個參數: isFlutterUiDisplayed 。
當 Flutter 被完成展示的時候, isFlutterUiDisplayed 就會被設置為 true。
所以當 Flutter 沒有執(zhí)行完成之前, FlutterView 的 onPreDraw 就會一直返回 false ,這也是 Flutter 2.5 開始之后適配啟動頁的新調整。
看了這么多,大概可以看到其實開源項目的推進并不是一帆風順的,沒有什么是一開始就是最優(yōu)解,而是經過多方嘗試和交流,才有了現(xiàn)在的版本,事實上開源項目里,類似這樣的經歷數不勝數:
Flutter支持穩(wěn)定的桌面設備開發(fā)已經一段時間了,不得不說,F(xiàn)lutter多平臺支持的特性真的很香。我本人并沒有任何桌面開發(fā)的經驗,但仍然使用Flutter開發(fā)出了一個桌面版小程序,功能很簡單,就是對輸入的json做格式化處理和轉模型。
話不多說,先來看看實際效果。 項目源碼地址
開發(fā)環(huán)境如下:
Flutter : 2.8.1
Dart : 2.15.1
IDE : VSCode
JSON作為我們日常開發(fā)工作中經常要打交道的一種數據格式,它共有6種數據類型: null , num , string , object , array , bool 。我們勢必對它又愛又恨。愛他因為他作為數據處理的一種格式確實非常方便簡潔。但是在我們做Flutter開發(fā)中,又需要接觸到json解析時,就會感覺非常棘手,因為flutter沒有反射,導致json轉模型這塊需要手寫那繁雜的映射關系。就像下面這樣子。
數據量少還能接受,一旦量大,那么光手寫這個解析方法都能讓你懷疑人生。更何況手寫還有出錯的可能。好在官方有個工具**json_serializable**可以自動生成這塊轉換代碼,也解決了flutter界json轉模型的空缺。當然,業(yè)界也有專門解析json的網站,可以自動生成dart代碼,使用者在生成后復制進項目中即可,也是非常方便的。
本項目以json解析為切入點,和大家一起來看下flutter是如何開發(fā)桌面應用的。
要讓我們的flutter項目支持桌面設備。我們首先需要修改下flutter的設置。如下,讓我們的項目支持 windows 和 macos 系統(tǒng)。
接下來使用 flutter create 命令創(chuàng)建我們的模版工程。
創(chuàng)建完項目后,我們就可以 run 起來了。
先來看下整體界面,界面四塊,分別為功能模塊、文件選擇模塊、輸入模塊、輸出模塊。
我們在新建一個桌面應用時,默認的模版又一個Appbar,此時應用可以用鼠標拖拽移動,放大縮小,還可以縮到很小。但是,我們一旦去掉這個導航欄,那么窗口就不能用鼠標拖動了,并且我們往往不希望用戶將我們的窗口縮放的很小,這會導致頁面異常,一些重要信息都展示不全。因此這里需要借助第三方組件 bitsdojo_window 。通過 bitsdojo_window ,我們可以實現(xiàn)窗口的定制化,拖動,最小尺寸,最大尺寸,窗口邊框,窗口頂部放大、縮小、關閉的按鈕等。
通過 InkWell 組件,可以捕捉到手勢、鼠標、觸控筆的移動和停留位置
這個功能是鼠標移動后的UI交互界面。要在窗口上顯示一個提示框,可以使用 Overlay 。需要注意的是,由于在 Overlay 上的 text 的根結點不是 Material 風格的組件,因此會出現(xiàn)黃色的下劃線。因此一定要用 Material 包一下 text 。并且你必須給創(chuàng)建的 OverlayEntry 一個位置,否則它將全屏顯示。
讀取說表拖拽的文件一開始想嘗試使用 InkWell 組件,但是這個組件無法識別拖拽中的鼠標,并且也無法從中拿到文件信息。因此放棄。后來從文章《Flutter-2天寫個桌面端APP》中發(fā)現(xiàn)一個可讀取拖拽文件的組件 desktop_drop ,能滿足要求。
使用開源組件 file_picker ,選完圖片后的操作和拖拽選擇圖片后的操作一致。
Textfield 如果要顯示富文本,那么需要自定義 TextEditingController 。并重寫 buildTextSpan 方法。
在做導出功能時遇到下列報錯,保存提示為沒有權限訪問對應目錄下的文件。
通過Apple的開發(fā)文檔找到有關權限問題的說明。其中有個授權私鑰的key為 com.apple.security.files.downloads.read-write ,表示 對用戶的下載文件夾的讀/寫訪問權限 。那么,使用Xcode打開Flutter項目中的mac應用,修改工程目錄下的 DebugProfile.entitlements 文件,向 entitlements 文件中添加 com.apple.security.files.downloads.read-write ,并將值設置為YES,保存后重啟Flutter項目。發(fā)現(xiàn)已經可以向下載目錄中讀寫文件了。
當然,這是正常操作。還有個騷操作就是關閉系統(tǒng)的沙盒機制。將 entitlements 文件的 App Sandbox 設置為NO。這樣我們就可以訪問任意路徑了。當然關閉應用的沙盒也就相當于關閉了應用的防護機制,因此這個選項慎用。
原文地址:
注:亮度調節(jié)和音量調節(jié)gif無法體現(xiàn),功能是ok的,其次默認Icon鎖的close和open實在難以分辨。
環(huán)境:Flutter 2.8.1 channel stable ;Dart 2.15.1
需要音頻播放器的看這里: Flutter音樂播放器
重點說下這個工具類,因為視頻播放,涉及到狀態(tài)改變有很多,筆者剛開始選擇使用 InheritedWidget 來在眾多的widget之間共享數據。但是總感覺這樣有點繁瑣,且不很優(yōu)雅!
這里非廣告,如果是使用 GetX 就很簡單了,筆者也使用了 GetX 進行封裝了,一瀉千里的趕腳!,但是筆者還是那句話:剛開始接觸Flutter的開發(fā)者不是很建議使用 GetX ,可以先熟悉下Flutter狀態(tài)管理的基礎原理再行使用。而且為了盡量簡潔,還是不引入其他的第三方了。
我們選擇對第三方插件進行封裝的目的不外乎這幾個:
于是筆者就寫了一個工具類 VideoPlayerUtils ,專門且只用來處理播放器的所有業(yè)務。包括暫停、播放、跳轉、調節(jié)音量、調節(jié)亮度、切換視頻等操作。在所有的widget中不會引用關于 video_player 或其他第三方插件的任何信息, VideoPlayerUtils 負責widget與播放器之間的所有操作交互。后續(xù)優(yōu)化迭代或更換播放器插件時,只需針對這個工具類進行修改,對所有widget不會有任何的影響,大大的解耦合了。
其中 VideoPlayerState :
提供以上的公共屬性,可以通過 VideoPlayerUtils 來獲取對應的值,使用 get 只讀,使外界不會誤修改這些屬性,以保證數值的安全性。開發(fā)者可根據自身需要自行添加屬性。
提供以上方法來處理播放器的所有業(yè)務。同樣的開發(fā)者可根據自身需要自行添加或修改。
重點說下這個方法,是整個業(yè)務的核心方法,控制視頻的播放或暫停。開發(fā)者只要遇到播放或暫停是均可調用此方法,具體是播放或暫停,內部根據傳入的 url 自行判斷,開發(fā)者不需要關心。
切換新視頻也是使用此方法,傳入的 url 與上次不一致,自動切換新視頻。筆者可根據 statusListener 來監(jiān)聽播放狀態(tài)的改變,以此處理自身邏輯。
這個也需要提下,視頻播放器在播放新視頻時會異步初始化,一般我們的操作是在 initState() 初始化,成功后再 setState() 。這里筆者遇到一個讓人蛋疼的問題:
我們看 video_player 的使用:
VideoPlayer(controller) :widget中已經持有了controller。本來筆者封裝的目的就是為了讓widget與controller的之間解耦合。但此時的筆者。。。。
放棄不是不可能放棄的,這輩子都不會放棄的!
于是筆者取了巧,寫了一個初始化監(jiān)聽器 initializedListener ,包換2個參數: bool,Widget ,初始化是否成功;其中widget為初始化成功返回需要展示的播放器UI,失敗默認返回 const SizedBox() 。
到這里就可以簡單使用了:
沒看錯,視頻播放就是這么簡單。
如果有更多的業(yè)務功能,筆者也按照自己的需求寫了一套,同樣的開發(fā)者可根據自身需要自行添加或修改。
VideoPlayerGestures 主要是處理手勢的,比如快進、快退等跳轉播放;左側上下滑動調節(jié)亮度;右側上下滑動調節(jié)音量;單擊是否開啟沉浸式播放,所有widget的隱藏與顯示;雙擊播放、暫停等。
哦,還有 PercentageWidget 也放到這個文件下了,就是這玩意:
因為顯示的百分比與手勢相關,隨著手勢移動而更新。開發(fā)者可自行處理。
筆者處出于簡單考慮,就按照整個UI的位置命名了。瞅一眼就知道是啥玩意。
同樣的開發(fā)者可根據自身需要自行添加或修改。
就是這玩意:
同樣的開發(fā)者可根據自身需要自行添加或修改。話說這個鎖的 Icon 的open和close是真的難分辨!
就是這玩意:
同樣的開發(fā)者可根據自身需要自行添加或修改。
這玩意是自定義的,別問,問就是跟產品干一架落了下風
主要就是自定義這玩意:
同樣的開發(fā)者可根據自身需要自定義。
注:這里沒有添加緩沖的進度,開發(fā)可查看 video_player 中的源碼 VideoProgressIndicator ,按業(yè)務自行定義。
這玩意就是整合以上的widget,再考慮下全屏的安全區(qū)域,沒啥東西。開發(fā)者可自行處理!
具體的實現(xiàn)監(jiān)聽器的思路, 看這里 。
自此一個漂亮的Flutter視頻播放器就已經結束了。如果您覺得對您有些許幫助的話,歡迎 Star !
目前Flutter平臺主流的兩個播放器是video_player和fijkplayer
pub
github
1、Flutter平臺官方插件,作者是國外的,有問題溝通比較困難,只能通過提交issue
2、硬解碼
4、UI封裝: better_player
基于video_player和Chewie的高級視頻播放器。它解決了許多典型的用例,并且易于運行。
5、播放器寬高比例與視頻內容寬高比例不一致時,會出現(xiàn)圖像壓縮變形的問題
6、調用原生內核播放器:iOS--AVPlayer, Android--ExoPlayer
7、對于分段源 m3u8 的播放不友好,如果一個切片播放超時,會導致整個播放都失敗
8、better_player可以緩存視頻,但不能自定義緩存的地址,只能指定key,和緩存的最大內存量(還未研究超出最大的話是不能緩存新的,還是刪除最舊的)
9、better_player不能完全自定義UI,只能修改類中的一些開放屬性,比如說icon圖標,文字顏色啥的
10、無網絡有緩存時,封面可以正常展示
11、better_player播放失敗有手動retry的設計
pub
github
1、fijkplayer 是一個 Flutter 生態(tài)的媒體播放器,是對 ijkplayer 的 Flutter 封裝,支持 Android 和 iOS。 fijkplayer 使用 ijkplayer 作為播放器內核,ijkplayer 使用 ffmpeg 進行音視頻解封裝和解碼,同時添加了 Android 和 iOS 平臺特有的硬件加速解碼能力。
2 、國內有QQ群,但是活躍度也是不高。
3、可以緩存視頻,可以自定義緩存的地址,方便后續(xù)的內存維護。
4、可以通過FijkPanelWidgetBuilder較大程度上自定義UI。
5、無網絡有緩存視頻時,無法展示封面,因為內部是通過imageProvider去加載網絡圖片的。
7、播放失敗無手動retry的設計
1、兩種播放器都是通過外接紋理方案 (Texture),將播放器視頻畫面渲染接入 flutter 中,性能上優(yōu)于 PlatformView 的接入方法。
如何自己實現(xiàn)?
下面以video_palyer的iOS源碼部分解釋:
iOS用CVPixelBufferRef將渲染出來的數據存在內存中,F(xiàn)lutter engine會將Texture的數據在內存中直接進行映射無需通過Channel傳輸,然后Texture Widget就可以把你提供的這些數據顯示出來。在我們傳輸數據的時候會需要將其與 TextureID 綁定,綁定的過程通過BasicMessageChannel實現(xiàn)數據流的傳輸,以做到實時展示的效果
Flutter Dio源碼分析(一)--Dio介紹
Flutter Dio源碼分析(二)--HttpClient、Http、Dio對比
Flutter Dio源碼分析(三)--深度剖析
Flutter Dio源碼分析(四)--封裝
Flutter Dio源碼分析(一)--Dio介紹視頻教程
Flutter Dio源碼分析(二)--HttpClient、Http、Dio對比視頻教程
Flutter Dio源碼分析(三)--深度剖析視頻教程
Flutter Dio源碼分析(四)--封裝視頻教程
github倉庫地址
本文會手把手教你該怎么去封裝一個類庫,平時在我們的工作中都是拿著別人的造好的輪子在使用,這篇文章將帶你怎么去自己造輪子,以后再碰到別的類庫需要對其進行封裝的時候提供一個的思路和方法。
在前面的文章中,我們對 Dio 的基本使用、請求庫對比、源碼分析,我們知道 Dio 的使用非常的簡單,那為什么還需要進行封裝呢?有兩點如下:
當組件庫方法發(fā)生重要改變需要遷移的時候如果有多處地方用到,那么需要對使用到的每個文件都進行修改,非常的繁瑣而且很容易出問題。
當不需要 Dio 庫的時候,我們可以隨時方便切換到別的網絡請求庫,當然 Dio 目前內置支持使用第三方庫的適配器。
因為一個應用程序基本都是統(tǒng)一的配置方式,所以我們可以針對 攔截器 、 轉換器 、 緩存 、 統(tǒng)一處理錯誤 、 代理配置 、 證書校驗 等多個配置進行統(tǒng)一管理。
因為我們的應用程序在每個頁面中都會用到網絡請求,那么如果我們每次請求的時候都去實例化一個 Dio ,無非是增加了系統(tǒng)不必要的開銷,而使用單例模式對象一旦創(chuàng)建每次訪問都是同一個對象,不需要再次實例化該類的對象。
這是通過靜態(tài)變量的私有構造器來創(chuàng)建的單例模式
我們對 超時時間 、 響應時間 、 BaseUrl 進行統(tǒng)一設置
因為不管是 get() 還是 post() 請求, Dio 內部最終都會調用 request 方法,只是傳入的 method 不一樣,所以我們這里定義一個枚舉類型在一個方法中進行處理
我們已經把 Restful API 風格簡化成了一個方法,通過 DioMethod 來標明不同的請求方式。在我們平時開發(fā)的過程中,需要在請求前、響應前、錯誤時對某一些接口做特殊的處理,那我們就需要用到攔截器。 Dio 為我們提供了自定義攔截器功能,很容易輕松的實現(xiàn)對請求、響應、錯誤時進行攔截
我們發(fā)現(xiàn)雖然 Dio 框架已經封裝了一個 DioError 類庫,但如果需要對返回的錯誤進行統(tǒng)一彈窗處理或者路由跳轉等就只能自定義了
在我們發(fā)送請求的時候會碰到幾種情況,比如需要對非open開頭的接口自動加上一些特定的參數,獲取需要在請求頭增加統(tǒng)一的 token
在我們請求接口前可以對響應數據進行一些基礎的處理,比如對響應的結果進行自定義封裝,還可以針對單獨的 url 做特殊處理等。
我們看了轉換器的介紹,發(fā)現(xiàn)和攔截器的功能差不多,那為什么還要存在轉換器,有兩點:
執(zhí)行流程: 請求攔截器 請求轉換器 發(fā)起請求 響應轉換器 響應攔截器 最終結果 。
只會被用于 'PUT'、 'POST'、 'PATCH'方法,因為只有這些方法才可以攜帶請求體(request body)
會被用于所有請求方法的返回數據。
在開發(fā)過程中,客戶端和服務器打交道的時候,往往會用一個 token 來做校驗,因為每個公司處理刷新token的邏輯都不一樣,我這里舉一個簡單的例子
為什么我們需要有取消請求的功能,如果當我們的頁面在發(fā)送請求時,用戶主動退出當前界面或者app應用程序退出的時候數據還沒有響應,那我們就需要取消該網絡請求,防止不必要的錯誤。
由 服務器生成 的 一小段文本信息 ,發(fā)送給瀏覽器,瀏覽器把 cookie 以kv形式保存到本地 某個目錄下的文本文件內,下一次請求同一網站時會把該 cookie 發(fā)送給服務器。
cookie 的使用需要用到兩個第三方組件 dio_cookie_manager 和 cookie_jar
因為在我們平時的開發(fā)過程中,會碰到一種情況,在進行網絡請求時,我們希望能正常訪問到上次的數據,對于用戶的體驗比較好,而不是展示一個空白的頁面,該緩存主要是 《Flutter實戰(zhàn)》網絡接口緩存 提供參考。
我們在程序退出后內存緩存將會消失,所以我們用 shared_preferences 進行磁盤緩存數據。
在我們用flutter進行抓包的時候需要配置 Dio 代理。由 DefaultHttpClientAdapter 提供了一個 onHttpClientCreate 回調來設置底層 HttpClient 的代理。
用于驗證正在訪問的網站是否真實。提供安全性,因為證書和域名綁定,并且由根證書機構簽名確認。
日志打印主要是幫助我們開發(fā)時進行輔助排錯
最近在寫flutter應用,需要集成藍牙功能,用了一個第三方的庫,踩了一些坑,做一下記錄。
這是庫的地址 PhilipsHue/flutter_reactive_ble: Flutter library that handles BLE operations for multiple devices. (github.com)
安卓主要的坑就是,在使用藍牙功能的時候,需要獲取定位權限,這個需要動態(tài)獲取。
集成之后,編譯出錯,提示Swift Compiler Error。
(1)首先嘗試,修改對應三方庫的Swift編譯版本。
我這邊嘗試修改,沒有成功。
(2) 沒辦法,我這邊手動修改三方庫的源碼文件,進行修復。重新編譯成功。
其他問題