Flutter入門:使用 PlatforView 顯示 iOS 原生 View

2018年一直在使用 flutter 寫專案,從0。2。0開始到現在1。0版本的釋出,終於開始慢慢的爬出坑位了,但是因為部分控制元件感覺還是不如原生控制元件好用,一直在摸索怎麼將原生view 可以放在 flutter 中並且不會遮擋住 flutter 的 widget。終於,看到官網提供了 PlatformView部件,因為我本身是一名 iOS 開發人員,這裡只提供 iOS 的教程,Android 開發教程在這裡。

什麼是 PlatformView?

PlatformView是 flutter 官方提供的一個可以嵌入 Android 和 iOS 平臺原生 view 的小部件。

在我們實際開發中,我們遇到一些 flutter 官方沒有提供的外掛可以自己建立編寫外掛來實現部分功能,但是原生View在 flutter 中會遮擋住flutter 中的小部件,比如你想使用高德地圖sdk、影片播放器、直播等原生控制元件,就無法很好的與 flutter 專案結合。

之前知道flutter 給 Android(

google 的親兒子

)提供了 AndroidView可以實現將 view 存放到部件中,教程也不少,無奈,iOS (

畢竟不是親的

)在網上使用 UiKitView的教程太少,目前就只看到日本的一個作者用 swift寫的 教程,終於有了可以參考的 Demo,下面我就用 Object-C 來說一下教程:

製作外掛

我們需要建立一個 專案外掛,我這裡使用 預設的 Object-C和 Java語言。Plugin

activity_indicator。dart

首先,我將建立一個StatefulWidget類,在class下顯示本機檢視。使用檔名activity_indicator。dart編寫以下程式碼。

