前言
目前pub上關(guān)于webview有兩個點贊最多的插件策治,
webview_flutter 和 flutter_webview_plugin
經(jīng)過一番比較選擇了后者:flutter_webview_plugin次绘,這里將記錄寫出來,希望對你有所幫助
兩者區(qū)別
webview_flutter :
flutter官方開發(fā)維護(hù),采用的platformView顯示。
受flutter端控制(在樹內(nèi))娩贷,對于頁面過渡動畫是可協(xié)調(diào),受控制的锁孟。
flutter_webview_plugin :
flutter 社區(qū)開發(fā)維護(hù)彬祖,采用的是原生端添加渲染的方式。
因為是原生端繪制罗岖,不在flutter 樹內(nèi)涧至,不受其控制,顯示和隱藏是需要methodChannel進(jìn)行通知的桑包。
看起來前者要比后者靈活方便,但是唯一也是最嚴(yán)重的扣分項就是性能問題 :
webview_flutter 的性能要明顯弱于 flutter_webview_plugin纺非,其所造成的卡頓是肉眼可見哑了,不需要看什么fps、dumpsy啥的...尤其是稍微復(fù)雜一些的頁面烧颖。
基于此我選擇了flutter_webview_plugin弱左,當(dāng)然它也有不足。
flutter_webview_plugin
遇到的問題
由于其本身是采用原生端渲染(以安卓為例炕淮,是通過addContentView(webview)),因此其不在flutter 的widget樹內(nèi)拆火,也就無從談起flutter對其的控制了。
那么當(dāng)我們的頁面采用了過渡動畫,如滑動進(jìn)入/退出们镜,由于flutter 頁面在沒有走完過渡動畫時币叹,是不會真正退出的(走dispose),而插件的顯隱和釋放是在頁面的dispose中才進(jìn)行的模狭,這就導(dǎo)致了颈抚,背景雖然滑出去了(或者漏出了上層頁面),但是webview的內(nèi)容依然殘留了一會才消失嚼鹉。
問題演示
問題分析
查看了flutter_webview_plugin的源碼贩汉,它的ui結(jié)構(gòu)和運行流程如下圖
代碼大致結(jié)構(gòu)
class _WebviewScaffoldState extends state{
widget build(){
return Scaffold(
body:_WebviewPlaceholder(
onRectChanged:(Rect rect){
webviewReference.launch(
rect:rect
...
);
}
)
);
}
}
在創(chuàng)建的renderBox的paint方法調(diào)用后,就會回調(diào)onRectChanged 這個方法并攜帶顯示區(qū)域rect锚赤,然后通過
webviewReference.launch 啟動原生端的view添加繪制匹舞,繪制區(qū)域基于所傳的rect。
webviewReference extends FlutterWebviewPlugin 這個類是一個通信類线脚,
這個類還對外暴露了一個resize方法用于在rect改變時進(jìn)行相應(yīng)的調(diào)整
/// resize webview
Future<Null> resize(Rect rect) async {
final args = {};
args['rect'] = {
'left': rect.left,
'top': rect.top,
'width': rect.width,
'height': rect.height,
};
await _channel.invokeMethod('resize', args);
}
經(jīng)過上面的分析策菜,只要我們改動這個rect就可以改變webview的顯示位置和大小。
首先我想到的是對頁面做動畫的PageRouteBuilder;
初版解決方案
經(jīng)過對PageRouteBuilder這類的源碼一層一層分析后
PageRouteBuilder 嵌套極多酒贬,同時我還捎帶了看了一下push方法又憨,所得的大致的流程圖我放在文章結(jié)尾,有興趣的可以看一下
發(fā)現(xiàn)通過builder.animation可以對過渡動畫進(jìn)行監(jiān)聽
SlideRightRouteBuilder builder = SlideRightRouteBuilder(ComplexPage());
Navigator.of(context).push(builder);
///要放在push后面锭吨,不然報錯蠢莺,原因見文章末尾的流程圖
builder.animation.addListener(() {
});
那么我給ComplexPage傳入一個 key,通過這個獲取context零如,進(jìn)而取到它的offset,然后在回調(diào)函數(shù)中執(zhí)行以下操作
final RenderBox box = context.findRenderObject();
final Offset offset = box.localToGlobal(Offset.zero);
這個offset就是包裹webview的那個父widget躏将,它是在widget樹上,受動畫控制的考蕾,換言之隨著動畫的進(jìn)行祸憋,這個offset也會變化。
之后我們只需要調(diào)用
webviewReference.resize(_rect.shift(offset));
在這個過程中肖卧,因為builder和resize分別在不同的widget(頁面)蚯窥,只能通過各種接口傳輸/調(diào)用,這樣就發(fā)生了嚴(yán)重的耦合塞帐,在考慮需要兼容 滑動/縮放動畫拦赠,并pr到插件倉庫后,便直接放棄了這個方法葵姥。
終版解決方案-兼容滑動/縮放
重新思考荷鼠,發(fā)現(xiàn)對于builder的依賴,只是對animation的監(jiān)聽榔幸,并觸發(fā)重繪(resize)允乐,對于進(jìn)度值矮嫉,完全可以通過其他方法解決。所以便有了下面的方案牍疏。
首先我在插件的通信類FlutterWebviewPlugin,定義了支持的過渡到動畫類型
/// the transition animation type of page on/off screen
enum TransitionType{
Non,
Slide,
Scale
}
之后在插件WebviewScaffold的構(gòu)造函數(shù)中增加了對應(yīng)的參數(shù)
final TransitionType transitionType;
在state的initState()中調(diào)用我創(chuàng)建的方法:
perceptionPageTransition();
/// coordinate the webview rect whit page's transition
void perceptionPageTransition(){
if(widget.transitionType != TransitionType.Non){
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
//avoid to concurrent modification exception
WidgetsBinding.instance.addPersistentFrameCallback((timeStamp) {
if(context != null){
driveWebView();
}
});
});
}
}
通過上面這個方法蠢笋,我就可以模擬出builder.animation的監(jiān)聽了。再看driveWebView()方法
void driveWebView(){
final RenderBox box = context.findRenderObject();
final Offset offset = box.localToGlobal(Offset.zero);
//獲得可繪制的大小
final Size size = box.size;
//獲得可繪制的區(qū)域
final Rect rect = box.paintBounds;
//當(dāng)變動位置等于繪制區(qū)域的位置時麸澜,說明動畫已經(jīng)執(zhí)行完畢挺尿,直接退出,避免過度繪制
if(offset.dx == rect.left)return;
//這個值用于縮放動畫
//根據(jù)當(dāng)前位置的dx值/除以size的寬度炊邦,就可以計算出動畫進(jìn)度value
final double value = offset.dx/size.width;
//根據(jù)傳入的動畫類型编矾,對rect進(jìn)行位移或者縮放
switch(widget.transitionType){
case TransitionType.Slide:
webviewReference.resize(_rect.shift(offset));
break;
case TransitionType.Scale:
final double www = box.size.width*(value*2);
final double hhh = box.size.height*(value*2);
webviewReference.resize(Rect.fromLTWH(offset.dx,offset.dy,size.width-www , size.height-hhh));
break;
case TransitionType.Non:
// TODO: Handle this case.
break;
}
}
這樣我們就完成了初版的功能,同時使插件和項目進(jìn)行了解耦馁害。
效果圖
debug模式下窄俏,第一次過度會有錯位之后正常
性能模式和release 則完全正常
分析時記錄的一些流程圖
.push()
[圖片上傳失敗...(image-8f4dbc-1598493593844)]
pageRouteBuilder
結(jié)語
希望以上對你有所幫助,如果不足之處歡迎指出碘菜,喜歡的點個贊撒 ;)
該項目已經(jīng)PR凹蜈,大佬正在幫忙審核,不知道什么時候合并忍啸。
我的其它文章
Bedrock——基于MVVM+Provider的Flutter快速開發(fā)框架