溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

在Flutter中嵌入Native組件的解決方法是什么

發(fā)布時間:2021-11-12 16:20:30 來源:億速云 閱讀:204 作者:iii 欄目:移動開發(fā)

本篇內(nèi)容介紹了“在Flutter中嵌入Native組件的解決方法是什么”的有關(guān)知識,在實(shí)際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

1. 使用教程

1.1. DemoRun

嵌入地圖這一場景可能在很多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)原理。

在Flutter中嵌入Native組件的解決方法是什么

1.2. AndroidView使用方式

AndroidView的使用方式和MethodChannel類似,比較簡單,主要分為三個步驟:

第一步:在dart代碼的相應(yīng)位置使用AndroidView,使用時需要傳入一個viewType,這個String將用于唯一標(biāo)識該Widget,用于和Native的View建立關(guān)聯(lián)。

在Flutter中嵌入Native組件的解決方法是什么

第二步:在native側(cè)添加代碼,寫一個PlatformViewFactory,PlatformViewFactory的主要任務(wù)是,在create()方法中創(chuàng)建一個View并把它傳給Flutter(這個說法并不準(zhǔn)確,但是我們姑且可以這么理解,后續(xù)會進(jìn)行解釋)

在Flutter中嵌入Native組件的解決方法是什么

第三步:使用registerViewFactory()方法注冊剛剛寫好的PlatformViewFactory,該方法需要傳入兩個參數(shù),第一個參數(shù)需要和之前在Flutter端寫的viewType對應(yīng),第二個參數(shù)是剛剛寫好的的PlatformViewFactory。

在Flutter中嵌入Native組件的解決方法是什么

配置高德地圖的部分這里就省略不說了,官方有比較詳細(xì)的文檔,可以去高德開發(fā)者平臺進(jìn)行查閱。

以上便是使用AndroidView的所有操作,總體看起來還是比較簡單的,但是真正要用起來,還是有兩個無法忽視的問題:

  1. View最終的顯示尺寸由誰決定?

  2. 觸摸事件是如何處理的?



2. 原理講解

想要解決上面的兩個問題,首先必須得理解所謂"傳View"的本質(zhì)是什么?

2.1. 所謂"傳View"的本質(zhì)是什么?

要解決這個問題,自然避免不了的需要去閱讀源碼,從更深的層面去看這個傳遞的整個過程,可以整理出一張這樣的流程圖:

在Flutter中嵌入Native組件的解決方法是什么

我們可以看到,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中嵌入Native組件的解決方法是什么

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ù)。

2.2. View最終的顯示尺寸由誰決定?

通過上面的流程大家應(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() {
        }
    };}

在Flutter中嵌入Native組件的解決方法是什么

首先先看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。

2.3. 觸摸事件如何傳遞

Android的事件流大家應(yīng)該都很熟悉了,自頂向下傳遞,自底向上處理或回流。Flutter同樣是使用這一規(guī)則,但是其中AndroidView通過兩個類來去處理手勢:

MotionEventsDispatcher:負(fù)責(zé)將事件封裝成Native的事件并向Native傳遞;

AndroidViewGestureRecognizer:負(fù)責(zé)識別出相應(yīng)的手勢,其中有兩個屬性:

在Flutter中嵌入Native組件的解決方法是什么

cachedEventsforwardedPointers,只有當(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í)用文章!

向AI問一下細(xì)節(jié)

免責(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)容。

AI