flutter-platform-views by flutter/skills
npx skills add https://github.com/flutter/skills --skill flutter-platform-views指导开发者如何为 Android、iOS 和 macOS 实现 Flutter Platform Views,以及如何将 Flutter 嵌入到现有的 Web 应用程序中。假设用户已配置好 Flutter 环境,并熟悉 Dart、JavaScript 及相关原生平台语言(Kotlin、Swift)。
在编写代码之前,您必须确定目标平台以及用户所需的具体嵌入策略。
请先询问用户: "您希望为哪个平台实现原生视图嵌入?
决策树:
根据用户在步骤 1 中的选择,实现 Dart 和 Kotlin 端。
Dart 实现: 如果用户选择混合合成(最佳保真度,Flutter FPS 较低):
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
Widget buildHybridAndroidView(BuildContext context, String viewType, Map<String, dynamic> creationParams) {
return PlatformViewLink(
viewType: viewType,
surfaceFactory: (context, controller) {
return AndroidViewSurface(
controller: controller as AndroidViewController,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
);
},
onCreatePlatformView: (params) {
return PlatformViewsService.initSurfaceAndroidView(
id: params.id,
viewType: viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
onFocus: () {
params.onFocusChanged(true);
},
)
..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
..create();
},
);
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
如果用户选择纹理层(最佳 Flutter FPS,快速滚动时可能卡顿):
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
Widget buildTextureAndroidView(BuildContext context, String viewType, Map<String, dynamic> creationParams) {
return AndroidView(
viewType: viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
);
}
Kotlin 实现(平台端): 创建 View、Factory,并在 MainActivity 中注册。
package dev.flutter.example
import android.content.Context
import android.graphics.Color
import android.view.View
import android.widget.TextView
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory
// 1. 定义 View
internal class NativeView(context: Context, id: Int, creationParams: Map<String?, Any?>?) : PlatformView {
private val textView: TextView = TextView(context).apply {
textSize = 72f
setBackgroundColor(Color.rgb(255, 255, 255))
text = "Rendered on a native Android view (id: $id)"
}
override fun getView(): View = textView
override fun dispose() {}
}
// 2. 定义 Factory
class NativeViewFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
override fun create(context: Context, viewId: Int, args: Any?): PlatformView {
val creationParams = args as Map<String?, Any?>?
return NativeView(context, viewId, creationParams)
}
}
// 3. 在 MainActivity 中注册
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
flutterEngine
.platformViewsController
.registry
.registerViewFactory("<platform-view-type>", NativeViewFactory())
}
}
验证与修复: 如果用户嵌入的是 SurfaceView 或 SurfaceTexture,请指导他们在内容更改时手动调用视图的 invalidate() 方法,因为它们不会自动失效。
iOS 仅使用混合合成。
Dart 实现:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
Widget buildIosView(BuildContext context, String viewType, Map<String, dynamic> creationParams) {
return UiKitView(
viewType: viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
);
}
Swift 实现(平台端):
import Flutter
import UIKit
// 1. 定义 View
class FLNativeView: NSObject, FlutterPlatformView {
private var _view: UIView
init(frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?, binaryMessenger messenger: FlutterBinaryMessenger?) {
_view = UIView()
super.init()
createNativeView(view: _view)
}
func view() -> UIView { return _view }
func createNativeView(view _view: UIView){
_view.backgroundColor = UIColor.blue
let nativeLabel = UILabel()
nativeLabel.text = "Native text from iOS"
nativeLabel.textColor = UIColor.white
nativeLabel.textAlignment = .center
nativeLabel.frame = CGRect(x: 0, y: 0, width: 180, height: 48.0)
_view.addSubview(nativeLabel)
}
}
// 2. 定义 Factory
class FLNativeViewFactory: NSObject, FlutterPlatformViewFactory {
private var messenger: FlutterBinaryMessenger
init(messenger: FlutterBinaryMessenger) {
self.messenger = messenger
super.init()
}
func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView {
return FLNativeView(frame: frame, viewIdentifier: viewId, arguments: args, binaryMessenger: messenger)
}
public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
return FlutterStandardMessageCodec.sharedInstance()
}
}
// 3. 在 AppDelegate 中注册
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
guard let pluginRegistrar = self.registrar(forPlugin: "plugin-name") else { return false }
let factory = FLNativeViewFactory(messenger: pluginRegistrar.messenger())
pluginRegistrar.register(factory, withId: "<platform-view-type>")
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
macOS 使用混合合成。请注意,目前手势支持有限。
Dart 实现:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
Widget buildMacOsView(BuildContext context, String viewType, Map<String, dynamic> creationParams) {
return AppKitView(
viewType: viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
);
}
Swift 实现(平台端):
import Cocoa
import FlutterMacOS
// 1. 定义 View
class NativeView: NSView {
init(viewIdentifier viewId: Int64, arguments args: Any?, binaryMessenger messenger: FlutterBinaryMessenger?) {
super.init(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
wantsLayer = true
layer?.backgroundColor = NSColor.systemBlue.cgColor
createNativeView(view: self)
}
required init?(coder nsCoder: NSCoder) { super.init(coder: nsCoder) }
func createNativeView(view _view: NSView) {
let nativeLabel = NSTextField()
nativeLabel.frame = CGRect(x: 0, y: 0, width: 180, height: 48.0)
nativeLabel.stringValue = "Native text from macOS"
nativeLabel.isEditable = false
nativeLabel.sizeToFit()
_view.addSubview(nativeLabel)
}
}
// 2. 定义 Factory
class NativeViewFactory: NSObject, FlutterPlatformViewFactory {
private var messenger: FlutterBinaryMessenger
init(messenger: FlutterBinaryMessenger) {
self.messenger = messenger
super.init()
}
func create(withViewIdentifier viewId: Int64, arguments args: Any?) -> NSView {
return NativeView(viewIdentifier: viewId, arguments: args, binaryMessenger: messenger)
}
public func createArgsCodec() -> (FlutterMessageCodec & NSObjectProtocol)? {
return FlutterStandardMessageCodec.sharedInstance()
}
}
// 3. 在 MainFlutterWindow.swift 中注册
class MainFlutterWindow: NSWindow {
override func awakeFromNib() {
let registrar = flutterViewController.registrar(forPlugin: "plugin-name")
let factory = NativeViewFactory(messenger: registrar.messenger)
registrar.register(factory, withId: "<platform-view-type>")
super.awakeFromNib()
}
}
如果用户选择嵌入式/多视图模式,请实现 JS 和 Dart 配置。
JavaScript 实现(flutter_bootstrap.js 或 HTML 脚本):
_flutter.loader.load({
onEntrypointLoaded: async function onEntrypointLoaded(engineInitializer) {
let engine = await engineInitializer.initializeEngine({
multiViewEnabled: true, // 启用嵌入式模式。
});
let app = await engine.runApp();
// 将视图添加到特定的宿主元素
let viewId = app.addView({
hostElement: document.querySelector('#flutter-host-element'),
initialData: { greeting: 'Hello from JS!' }
});
}
});
Dart 实现(main.dart): 验证与修复: 确保使用 runWidget 而不是 runApp。在多视图模式下,runApp 会因 implicitView 为 null 而失败。
import 'dart:ui' show FlutterView;
import 'package:flutter/widgets.dart';
void main() {
// 对于多视图 Web 嵌入,必须使用 runWidget,而不是 runApp。
runWidget(
MultiViewApp(
viewBuilder: (BuildContext context) => const MyEmbeddedWidget(),
),
);
}
class MultiViewApp extends StatefulWidget {
const MultiViewApp({super.key, required this.viewBuilder});
final WidgetBuilder viewBuilder;
@override
State<MultiViewApp> createState() => _MultiViewAppState();
}
class _MultiViewAppState extends State<MultiViewApp> with WidgetsBindingObserver {
Map<Object, Widget> _views = <Object, Widget>{};
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_updateViews();
}
@override
void didChangeMetrics() {
_updateViews();
}
void _updateViews() {
final Map<Object, Widget> newViews = <Object, Widget>{};
for (final FlutterView view in WidgetsBinding.instance.platformDispatcher.views) {
final Widget viewWidget = _views[view.viewId] ?? _createViewWidget(view);
newViews[view.viewId] = viewWidget;
}
setState(() {
_views = newViews;
});
}
Widget _createViewWidget(FlutterView view) {
return View(
view: view,
child: Builder(builder: widget.viewBuilder),
);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
Widget build(BuildContext context) {
return ViewCollection(views: _views.values.toList(growable: false));
}
}
class MyEmbeddedWidget extends StatelessWidget {
const MyEmbeddedWidget({super.key});
@override
Widget build(BuildContext context) {
// 如果需要处理特定逻辑,可以获取 viewId
final int viewId = View.of(context).viewId;
return Directionality(
textDirection: TextDirection.ltr,
child: Center(child: Text('Rendered in View ID: $viewId')),
);
}
}
runApp。您必须使用 runWidget 并通过 WidgetsBindingObserver 管理 FlutterView 的生命周期。ShaderMask 或 ColorFiltered 小部件,因为它们不受支持。BackdropFilter 有严格的限制。SurfaceView 或 SurfaceTexture 会在内容更改时自动失效。您必须在视图或其父视图上手动调用 invalidate()。每周安装量
943
代码仓库
GitHub 星标数
784
首次出现
2026年3月4日
安全审计
已安装于
codex909
cursor907
github-copilot906
gemini-cli905
opencode905
kimi-cli904
Guides developers through implementing Flutter Platform Views for Android, iOS, and macOS, as well as embedding Flutter into existing web applications. Assumes the user has a configured Flutter environment and is comfortable with Dart, JavaScript, and the relevant native platform languages (Kotlin, Swift).
Before writing code, you must determine the target platform and the specific embedding strategy required by the user.
STOP AND ASK THE USER: "Which platform are you targeting for native view embedding?
Decision Tree:
Based on the user's choice in Step 1, implement the Dart and Kotlin sides.
Dart Implementation: If the user chose Hybrid Composition (Best fidelity, lower Flutter FPS):
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
Widget buildHybridAndroidView(BuildContext context, String viewType, Map<String, dynamic> creationParams) {
return PlatformViewLink(
viewType: viewType,
surfaceFactory: (context, controller) {
return AndroidViewSurface(
controller: controller as AndroidViewController,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
);
},
onCreatePlatformView: (params) {
return PlatformViewsService.initSurfaceAndroidView(
id: params.id,
viewType: viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
onFocus: () {
params.onFocusChanged(true);
},
)
..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
..create();
},
);
}
If the user chose Texture Layer (Best Flutter FPS, janky quick scrolling):
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
Widget buildTextureAndroidView(BuildContext context, String viewType, Map<String, dynamic> creationParams) {
return AndroidView(
viewType: viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
);
}
Kotlin Implementation (Platform Side): Create the View, the Factory, and register it in the MainActivity.
package dev.flutter.example
import android.content.Context
import android.graphics.Color
import android.view.View
import android.widget.TextView
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory
// 1. Define the View
internal class NativeView(context: Context, id: Int, creationParams: Map<String?, Any?>?) : PlatformView {
private val textView: TextView = TextView(context).apply {
textSize = 72f
setBackgroundColor(Color.rgb(255, 255, 255))
text = "Rendered on a native Android view (id: $id)"
}
override fun getView(): View = textView
override fun dispose() {}
}
// 2. Define the Factory
class NativeViewFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
override fun create(context: Context, viewId: Int, args: Any?): PlatformView {
val creationParams = args as Map<String?, Any?>?
return NativeView(context, viewId, creationParams)
}
}
// 3. Register in MainActivity
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
flutterEngine
.platformViewsController
.registry
.registerViewFactory("<platform-view-type>", NativeViewFactory())
}
}
Validate-and-Fix: If the user is embedding a SurfaceView or SurfaceTexture, instruct them to manually call invalidate() on the view when content changes, as they do not invalidate themselves automatically.
iOS uses Hybrid Composition exclusively.
Dart Implementation:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
Widget buildIosView(BuildContext context, String viewType, Map<String, dynamic> creationParams) {
return UiKitView(
viewType: viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
);
}
Swift Implementation (Platform Side):
import Flutter
import UIKit
// 1. Define the View
class FLNativeView: NSObject, FlutterPlatformView {
private var _view: UIView
init(frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?, binaryMessenger messenger: FlutterBinaryMessenger?) {
_view = UIView()
super.init()
createNativeView(view: _view)
}
func view() -> UIView { return _view }
func createNativeView(view _view: UIView){
_view.backgroundColor = UIColor.blue
let nativeLabel = UILabel()
nativeLabel.text = "Native text from iOS"
nativeLabel.textColor = UIColor.white
nativeLabel.textAlignment = .center
nativeLabel.frame = CGRect(x: 0, y: 0, width: 180, height: 48.0)
_view.addSubview(nativeLabel)
}
}
// 2. Define the Factory
class FLNativeViewFactory: NSObject, FlutterPlatformViewFactory {
private var messenger: FlutterBinaryMessenger
init(messenger: FlutterBinaryMessenger) {
self.messenger = messenger
super.init()
}
func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView {
return FLNativeView(frame: frame, viewIdentifier: viewId, arguments: args, binaryMessenger: messenger)
}
public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
return FlutterStandardMessageCodec.sharedInstance()
}
}
// 3. Register in AppDelegate
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
guard let pluginRegistrar = self.registrar(forPlugin: "plugin-name") else { return false }
let factory = FLNativeViewFactory(messenger: pluginRegistrar.messenger())
pluginRegistrar.register(factory, withId: "<platform-view-type>")
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
macOS uses Hybrid Composition. Note that gesture support is currently limited.
Dart Implementation:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
Widget buildMacOsView(BuildContext context, String viewType, Map<String, dynamic> creationParams) {
return AppKitView(
viewType: viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
);
}
Swift Implementation (Platform Side):
import Cocoa
import FlutterMacOS
// 1. Define the View
class NativeView: NSView {
init(viewIdentifier viewId: Int64, arguments args: Any?, binaryMessenger messenger: FlutterBinaryMessenger?) {
super.init(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
wantsLayer = true
layer?.backgroundColor = NSColor.systemBlue.cgColor
createNativeView(view: self)
}
required init?(coder nsCoder: NSCoder) { super.init(coder: nsCoder) }
func createNativeView(view _view: NSView) {
let nativeLabel = NSTextField()
nativeLabel.frame = CGRect(x: 0, y: 0, width: 180, height: 48.0)
nativeLabel.stringValue = "Native text from macOS"
nativeLabel.isEditable = false
nativeLabel.sizeToFit()
_view.addSubview(nativeLabel)
}
}
// 2. Define the Factory
class NativeViewFactory: NSObject, FlutterPlatformViewFactory {
private var messenger: FlutterBinaryMessenger
init(messenger: FlutterBinaryMessenger) {
self.messenger = messenger
super.init()
}
func create(withViewIdentifier viewId: Int64, arguments args: Any?) -> NSView {
return NativeView(viewIdentifier: viewId, arguments: args, binaryMessenger: messenger)
}
public func createArgsCodec() -> (FlutterMessageCodec & NSObjectProtocol)? {
return FlutterStandardMessageCodec.sharedInstance()
}
}
// 3. Register in MainFlutterWindow.swift
class MainFlutterWindow: NSWindow {
override func awakeFromNib() {
let registrar = flutterViewController.registrar(forPlugin: "plugin-name")
let factory = NativeViewFactory(messenger: registrar.messenger)
registrar.register(factory, withId: "<platform-view-type>")
super.awakeFromNib()
}
}
If the user chose Embedded/Multi-view mode , implement the JS and Dart configurations.
JavaScript Implementation (flutter_bootstrap.js or HTML script):
_flutter.loader.load({
onEntrypointLoaded: async function onEntrypointLoaded(engineInitializer) {
let engine = await engineInitializer.initializeEngine({
multiViewEnabled: true, // Enables embedded mode.
});
let app = await engine.runApp();
// Add a view to a specific host element
let viewId = app.addView({
hostElement: document.querySelector('#flutter-host-element'),
initialData: { greeting: 'Hello from JS!' }
});
}
});
Dart Implementation (main.dart): Validate-and-Fix: Ensure runWidget is used instead of runApp. runApp will fail with a null implicitView error in multi-view mode.
import 'dart:ui' show FlutterView;
import 'package:flutter/widgets.dart';
void main() {
// MUST use runWidget, not runApp, for multi-view web embedding.
runWidget(
MultiViewApp(
viewBuilder: (BuildContext context) => const MyEmbeddedWidget(),
),
);
}
class MultiViewApp extends StatefulWidget {
const MultiViewApp({super.key, required this.viewBuilder});
final WidgetBuilder viewBuilder;
@override
State<MultiViewApp> createState() => _MultiViewAppState();
}
class _MultiViewAppState extends State<MultiViewApp> with WidgetsBindingObserver {
Map<Object, Widget> _views = <Object, Widget>{};
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_updateViews();
}
@override
void didChangeMetrics() {
_updateViews();
}
void _updateViews() {
final Map<Object, Widget> newViews = <Object, Widget>{};
for (final FlutterView view in WidgetsBinding.instance.platformDispatcher.views) {
final Widget viewWidget = _views[view.viewId] ?? _createViewWidget(view);
newViews[view.viewId] = viewWidget;
}
setState(() {
_views = newViews;
});
}
Widget _createViewWidget(FlutterView view) {
return View(
view: view,
child: Builder(builder: widget.viewBuilder),
);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
Widget build(BuildContext context) {
return ViewCollection(views: _views.values.toList(growable: false));
}
}
class MyEmbeddedWidget extends StatelessWidget {
const MyEmbeddedWidget({super.key});
@override
Widget build(BuildContext context) {
// Retrieve the viewId to handle specific logic if needed
final int viewId = View.of(context).viewId;
return Directionality(
textDirection: TextDirection.ltr,
child: Center(child: Text('Rendered in View ID: $viewId')),
);
}
}
runApp when configuring Flutter Web for multi-view embedding. You must use runWidget and manage the FlutterView lifecycle via WidgetsBindingObserver.ShaderMask or ColorFiltered widgets over iOS Platform Views, as they are unsupported. BackdropFilter has strict limitations.SurfaceView or SurfaceTexture will automatically invalidate when their content changes. You must manually call invalidate() on the view or its parent.Weekly Installs
943
Repository
GitHub Stars
784
First Seen
Mar 4, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex909
cursor907
github-copilot906
gemini-cli905
opencode905
kimi-cli904
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
102,200 周安装
演讲技巧与PPT制作指南:19位产品领导者的高效演讲呈现方法
904 周安装
Favicon生成器 - 一键生成完整网站图标包,支持SVG/ICO/iOS/Android/PWA格式
907 周安装
Zod 最佳实践指南:TypeScript 模式验证 43 条规则与性能优化
908 周安装
Theme Factory - 专业字体色彩主题库,一键应用设计风格到演示文稿和作品
909 周安装
高级全栈工程师技能包:自动化脚手架、代码质量分析与最佳实践工具
909 周安装
专业市场研究报告生成器 | 50+页咨询级分析报告,含波特五力、SWOT、PESTLE等框架
910 周安装