Flutter嵌套原生View。
还是最近的工作内容,把原生直播播放器嵌套到flutter内,引出的东西。
本篇通过这个需求,引出牵扯的东西吧。
如果要把原生组件嵌套到flutter中,那么我们需要认识一下下面几个东西。
FlutterPlugin
注册FlutterPlatformViewFactory对象的方法
/**
* Registers a `FlutterPlatformViewFactory` for creation of platform views.
*
* Plugins can expose a `UIView` for embedding in Flutter apps by registering a view factory.
*
* @param factory The view factory that will be registered.
* @param factoryId A unique identifier for the factory, the Dart code of the Flutter app can use
* this identifier to request creation of a `UIView` by the registered factory.
* @param gestureRecognizersBlockingPolicy How UIGestureRecognizers on the platform views are
* blocked.
*
*/
- (void)registerViewFactory:(NSObject<FlutterPlatformViewFactory>*)factory
withId:(NSString*)factoryId;
FlutterPlatformViewFactory负责嵌套原生的工厂类。
FLUTTER_DARWIN_EXPORT
@protocol FlutterPlatformViewFactory <NSObject>
/**
* Create a `FlutterPlatformView`.
*
* Implemented by iOS code that expose a `UIView` for embedding in a Flutter app.
*
* The implementation of this method should create a new `UIView` and return it.
*
* @param frame The rectangle for the newly created `UIView` measured in points.
* @param viewId A unique identifier for this `UIView`.
* @param args Parameters for creating the `UIView` sent from the Dart side of the Flutter app.
* If `createArgsCodec` is not implemented, or if no creation arguments were sent from the �Dart
* code, this will be null. Otherwise this will be the value sent from the Dart code as decoded by
* `createArgsCodec`.
*/
- (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
viewIdentifier:(int64_t)viewId
arguments:(id _Nullable)args;
/**
* Returns the `FlutterMessageCodec` for decoding the args parameter of `createWithFrame`.
*
* Only needs to be implemented if `createWithFrame` needs an arguments parameter.
*/
@optional
- (NSObject<FlutterMessageCodec>*)createArgsCodec;
@end
FlutterPlatformView 包裹原生组件的flutterView
@protocol FlutterPlatformView <NSObject>
/**
* Returns a reference to the `UIView` that is wrapped by this `FlutterPlatformView`.
*/
- (UIView*)view;
@end
答题的思路在于,通过FlutterPlatformViewFactory协议,来创建工厂对象,并通过工厂类的create方法,创建嵌套原生的flutterView。
// 直播播放器
public class ZLFlutterRTCLivePlayViewFactory : NSObject,FlutterPlatformViewFactory{
public static let SIGN = "ZLFlutterRTCLivePlayView";
private var message : FlutterBinaryMessenger;
init(message : FlutterBinaryMessenger) {
self.message = message;
}
public func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView {
// 绑定方法监听
let channel = FlutterMethodChannel(
name: "\(ZLFlutterRTCLivePlayViewFactory.SIGN)_\(viewId)",
binaryMessenger: message
)
let view = ZLFlutterRTCLivePlayView(frame, channel: channel);
channel.setMethodCallHandler(view.handle);
return view;
}
}
class ZLFlutterRTCLivePlayView : NSObject,FlutterPlatformView{
private lazy var playView : UIView = {
return UIView();
}(); // 播放View
private var frame: CGRect?;
private var channel: FlutterMethodChannel?
private lazy var player: TXLivePlayer = { // 播放器
let livePlayer = TXLivePlayer()
livePlayer.delegate = self
return livePlayer
}()
init(_ frame : CGRect, channel: FlutterMethodChannel?) {
super.init()
self.frame = frame;
self.channel = channel;
}
public func view() -> UIView {
return playView;
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "startPlay":
startPlay(call, result: result);
break;
case "stopPlay":
stopPlay(call, result: result);
break;
case "setMute":
setMute(call, result: result);
break;
case "setRenderMode":
setRenderMode(call, result: result);
break;
case "setRenderRotation":
setRenderRotation(call, result: result);
break;
default:
result(FlutterMethodNotImplemented);
}
}
private func startPlay(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
if let url = CommonUtils.getParamByKey(call: call, result: result, param: "url") as? String,
let type = CommonUtils.getParamByKey(call: call, result: result, param: "type") as? Int,
let playType = TX_Enum_PlayType(rawValue: type) {
player.setupVideoWidget(frame ?? CGRect.zero, contain: self.playView, insert: 0)
player.startPlay(url, type: .PLAY_TYPE_VOD_HLS)
}
result(nil)
}
private func stopPlay(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
player.stopPlay()
player.removeVideoWidget()
result(nil)
}
private func setMute(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
if let mute = CommonUtils.getParamByKey(call: call, result: result, param: "url") as? Bool {
player.setMute(mute)
}
result(nil)
}
private func setRenderMode(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
if let value = CommonUtils.getParamByKey(call: call, result: result, param: "renderMode") as? Int,
let renderMode = TX_Enum_Type_RenderMode.init(rawValue: value) {
self.player.setRenderMode(renderMode)
}
result(nil)
}
private func setRenderRotation(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
if let value = CommonUtils.getParamByKey(call: call, result: result, param: "rotation") as? Int,
let rotation = TX_Enum_Type_HomeOrientation.init(rawValue: value) {
self.player.setRenderRotation(rotation)
}
result(nil)
}
}
通过FlutterPlugin来注册工厂类。
public static func register(with registrar: FlutterPluginRegistrar) {
// 直播播放器
let playViewFactory = ZLFlutterRTCLivePlayViewFactory(message: registrar.messenger());
registrar.register(
playViewFactory,
withId: ZLFlutterRTCLivePlayViewFactory.SIGN
);
}
对于flutter端的代码,通过channelType来进行原生flutter的交互。
/// 直播播放器
class ZLPlayView extends StatefulWidget {
/// @nodoc
/// channel标识符
static String channelType = "ZLFlutterRTCLivePlayView";
final ValueChanged<int>? onViewCreated;
final Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers;
const ZLPlayView({Key? key, this.onViewCreated, this.gestureRecognizers})
: super(key: key);
@override
State<StatefulWidget> createState() => ZLPlayViewState();
}
//// @nodoc
class ZLPlayViewState extends State<ZLPlayView> {
@override
Widget build(BuildContext context) {
if (Platform.isAndroid) {
return AndroidView(
viewType: ZLPlayView.channelType,
onPlatformViewCreated: _onPlatformViewCreated,
gestureRecognizers: widget.gestureRecognizers,
);
} else if (Platform.isIOS) {
return UiKitView(
viewType: ZLPlayView.channelType,
onPlatformViewCreated: _onPlatformViewCreated,
gestureRecognizers: widget.gestureRecognizers,
);
} else {
return Text("该平台不支持Platform View");
}
}
void _onPlatformViewCreated(int id) {
if (widget.onViewCreated != null) {
widget.onViewCreated!(id);
}
}
}
typedef ZLPlayOnPlayEvent = void Function(int id, Map param);
/// @nodoc
/// Weex页面控制器方法
class ZLPlayViewController {
late final MethodChannel _channel;
ZLPlayOnPlayEvent? _onPlayEvent;
ZLPlayViewController(int id) {
_channel = new MethodChannel(ZLPlayView.channelType + '_$id');
_channel.setMethodCallHandler((methodCall) async {
switch (methodCall.method) {
case 'onPlayEvent':
int id = methodCall.arguments['EvtID'] ?? 0;
Map param = methodCall.arguments['param'] ?? {};
if (_onPlayEvent != null) {
_onPlayEvent!(id, param);
}
throw MissingPluginException();
case 'onNetStatus':
throw MissingPluginException();
default:
throw MissingPluginException();
}
});
}
setOnPlayEvent(ZLPlayOnPlayEvent onPlayEvent) {
_onPlayEvent = onPlayEvent;
}
/// @description: 开始播放
/// @param url: 播放地址
/// @param type: 播放类型
/// @return void
Future<void> startPlay(String url, ZLPlayType type) {
return _channel.invokeMethod('startPlay', {"url": url, "type": type.index});
}
/// @description: 停止播放
/// @return void
Future<void> stopPlay() {
return _channel.invokeMethod('stopPlay');
}
/// @description: 设置静音
/// @param mute: true 静音
/// @return void
Future<void> setMute(bool mute) {
return _channel.invokeMethod('setMute', {"mute": mute});
}
/// @description: 设置画面的裁剪模式
/// @param setRenderMode: 模式
/// @return void
Future<void> setRenderMode(ZLPlayViewRenderMode renderMode) {
return _channel
.invokeMethod('setRenderMode', {"renderMode": renderMode.index});
}
/// @description: 设置画面的方向
/// @param rotation: 方向
/// @return void
Future<void> setRenderRotation(ZLPlayViewHomeOrientation rotation) {
return _channel
.invokeMethod('setRenderRotation', {"rotation": rotation.index});
}
}
如果使用的话,最好在外面再套一层业务层来使用。
class ZLLiveRoomVideoView extends ZLPlayView {
const ZLLiveRoomVideoView(
{Key? key,
// int? viewType,
ValueChanged<int>? onViewCreated,
Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers})
: super(
key: key,
onViewCreated: onViewCreated,
gestureRecognizers: gestureRecognizers);
}