【譯】Flutter PlatformView: 如何通過原生view建立widget
最近想要實現一個flutter地圖外掛,但是flutter只提供了基於google map的官方外掛,所以想要使用高德地圖和百度地圖自己做一個,就看到了這篇文章,順便翻譯一下分享給大家。
這裡先貼出google map外掛文章以便有梯子的同學可以直接使用 ofollow,noindex">Exploring Google Maps in Flutter
譯文

Flutter最近剛剛有了一個新的元件叫做 PlatformView
,它允許開發者在flutter裡面嵌入Android原生的view。這一開放舉措為實現注入地圖和webview提供了許多新的可能。( github.com/flutter/flu… ).
在這篇教程裡,我們將探索如何建立一個 TextViewPlugin
,在plugin裡我們會暴露一個android原生TextView作為flutter元件。
在進入程式碼實現之前需要注意以下幾點:
- 目前只支援Android(作者文章釋出於2018.9.7, 目前已經支援ios )。
- 需要android api版本在20及以上。
- 嵌入Android views是一個昂貴的操作,所以應當避免在flutter能夠實現的情況下去使用它。
- 嵌入Android view的繪製和其他任何flutter widget一樣,view的轉換也同樣使用。
- 元件會撐滿所有可獲得控制元件,因此它的父元件需要提供一個佈局邊界。
- 需要切換到flutter的master分支上使用(目前已經不需要)
下面開始實現部分
第一步就是建立一個flutter plugin專案。


在flutter plugin專案建立完成之後在 ./lib/text_view.dart
建立TextView類。
import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; typedef void TextViewCreatedCallback(TextViewController controller); class TextView extends StatefulWidget { const TextView({ Key key, this.onTextViewCreated, }) : super(key: key); final TextViewCreatedCallback onTextViewCreated; @override State<StatefulWidget> createState() => _TextViewState(); } class _TextViewState extends State<TextView> { @override Widget build(BuildContext context) { if (defaultTargetPlatform == TargetPlatform.android) { return AndroidView( viewType: 'plugins.felix.angelov/textview', onPlatformViewCreated: _onPlatformViewCreated, ); } return Text( '$defaultTargetPlatform is not yet supported by the text_view plugin'); } void _onPlatformViewCreated(int id) { if (widget.onTextViewCreated == null) { return; } widget.onTextViewCreated(new TextViewController._(id)); } } class TextViewController { TextViewController._(int id) : _channel = new MethodChannel('plugins.felix.angelov/textview_$id'); final MethodChannel _channel; Future<void> setText(String text) async { assert(text != null); return _channel.invokeMethod('setText', text); } } 複製程式碼
一個需要重點注意的是當我們在上面程式碼第24行建立了 AndroidView
,我們需要提供一個 viewType
,會在稍後介紹。
我們還提供了一個 onPlatformCompleted
實現,以便我們可以為 TextView
小部件提供一個 TextViewController
,然後可以使用它來使用 setText
方法更新它的文字。
完整的 AndroidView
文件請見 docs.flutter.io/flutter/wid…
接下來我們需要實現Android部分的 TextViewPlugin
。
我們開啟另一個生成檔案 ./android/src/main/java/{organization_name}/TextViewPlugin.java
,用一下內容替換檔案內的內容。
package angelov.felix.textview; import io.flutter.plugin.common.PluginRegistry.Registrar; public class TextViewPlugin { public static void registerWith(Registrar registrar) { registrar .platformViewRegistry() .registerViewFactory( "plugins.felix.angelov/textview", new TextViewFactory(registrar.messenger())); } } 複製程式碼
我們需要做的就是實現 registerWith
方法,傳入 text_view.dart
中定義的 viewType
,同時提供一個 TextViewFactory
,它將會建立原生 TextView
作為 PlatformView
。
接著我們需要建立 ./android/src/main/java/{organization_name}/TextViewFactory.java
,並繼承 PlatformViewFactory
package angelov.felix.textview; import android.content.Context; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.StandardMessageCodec; import io.flutter.plugin.platform.PlatformView; import io.flutter.plugin.platform.PlatformViewFactory; public class TextViewFactory extends PlatformViewFactory { private final BinaryMessenger messenger; public TextViewFactory(BinaryMessenger messenger) { super(StandardMessageCodec.INSTANCE); this.messenger = messenger; } @Override public PlatformView create(Context context, int id, Object o) { return new FlutterTextView(context, messenger, id); } } 複製程式碼
TextViewFactory
實現了create方法,它會返回 PlatformView
,在我們的例子裡叫做 FlutterTextView
。
然後,建立 ./android/src/main/java/{organization_name}/FlutterTextView.java
並且實現 PlatformView
和 MethodCallHandler
以至於我們可以將原生view繪製到flutter元件並且通過 MethodChannel
從dart接收資料。
package angelov.felix.textview; import android.content.Context; import android.view.View; import android.widget.TextView; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import static io.flutter.plugin.common.MethodChannel.MethodCallHandler; import static io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.platform.PlatformView; public class FlutterTextView implements PlatformView, MethodCallHandler{ private final TextView textView; private final MethodChannel methodChannel; FlutterTextView(Context context, BinaryMessenger messenger, int id) { textView = new TextView(context); methodChannel = new MethodChannel(messenger, "plugins.felix.angelov/textview_" + id); methodChannel.setMethodCallHandler(this); } @Override public View getView() { return textView; } @Override public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) { switch (methodCall.method) { case "setText": setText(methodCall, result); break; default: result.notImplemented(); } } private void setText(MethodCall methodCall, Result result) { String text = (String) methodCall.arguments; textView.setText(text); result.success(null); } @Override public void dispose() {} } 複製程式碼
FlutterTextView
建立了一個新的 TextView
並且設定了一個 MethodChannel
,因此TextView可以從dart程式碼接收資料進行檢視更新。
為了實現 PlatformView
,我們需要重寫 getView
方法返回textview物件,同時重寫 dispose
方法。
為了實現 MethodCallHandler
,需要重寫 onMethodCall
,在接收到 setText 呼叫指令時候更新TextView,或者在不支援的其他指令情況下返回 result.notImplemented
。
現在可以測試我們的新 TextView
元件。
開啟 ./example/lib/main.dart
用下面的程式碼替換。
import 'package:flutter/material.dart'; import 'package:text_view/text_view.dart'; void main() => runApp(MaterialApp(home: TextViewExample())); class TextViewExample extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Flutter TextView example')), body: Column(children: [ Center( child: Container( padding: EdgeInsets.symmetric(vertical: 30.0), width: 130.0, height: 100.0, child: TextView( onTextViewCreated: _onTextViewCreated, ))), Expanded( flex: 3, child: Container( color: Colors.blue[100], child: Center(child: Text("Hello from Flutter!")))) ])); } void _onTextViewCreated(TextViewController controller) { controller.setText('Hello from Android!'); } } 複製程式碼
程式碼看著很熟悉,我們執行一個 MaterialApp
,最外層用 Scaffold
。有一個 TextView
用 Container
包裹,這個container元件作為一個垂直排列元件的第一個子元件,第二個子元件是一個flutter Text
。
我們同樣實現了方法 onTextViewCreated
,在這個方法裡面去呼叫 setText
。

這個例子證明了可以對任何原生Android view轉換成flutterwidget,並且像其他flutter元件一樣繪製和轉換。