【译】Flutter PlatformView: 如何通过原生view创建widget

6,089 阅读4分钟

原文来自 flutter community

medium.com/flutter-com…)

原由

最近想要实现一个flutter地图插件,但是flutter只提供了基于google map的官方插件,所以想要使用高德地图和百度地图自己做一个,就看到了这篇文章,顺便翻译一下分享给大家。

这里先贴出google map插件文章以便有梯子的同学可以直接使用 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并且实现PlatformViewMethodCallHandler以至于我们可以将原生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。有一个TextViewContainer包裹,这个container组件作为一个垂直排列组件的第一个子组件,第二个子组件是一个flutter Text

我们同样实现了方法onTextViewCreated,在这个方法里面去调用setText

这个例子证明了可以对任何原生Android view转换成flutterwidget,并且像其他flutter组件一样绘制和转换。