import ‘package:flutter/foundation。dart’;import ‘package:flutter/material。dart’;import ‘package:flutter/services。dart’;typedef void UIActivityIndicatorWidgetCreatedCallback(ActivityIndicatorController controller);class ActivityIndicatorController { ActivityIndicatorController。_(int id) : _channel = MethodChannel(‘plugins/activity_indicator_$id’); final MethodChannel _channel; Future start() async { return _channel。invokeMethod(‘start’); } Future stop() async { return _channel。invokeMethod(‘stop’); }}class UIActivityIndicator extends StatefulWidget{ const UIActivityIndicator({ Key key, this。onActivityIndicatorWidgetCreated, this。hexColor, }):super(key:key); final UIActivityIndicatorWidgetCreatedCallback onActivityIndicatorWidgetCreated; final String hexColor; @override State createState() { // TODO: implement createState return _UIActivityIndicatorState(); }}class _UIActivityIndicatorState extends State{ @override Widget build(BuildContext context) { // TODO: implement build if(defaultTargetPlatform == TargetPlatform。iOS){ return UiKitView( viewType: “plugins/activity_indicator”, onPlatformViewCreated:_onPlatformViewCreated, creationParams: { “hexColor”:widget。hexColor, “hidesWhenStopped”:true, }, creationParamsCodec: new StandardMessageCodec(), ); } return Text(‘activity_indicator外掛尚不支援$defaultTargetPlatform ’); } void _onPlatformViewCreated(int id){ if(widget。onActivityIndicatorWidgetCreated == null){ return; } widget。onActivityIndicatorWidgetCreated(new ActivityIndicatorController。_(id)); }}

呼叫 iOS檢視

UIKitView用於呼叫iOS檢視,如下所示。對於指定的引數,viewType用於確定本機端的目標View的返回。對於Android,我們使用AndroidView但指定viewType不會更改。

此外,onPlatformViewCreated可以將ActivityIndi​catorController與UIActivityIndi​​cator小部件一起使用。要將引數傳遞給本機端,請使用creationParams。

Widget build(BuildContext context) { // TODO: implement build if(defaultTargetPlatform == TargetPlatform。iOS){ return UiKitView( viewType: “plugins/activity_indicator”, onPlatformViewCreated:_onPlatformViewCreated, creationParams: { “hexColor”:widget。hexColor, “hidesWhenStopped”:true, }, creationParamsCodec: new StandardMessageCodec(), ); } return Text(‘activity_indicator外掛尚不支援$defaultTargetPlatform ’); }

從 Flutter 執行原生程式碼

使用MethodChannel從Flutter執行本機程式碼。這也會編寫接收MethodChannel和invokeMethod引數的程式碼,並在本機端執行相應的處理。這次實現它,以便可以透過ActivityIndi​​catorController執行本機程式碼。

class ActivityIndicatorController { ActivityIndicatorController。_(int id) : _channel = MethodChannel(‘plugins/activity_indicator_$id’); final MethodChannel _channel; Future start() async { return _channel。invokeMethod(‘start’); } Future stop() async { return _channel。invokeMethod(‘stop’); }}

main。dart

接下來,編輯example / main。dart並建立一個螢幕。我將使用我之前建立的UIActivityIndi​​cator小部件。

import ‘package:flutter/material。dart’;import ‘dart:async’;import ‘package:flutter/services。dart’;import ‘package:activity_indicator/activity_indicator。dart’;void main() => runApp(MaterialApp( home: ActivityIndicatorExample(),));class ActivityIndicatorExample extends StatelessWidget{ ActivityIndicatorController controller; void _onActivityIndicatorControllerCreated(ActivityIndicatorController _controller){ controller = _controller; } @override Widget build(BuildContext context) { // TODO: implement build return Scaffold( appBar: AppBar(title: const Text(“載入測試”),), body: Stack( alignment: Alignment。bottomCenter, children: [ new Container( child: new Stack( children: [ UIActivityIndicator( hexColor: “FF0000”, onActivityIndicatorWidgetCreated: _onActivityIndicatorControllerCreated, ), new Container( alignment: Alignment。center, child: new Text(“我是flutter控制元件,沒有被遮擋~”), ), ], ), ), Padding( padding: const EdgeInsets。only(left: 45。0,right: 45。0,top: 0。0,bottom: 50。0), child: new Row( mainAxisAlignment: MainAxisAlignment。spaceEvenly, children: [ FloatingActionButton( onPressed: (){ controller。start(); }, child: new Text(“Start”), ), FloatingActionButton( onPressed: (){ controller。stop(); }, child: new Text(“Stop”), ) ], ), ) ], ), ); }}

iOS 端實現

FlutterActivityIndicator。h

新建類,提供FlutterPlatformView和FlutterPlatformViewFactory協議

#import #import NS_ASSUME_NONNULL_BEGIN@interface FlutterActivityIndicatorController : NSObject- (instancetype)initWithWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id _Nullable)args binaryMessenger:(NSObject*)messenger;@end@interface FlutterActivityIndicatorFactory : NSObject- (instancetype)initWithMessenger:(NSObject*)messager;@endNS_ASSUME_NONNULL_END

從 Flutter 執行原生程式碼

要從Flutter端執行本機程式碼,可以按如下方式使用MethodChannel。它產生以前MethodChannel,當您從ActivityIndi​​catorController時,InvokeMethod,onMethodCall被呼叫,所以你遇到在引數中指定的字串,以及執行過程中看到它的價值。

_viewId = viewId; NSString* channelName = [NSString stringWithFormat:@“plugins/activity_indicator_%lld”, viewId];_channel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:messenger];__weak __typeof__(self) weakSelf = self;[_channel setMethodCallHandler:^(FlutterMethodCall * call, FlutterResult result) { [weakSelf onMethodCall:call result:result];}];-(void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result{ if ([[call method] isEqualToString:@“start”]) { [_indicator startAnimating]; }else if ([[call method] isEqualToString:@“stop”]){ [_indicator stopAnimating]; } else { result(FlutterMethodNotImplemented); }}

將引數從 Flutter 傳遞到 iOS

由於Flutter端的creationParams指定的值是args,因此將其轉換為型別並設定為UIActivityIndi​​catorView的屬性。

NSDictionary *dic = args;NSString *hexColor = dic[@“hexColor”]; bool hidesWhenStopped = [dic[@“hidesWhenStopped”] boolValue]; _indicator = [[UIActivityIndicatorView alloc]init];_indicator。activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge;_indicator。color = [UIColor colorWithHexString:hexColor];_indicator。hidesWhenStopped = hidesWhenStopped;

ActivityIndicatorPlugin。m

自動生成檔案中,只需要這樣寫

#import “ActivityIndicatorPlugin。h”#import “FlutterActivityIndicator。h”@implementation ActivityIndicatorPlugin+ (void)registerWithRegistrar:(NSObject*)registrar { [registrar registerViewFactory:[[FlutterActivityIndicatorFactory alloc] initWithMessenger:registrar。messenger] withId:@“plugins/activity_indicator”]; }@end

要保證你的viewId指定的字串與你 flutter 端程式碼的ViewType指定的字串相匹配

最重要的一步操作

要在你的 info。plist中新增

io。flutter。embedded_views_preview

要求必須這樣設定https://github。com/flutter/flutter/issues/19030#issuecomment-437534853

演示

演示 Demo。gif

Demo 地址

https://github。com/rongshuizhou/activity_indicator。git