Flutter很好用,but……

lwlizhe發表於2019-10-08

前言

為什麼有這篇文章?

這篇文章可以說是個人Flutter問題隨筆吧,雖然Flutter可以方便的做到跨平臺,但是畢竟是新專案,功能沒那麼全面也是意料之中,所以在此記錄跟蹤一下目前在使用Flutter過程中遇到的問題,目前的解決方案,並跟蹤記錄一下。

問題列表(持續更新):

  1. Flutter 中不支援非同步測繪大量文字
  2. Flutter IOS 後臺音訊播放
  3. Flutter 中 NestedScrollView 中存在摺疊頭、子列表等情況下的滑動等異常
  4. Hero 跳轉之後的圖片如果在摺疊佈局中被摺疊隱藏了,那麼Hero的退出動畫無法正常播放,並且原圖片無法顯示(flutter的master分支已修復)
  5. listView 不支援按index跳轉,而且無法獲取未展示區域的位置

正文

1. Flutter 中不支援非同步測繪大量文字

這個源自於個人練手APP,在開發閱讀模組過程中遇到了這麼一個問題:

如果想要自定義文字間距,段落間距等,那麼自然就要使用textPainter自己繪製文字。但是由此引發出一個問題:如果有上萬個文字,那麼測繪時間會很長。自然會阻塞主程式。

解決這個問題,第一個想法自然就是使用非同步處理,那麼flutter 中的非同步方案有哪些呢?

  1. Future(包括await async那些東西)
  2. isoloate

按照官方文件,大量計算東西應該放到Isolate中,所以,自然而然地,我們將textPinter測繪方法放到Isolate中執行,但是呢,執行報錯:native function not found

原因就是UI包的類只允許在Flutter的UI Isolate中執行。

此題無解,完結撒花,各回各家,各找各媽(才怪)

解決方案一:Plugin

這個方法的思路來自於官方開發人員的回答:

right now you would have to write your own line-breaking logic - perhaps as a plugin. We don't currently have any API for doing text layout in a separate isolate. @GaryQian might have some other idea about this too.

所以說,我們看下官方的Plugin是怎麼實現的。

在 Plugin工程中,有這麼一個: android_alarm_manager ,這個Plugin中的AlarmService.java檔案中,有這麼一個方法:startBackgroundIsolate(要素察覺+1)

通過閱讀原始碼,我們得知,其實說白了就是新建一個FlutterNativeView ,這個FlutterNativeView 不負責渲染介面,只負責當後臺執行緒來用。同樣的道理,我們也可以用同樣的方法來處理我們的需要執行在UI Isolate的非同步任務。(至於為什麼新建一個FlutterNativeView ,就能實現這個需求,粗略的說下是因為,每次新建FlutterNativeView,其實都會新建一些TaskRunner,其中就包含UI 的TaskRunner,總之有興趣的可以搜下Flutter中的TaskRunner機制瞭解下)

不過,其實不用自己實現註冊,有這麼一個第三方:flutter_isolate可以做到這點,用法跟普通的isolate差不多,所以解決方式也很簡單:找到原來使用Isolate的那段程式碼,把Isolate.spawn替換為FlutterIsolate.spawn即可。不過經過測試,在1.10版本上會報錯,所以想使用這個方式的話,還是切回stable 分支使用吧。

解決方案二:分割為多個小任務(個人感覺不太推薦這種方式,感覺不可控,畢竟假如小任務的執行時間也很長咋辦)

這種方式說白了就是每測繪完一行,就提交一次,而不是整體測繪完之後再提交結果。

舉個小例子:

Future(() async {
  var result;
  for (var i = 0; i < 1000000; ++i) {
    result = 'result is $i';
    await Future.delayed(Duration.zero);
  }
  print(result);
});
複製程式碼

用這種方式計算就不會卡UI

解決方案三:目前這個問題已經加入到里程碑了,在未來的某天應該就有解決方案

github.com/flutter/flu…

2. Flutter IOS 後臺音訊播放

也就是像音樂播放器那樣,會有個通知顯示進度,即使在桌面也可以播放音樂這種

目前只有Android的實現,IOS這塊目前還沒有方案……%

3. Flutter 中 NestedScrollView 中存在摺疊頭、子列表等情況下的滑動等異常

找了半天才發現有個大佬已經搞定了這個問題:

juejin.im/post/5bea43…

使用extended_nested_scroll_view即可解決這個問題,在此記錄一下

4. Hero 跳轉之後的圖片如果在摺疊佈局中被摺疊隱藏了,那麼Hero的退出動畫無法正常播放,並且原圖片無法顯示

這個描述可能有些抽象,具體情況以一個例子為例描述:

一、首頁放一個圖片,給他設定點選跳轉詳情頁,並設定hero動畫。

