您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“在Flutter中嵌入Native組件的解決方法是什么”的有關(guān)知識,在實(shí)際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
嵌入地圖這一場景可能在很多App中都會存在,但是現(xiàn)在的地圖SDK都沒有提供Flutter的庫,而自己開發(fā)一套地圖顯然不太現(xiàn)實(shí)。這種場景下,使用混合棧的形式是一個比較好的選擇。我們可以直接在Native的繪圖樹中嵌入一個Map,但是這個方案嵌入的View并不在Flutter的繪圖樹中,是一種比較暴力且不優(yōu)雅的方式,使用起來也很費(fèi)勁。
這時候,使用Flutter官方提供的控件AndroidView就是一種比較優(yōu)雅的解決方案了。這里做了一個簡單的嵌入高德地圖的demo,就讓我們跟著這個應(yīng)用場景,看一下AndroidView的使用方式和實(shí)現(xiàn)原理。
AndroidView的使用方式和MethodChannel類似,比較簡單,主要分為三個步驟:
第一步:在dart代碼的相應(yīng)位置使用AndroidView,使用時需要傳入一個viewType
,這個String將用于唯一標(biāo)識該Widget,用于和Native的View建立關(guān)聯(lián)。
第二步:在native側(cè)添加代碼,寫一個PlatformViewFactory,PlatformViewFactory的主要任務(wù)是,在create()
方法中創(chuàng)建一個View并把它傳給Flutter(這個說法并不準(zhǔn)確,但是我們姑且可以這么理解,后續(xù)會進(jìn)行解釋)
第三步:使用registerViewFactory()
方法注冊剛剛寫好的PlatformViewFactory,該方法需要傳入兩個參數(shù),第一個參數(shù)需要和之前在Flutter端寫的viewType
對應(yīng),第二個參數(shù)是剛剛寫好的的PlatformViewFactory。
配置高德地圖的部分這里就省略不說了,官方有比較詳細(xì)的文檔,可以去高德開發(fā)者平臺進(jìn)行查閱。
以上便是使用AndroidView的所有操作,總體看起來還是比較簡單的,但是真正要用起來,還是有兩個無法忽視的問題:
View最終的顯示尺寸由誰決定?
觸摸事件是如何處理的?
想要解決上面的兩個問題,首先必須得理解所謂"傳View"的本質(zhì)是什么?
要解決這個問題,自然避免不了的需要去閱讀源碼,從更深的層面去看這個傳遞的整個過程,可以整理出一張這樣的流程圖:
我們可以看到,F(xiàn)lutter最終拿到的是native層返回的一個textureId。根據(jù)native的知識ky h這個textureId是已經(jīng)在native側(cè)渲染好了的view的繪圖數(shù)據(jù)對應(yīng)的ID,通過這個ID可以直接在GPU中找到相應(yīng)的繪圖數(shù)據(jù)并使用,那么Flutter是如何去利用這個ID的呢?
在之前的 深入了解Flutter界面開發(fā)中,也給大家介紹了Flutter的繪圖流程。我這里也給大家再簡單整理一下
Flutter的Framework層最后會遞交給Engine層一個layerTree,在管線中會遍歷layertree的每一個葉子節(jié)點(diǎn),每一個葉子節(jié)點(diǎn)最終會調(diào)用Skia引擎完成界面元素的繪制,在遍歷完成后,在調(diào)用glPresentRenderBuffer(IOS)或者glSwapBuffer(Android)按完成上屏操作。
Layer的種類有很多,而AndroidView則使用的是其中的TextureLayer。TextureLayer在之前的
《Flutter外接紋理》中有更為詳細(xì)的介紹,這里就不再贅述。TextureLayer在被遍歷到時,會調(diào)用一個engine層的方法SceneBuilder::addTexture()
將textureId作為參數(shù)傳入。最終在繪制的時候,skia會直接在GPU中根據(jù)textureId找到相應(yīng)的繪制數(shù)據(jù),并將其繪制到屏幕上。
那么是不是誰拿到這個ID都可以進(jìn)行這樣的操作呢?答案當(dāng)然是否定的,Texture數(shù)據(jù)存儲在創(chuàng)建它的EGLContext對應(yīng)的線程中,所以如果在別的線程進(jìn)行操作是無法獲取到對應(yīng)的數(shù)據(jù)的。這里需要引入幾個概念:
顯示屏對象(Display):提供合理的顯示器的像素密度和大小的信息
Presentation:它給Android提供了在對應(yīng)的上下文(Context)和顯示屏對象(Display)上繪制的能力,通常用于雙屏異顯。
這里不展開講解Presentation,我們只需要明白Flutter是通過Presentation實(shí)現(xiàn)了外接紋理,在創(chuàng)建Presentation時,傳入FlutterView對應(yīng)的Context和創(chuàng)建出來的一個虛擬顯示屏對象,使得Flutter可以直接通過ID找到并使用Native創(chuàng)建出來的紋理數(shù)據(jù)。
通過上面的流程大家應(yīng)該都能想到,顯示尺寸看起來像是由兩部分決定的:AndroidView的大小,Android端View的大小。那么實(shí)際上到底是有誰來決定的呢,讓我們來做一個實(shí)驗(yàn)?
直接新建一個Flutter工程,并把中間改成一個AndroidView。
//Flutterclass _MyHomePageState extends State<MyHomePage> { double size = 200.0; void _changeSize() { setState(() { size = 100.0; }); } @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text(widget.title), ), body: Container( color: Color(0xff0000ff), child: SizedBox( width: size, height: size, child: AndroidView( viewType: 'testView', ), ), ), floatingActionButton: new FloatingActionButton( onPressed: _changeSize, child: new Icon(Icons.add), ), ); }}
在Android端也要加上對應(yīng)的代碼,為了更好地看出裁切效果,這里使用ImageView。
//Android@Overridepublic PlatformView create(final Context context, int i, Object o) { final ImageView imageView = new ImageView(context); imageView.setLayoutParams(new ViewGroup.LayoutParams(500,500)); imageView.setBackground(context.getResources().getDrawable(R.drawable.idle_fish)); return new PlatformView() { @Override public View getView() { return imageView; } @Override public void dispose() { } };}
首先先看AndroidView,AndroidView對應(yīng)的RenderObject是RenderAndroidView,而一個RenderObject的最終大小的確定是存在兩種可能,一種是由父節(jié)點(diǎn)所指定,還有一種是在父節(jié)點(diǎn)指定的范圍中根據(jù)自身情況確定大小。打開對應(yīng)的源碼,可以看到其中有個很重要的屬性sizedByParent = true
,也就是說AndroidView的大小是由其父節(jié)點(diǎn)所決定的,我們可以使用Container、SizedBox等控件控制AndroidView的大小。
AndroidView的繪圖數(shù)據(jù)是Native層所提供的,那么當(dāng)Native中渲染的View的實(shí)際像素大小大于AndroidView的大小時,會發(fā)生什么呢?通常情況下,這種情況的處理思路無非就兩種選擇,一種是裁切,另一種是縮放。Flutter保持了其一貫的做法,所有out of the bounds的Widget統(tǒng)一使用裁切的方式進(jìn)行展示,上面所描述的情況就被當(dāng)作是一種out of the bounds。
當(dāng)這個View的實(shí)際像素大小小于AndroidView的時候,會發(fā)現(xiàn)View并不會相應(yīng)地變小(Container的背景色并沒有顯露出來),沒有內(nèi)容的地方會被白色填充。這其中的原因是SingleViewPresentation::onCreate中,會使用一個FrameLayout作為rootView。
Android的事件流大家應(yīng)該都很熟悉了,自頂向下傳遞,自底向上處理或回流。Flutter同樣是使用這一規(guī)則,但是其中AndroidView通過兩個類來去處理手勢:
MotionEventsDispatcher:負(fù)責(zé)將事件封裝成Native的事件并向Native傳遞;
AndroidViewGestureRecognizer:負(fù)責(zé)識別出相應(yīng)的手勢,其中有兩個屬性:
cachedEvents
和forwardedPointers
,只有當(dāng)PointerEvent的pointer屬性在forwardedPointers中時才會去進(jìn)行分發(fā),否則會存在cacheEvents中。這里的實(shí)現(xiàn)主要是為了解決一些事件的沖突,比如滑動事件,可以通過gestureRecognizers來進(jìn)行處理,這里可以參考官方注釋。
/// For example, with the following setup vertical drags will not be dispatched to the Android view as the vertical drag gesture is claimed by the parent [GestureDetector]./// /// GestureDetector(/// onVerticalDragStart: (DragStartDetails d) {},/// child: AndroidView(/// viewType: 'webview',/// gestureRecognizers: <OneSequenceGestureRecognizer>[],/// ),/// )/// /// To get the [AndroidView] to claim the vertical drag gestures we can pass a vertical drag gesture recognizer in [gestureRecognizers] e.g:/// /// GestureDetector(/// onVerticalDragStart: (DragStartDetails d) {},/// child: SizedBox(/// width: 200.0,/// height: 100.0,/// child: AndroidView(/// viewType: 'webview',/// gestureRecognizers: <OneSequenceGestureRecognizer>[ new VerticalDragGestureRecognizer() ],/// ),/// ),/// )
“在Flutter中嵌入Native組件的解決方法是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。