Flutterの最後の秘宝、Platform Viewの実装を調べに行く

この記事は[Flutter Advent Calendar 2018](https://qiita.com/advent-calendar/2018/flutter)の10日目の記事です。

# まえおき

Flutter 1.0のリリースがアナウンスされましたね、めでたい！！ :confetti_ball:  :congratulations: :tada: 

さて、Flutter Liveで ~~は紹介されていなかったようですが~~ [^misstake]も紹介されていたようですが、[Google Developers Blogのポスト](https://developers-jp.googleblog.com/2018/12/flutter-10-googles-portable-ui-toolkit.html)を見ていると、記事の中ほどに `プラットフォーム ビュー` [^blogpost]という見出しとその説明があります。
今までAndroidでしか実現できていなかったのが、いよいよiOSでも使用できるようになった、ということのようです。

[^misstake]: [コメントをいただきました](https://qiita.com/kikuchy/items/397b7664ad32656748bf#comment-756779a8b2e145d1ecd1)。私が見逃していただけだったようです…
[^blogpost]: [原文](https://developers.googleblog.com/2018/12/flutter-10-googles-portable-ui-toolkit.html)では `Platform views`

[私がいま所属している会社的にはこの機能のおかげで大変救われました。](https://qiita.com/kikuchy/items/09bcc8e4c7fd691e1a9c#%E6%AC%A1%E3%81%AE%E6%89%8B%E6%AE%B5%E6%A1%88)

それはそうとして、この機能、どう実現されているのでしょうか？
気になりませんか？　気になりますよね？　ね？

# Platform View とは

`Platform View` とは、FlutterのWidgetツリーの中に、AndroidやiOSと言ったプラットフォームのViewを表示する機能、
またはそれを行うFlutterのWidgetとプラットフォーム側で実装するViewを指します。


通常、Flutterのアプリケーション中にプラットフォームのViewを表示することはできません。[^cantshowview]
Flutterが全ての画面を自前で描画しているためです。
この自前で描画を行う戦略のおかげで、ワンソースでマルチプラットフォームを対応できているのですが、プラットフォーム固有のViewを表示したいケースでは困ることとなります。

[^cantshowview]: [flutter_webview_plugin](https://github.com/fluttercommunity/flutter_webview_plugin)のように、Flutterの画面を保持しているプラットフォームのViewより前面に、出したいプラットフォームのViewを配置する、という荒業はあります。が、Flutterの画面が隠れてしまうので色々問題になるわけです。

どうしてもプラットフォームの機能を使用したい場合、通常はPluginを作成してプラットフォームの機能を呼び出します。
ところがViewに関しては、上記の事情でそうも行かなかった、という訳です。

Platform Viewは、今まで手が出なかった**プラットフォームのViewの外観と機能**を使用できるようにしてくれるものです。


## 使い方

この記事の趣旨ではないのでごく軽い説明に留めます。

Pluginを作成するのと似た要領です。
以下のものを用意してがっちゃんこします。

* Dart
    * PlatformViewを表示するStatefulWidgetを作成
    * Android向けには `AndroidView`, iOS向けには `UIKitView` を作成して `build()` の戻り値としてreturn
* Android
    * 表示したいViewを生成する、`PlatformView` を実装したクラスを用意
    * `PlatformView` 実装クラスをインスタンス化する、 `PlatformViewFactory` を実装したクラスを用意
    * plugin内で、`PlatformViewFactory`実装クラスを登録
* iOS
    * 表示したいUIViewを生成する `FlutterPlatformView` プロトコルに準拠したクラスを用意
    * `FlutterPlatformView` 準拠クラスをインスタンス化する、 `FlutterPlatformViewFactory` プロトコル準拠クラスを用意
    * plugin内で、 `FlutterPlatformViewFactory` 準拠クラスを登録


Androidでの作り方はこちらに解説があるので、読んでみてください。

* [Flutter PlatformView: How to create Widgets from Native Views](https://medium.com/flutter-community/flutter-platformview-how-to-create-flutter-widgets-from-native-views-366e378115b6)

より実践的な作り方は[`webview_flutter` の実装](https://github.com/flutter/plugins/tree/master/packages/webview_flutter)が参考になると思います。

# Platform View一番の謎

Platform Viewで一番の謎…それは

**いかにしてプラットフォームのViewの描画をFlutterの世界に取り込んでいるか**

ですね！

Android/iOS共に、Flutterで作成したアプリケーションは、Activity/UIViewControllerの上に `FlutterView` という、自前描画した画面内容を書き出すためのViewをひとつだけ持った構成になっています。

![FlutterView.png](https://qiita-image-store.s3.amazonaws.com/0/41896/ea0d6c05-619f-f03e-c2e5-812d053e80b3.png)



この `FlutterView` の上にプラットフォームのViewを重ねてしまうとFlutterの世界が見えなくなってしまいます。
そのため、**Widgetツリー内にViewの内容を表示する**という要求を満たすためには、どうしても `FlutterView` より前面にViewを配置するわけには行かないのです。

この制限を回避するため、各プラットフォームではまたトリッキーな実装が行われていました。

## Android

`VirtualDisplay` というクラスが使用されています。

* https://developer.android.com/reference/android/hardware/display/VirtualDisplay

つまり、仮想的なディスプレイを作り、そこに目的のViewを配置することで、その表示内容をFlutterの世界に転送する、という手段を取っています。


図にすると大体こんな感じ。
（コード全部は追いきれていないので、大体こんな感じ、というものです。間違っていたらコメント等で教えてください）

![Untitled (2).png](https://qiita-image-store.s3.amazonaws.com/0/41896/2d06bf86-6c29-ce18-401d-2be714a45c45.png)


ソースコードはFlutterのEngineリポジトリに入っています。

* https://github.com/flutter/engine/tree/master/shell/platform/android/io/flutter/plugin/platform

## iOS

UIViewの描画内容は、UIViewが保持しているCALayerが保持しています。
その保持内容をSkiaのクラス経由でFlutterViewまで流しているようです。

図にするとこんな感じ。
（読み切れていないのであまり自信がないです。間違っていたらコメント等で教えてください）

![iOSの方 (1).png](https://qiita-image-store.s3.amazonaws.com/0/41896/3ab1eff9-03b6-5c85-ddfe-e0e8da4bb73b.png)

ソースコードはAndroidと同じくEngineリポジトリに入っています。

* https://github.com/flutter/engine/blob/master/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm

# おしまい

実は描画内容転送以外にもタッチイベントの転送など、随所に工夫が見られます。
興味のある人はソースを読んでみると楽しいと思います。[^nocomment]
[^nocomment]: しかしドキュメントコメントなどがそんなにないEngineの中を読み解いていくのはなかなかに大変です。今回の内容分を把握するだけでも1日かかりました。誰か読み方を教えてください…