二、將圖片設定為SliverAppBar的FlexibleSpaceBar的widget中的內容,然後上拉頁面,摺疊隱藏掉SliverAppBar。

三、按後退鍵退出詳情頁,觸發hero的關閉動畫。

按這三步執行之後,首頁原來那張圖片就變成空白,而且無法點選。

issue:

github.com/flutter/flu…

github.com/flutter/flu…

所以切換到master分支或者dev分支(問題修復版本v.1.10.14),或者降低穩定版的版本。

5. listView 不支援按index跳轉,而且無法獲取未展示區域的位置

下面是我搜尋出的內容:

stackoverflow.com/questions/5…

pub.dev/packages/in…

pub.dev/packages/sc…

pub.dev/packages/fl…

上面的實現方法各有千秋吧,整理一下主要的缺陷點,以後可以根據實際情況使用:

  1. stackOverflow 中提到了兩種方法:一是使用index_list_view,這個稍後統一分析。二是 gist.github.com/debuggerx01… ,不過有老哥將它放在帶評論的列表上,結果效能比較差,簡介文章中也講明瞭,如果處理不好確實可能存在效能問題。
  2. indexed_list_view stackOverflow中也提了,這個適用於無限列表的情況。
  3. scroll_to_index 程式碼稍微有點侵入性(其實這個不是重點),其次沒有jump方法,scroll時長好像也沒什麼效果,所以如果列表比較大而跳轉距離也很大(比如說凡人修仙傳的章節那種……),那麼動畫效果比較感人。
  4. flutter_widgets 谷歌大大的產品(不過不是官方正式控制元件),基本滿足需求,方法也很簡單,jump方法的什麼也有。但是,如果把它放到NestScrollView中搞視差滑動的頁面的時候,會導致整體無法滑動,只能滑動ScrollablePositionedList中的內容……(其實原因就是因為自定義了controller),而且如果存在ExpansionTile這種可伸縮的佈局,通過伸縮方式改變了item的高度,ScrollablePositionedList並不會更新高度,而是還是以改變前高度為準進行index跳轉。

現在正在嘗試解決ScrollablePositionedList中的視差滑動問題,(得想個辦法讓NestScrollView屆的到ScrollablePositionedList的controller) 在NestScrollView中是通過設定PrimaryScrollController,讓innerController管理body滑動的,所以使用這個innerController來替代ScrollablePositionedList的controller即可解決無法滑動的問題,但是隨之而來的是一個新的問題,現在按index跳轉,會導致body觸發滑動,進而直接摺疊掉SliverAppBar……………如何讓index跳轉的時候,body在NestScrollView中的位置不動,只在原地實現自身的index跳轉,只有手動滑動的時候才允許改變body在NestScrollView中的位置,目前想法是在SliverAppBar的floating、pinned等控制滑動方式的方法上看看有沒有突破口。 不搞了,現在挺好的。直接滿屏展示,實在不行加個跳轉到頂部按鈕,我感覺蠻合理的。(大霧)

改變高度這個,目前採用設定addPersistentFrameCallback 方式持續監聽。本以為應該效能比較差,但是測試了一下,好像frame沒啥變化,所以應該沒啥問題。

題外話

有些同學可能對我為什麼需要測量上萬字感到困惑,所以在這簡單的提下:

測繪上萬字這個情況來自於我想把自己原來的一個Android原生小說閱讀器,轉換為flutter實現(主要是想實現那幫模擬、滾動等翻頁動畫啥的,練習一下flutter的canvas操作),小說閱讀器的基本思路參考自這個專案:任閱

思路基本是這樣的:

基本功能是:上一頁,下一頁,上一章,下一章,章內跳頁。

因為要考慮上一章下一章切換以及章內跳轉到多少頁的這些功能,所以自然以章為基礎單位,章內部包含頁,取的時候把章的資料拿出來,然後從章中頁資料中取出要展示的頁的資料,並將其繪製到canvas上。

所以,快取計算也是計算的章的內容,所以當從伺服器下載下來新的章的時候,就有可能遇到上萬字的章的情況。

可能也有同學說:直接按頁快取不就行了,每次只快取幾頁的內容,那肯定不會遇到需要一次性測量上萬字的情況。但是如果純粹按頁快取的話,如果遇到這兩種情況就傻眼了:1. 如果有個使用者開啟一本新的小說,直接跳到第十章,然後按了上一頁。 2. 章節內跳轉到第n頁(n>快取頁數)。

因為標點符號、全形、特殊字元等非普通漢字的存在,所以我們是無法直接得知每頁展示的內容,自然無法在測量完整章之前得知某頁的展示內容,所以要支援上面提到2個情況,必然要測量分頁整章內容。

當然,可能我這個思路也不是最優解,如果有更好的方案,希望各位不吝指教

相關文章