向 Android 应用添加 Flutter 片段
本指南介绍如何在现有的 Android 应用中添加 Flutter Fragment
。在 Android 中,Fragment
代表较大 UI 的一个模块化部分。Fragment
可用于呈现滑动抽屉、选项卡内容、ViewPager
中的页面,或者它可能只是单一 Activity
应用中普通屏幕的表示。Flutter 提供了一个 FlutterFragment
,以便开发人员可以在任何可以使用常规 Fragment
的地方呈现 Flutter 体验。
如果 Activity
同样适用于您的应用程序需求,请考虑 使用 FlutterActivity
而不是 FlutterFragment
,这更快且更易于使用。
FlutterFragment
允许开发人员控制 Fragment
中 Flutter 体验的以下详细信息
- 初始 Flutter 路由
- 要执行的 Dart 入口点
- 不透明与半透明背景
FlutterFragment
是否应控制其周围的Activity
- 是否应使用新的
FlutterEngine
或缓存的FlutterEngine
FlutterFragment
还附带了一些必须从其周围的 Activity
转发的调用。这些调用允许 Flutter 对操作系统事件做出适当的反应。
本指南介绍了所有类型的 FlutterFragment
及其要求。
在 Activity 中添加 FlutterFragment 并使用新的 FlutterEngine
#使用 FlutterFragment
的第一步是将其添加到宿主 Activity
中。
要将 FlutterFragment
添加到宿主 Activity
,请在 Activity
中的 onCreate()
中实例化并附加 FlutterFragment
的实例,或者在适合您的应用的其他时间进行。
class MyActivity : FragmentActivity() {
companion object {
// Define a tag String to represent the FlutterFragment within this
// Activity's FragmentManager. This value can be whatever you'd like.
private const val TAG_FLUTTER_FRAGMENT = "flutter_fragment"
}
// Declare a local variable to reference the FlutterFragment so that you
// can forward calls to it later.
private var flutterFragment: FlutterFragment? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Inflate a layout that has a container for your FlutterFragment. For
// this example, assume that a FrameLayout exists with an ID of
// R.id.fragment_container.
setContentView(R.layout.my_activity_layout)
// Get a reference to the Activity's FragmentManager to add a new
// FlutterFragment, or find an existing one.
val fragmentManager: FragmentManager = supportFragmentManager
// Attempt to find an existing FlutterFragment, in case this is not the
// first time that onCreate() was run.
flutterFragment = fragmentManager
.findFragmentByTag(TAG_FLUTTER_FRAGMENT) as FlutterFragment?
// Create and attach a FlutterFragment if one does not exist.
if (flutterFragment == null) {
var newFlutterFragment = FlutterFragment.createDefault()
flutterFragment = newFlutterFragment
fragmentManager
.beginTransaction()
.add(
R.id.fragment_container,
newFlutterFragment,
TAG_FLUTTER_FRAGMENT
)
.commit()
}
}
}
public class MyActivity extends FragmentActivity {
// Define a tag String to represent the FlutterFragment within this
// Activity's FragmentManager. This value can be whatever you'd like.
private static final String TAG_FLUTTER_FRAGMENT = "flutter_fragment";
// Declare a local variable to reference the FlutterFragment so that you
// can forward calls to it later.
private FlutterFragment flutterFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Inflate a layout that has a container for your FlutterFragment.
// For this example, assume that a FrameLayout exists with an ID of
// R.id.fragment_container.
setContentView(R.layout.my_activity_layout);
// Get a reference to the Activity's FragmentManager to add a new
// FlutterFragment, or find an existing one.
FragmentManager fragmentManager = getSupportFragmentManager();
// Attempt to find an existing FlutterFragment,
// in case this is not the first time that onCreate() was run.
flutterFragment = (FlutterFragment) fragmentManager
.findFragmentByTag(TAG_FLUTTER_FRAGMENT);
// Create and attach a FlutterFragment if one does not exist.
if (flutterFragment == null) {
flutterFragment = FlutterFragment.createDefault();
fragmentManager
.beginTransaction()
.add(
R.id.fragment_container,
flutterFragment,
TAG_FLUTTER_FRAGMENT
)
.commit();
}
}
}
前面的代码足以渲染 Flutter UI,该 UI 以对您的 main()
Dart 入口点的调用开始,初始 Flutter 路由为 /
,并且使用新的 FlutterEngine
。但是,此代码不足以实现所有预期的 Flutter 行为。Flutter 依赖于各种操作系统信号,这些信号必须从您的宿主 Activity
转发到 FlutterFragment
。以下示例显示了这些调用
class MyActivity : FragmentActivity() {
override fun onPostResume() {
super.onPostResume()
flutterFragment!!.onPostResume()
}
override fun onNewIntent(@NonNull intent: Intent) {
flutterFragment!!.onNewIntent(intent)
}
override fun onBackPressed() {
flutterFragment!!.onBackPressed()
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String?>,
grantResults: IntArray
) {
flutterFragment!!.onRequestPermissionsResult(
requestCode,
permissions,
grantResults
)
}
override fun onActivityResult(
requestCode: Int,
resultCode: Int,
data: Intent?
) {
super.onActivityResult(requestCode, resultCode, data)
flutterFragment!!.onActivityResult(
requestCode,
resultCode,
data
)
}
override fun onUserLeaveHint() {
flutterFragment!!.onUserLeaveHint()
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
flutterFragment!!.onTrimMemory(level)
}
}
public class MyActivity extends FragmentActivity {
@Override
public void onPostResume() {
super.onPostResume();
flutterFragment.onPostResume();
}
@Override
protected void onNewIntent(@NonNull Intent intent) {
flutterFragment.onNewIntent(intent);
}
@Override
public void onBackPressed() {
flutterFragment.onBackPressed();
}
@Override
public void onRequestPermissionsResult(
int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults
) {
flutterFragment.onRequestPermissionsResult(
requestCode,
permissions,
grantResults
);
}
@Override
public void onActivityResult(
int requestCode,
int resultCode,
@Nullable Intent data
) {
super.onActivityResult(requestCode, resultCode, data);
flutterFragment.onActivityResult(
requestCode,
resultCode,
data
);
}
@Override
public void onUserLeaveHint() {
flutterFragment.onUserLeaveHint();
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
flutterFragment.onTrimMemory(level);
}
}
将操作系统信号转发到 Flutter 后,您的 FlutterFragment
将按预期工作。您现在已将 FlutterFragment
添加到现有的 Android 应用中。
最简单的集成路径使用新的 FlutterEngine
,它具有非平凡的初始化时间,导致在 Flutter 首次初始化并渲染之前 UI 为空白。通过使用缓存的预热 FlutterEngine
可以避免大部分此时间开销,这将在后面讨论。
使用预热好的 FlutterEngine
#默认情况下,FlutterFragment
会创建自己的 FlutterEngine
实例,这需要非平凡的预热时间。这意味着您的用户会短暂看到空白的 Fragment
。您可以通过使用现有的预热 FlutterEngine
实例来减轻大部分此预热时间。
要在 FlutterFragment
中使用预热好的 FlutterEngine
,请使用 withCachedEngine()
工厂方法实例化 FlutterFragment
。
// Somewhere in your app, before your FlutterFragment is needed,
// like in the Application class ...
// Instantiate a FlutterEngine.
val flutterEngine = FlutterEngine(context)
// Start executing Dart code in the FlutterEngine.
flutterEngine.getDartExecutor().executeDartEntrypoint(
DartEntrypoint.createDefault()
)
// Cache the pre-warmed FlutterEngine to be used later by FlutterFragment.
FlutterEngineCache
.getInstance()
.put("my_engine_id", flutterEngine)
FlutterFragment.withCachedEngine("my_engine_id").build()
// Somewhere in your app, before your FlutterFragment is needed,
// like in the Application class ...
// Instantiate a FlutterEngine.
FlutterEngine flutterEngine = new FlutterEngine(context);
// Start executing Dart code in the FlutterEngine.
flutterEngine.getDartExecutor().executeDartEntrypoint(
DartEntrypoint.createDefault()
);
// Cache the pre-warmed FlutterEngine to be used later by FlutterFragment.
FlutterEngineCache
.getInstance()
.put("my_engine_id", flutterEngine);
FlutterFragment.withCachedEngine("my_engine_id").build();
FlutterFragment
在内部了解 FlutterEngineCache
并根据提供给 withCachedEngine()
的 ID 检索预热好的 FlutterEngine
。
通过提供预热好的 FlutterEngine
(如前所示),您的应用会尽快渲染第一个 Flutter 帧。
使用缓存引擎的初始路由
#在使用新的 FlutterEngine
配置 FlutterActivity
或 FlutterFragment
时,可以使用初始路由的概念。但是,FlutterActivity
和 FlutterFragment
在使用缓存引擎时不提供初始路由的概念。这是因为缓存引擎预计已在运行 Dart 代码,这意味着为时已晚,无法配置初始路由。
希望其缓存引擎以自定义初始路由开始的开发人员可以在执行 Dart 入口点之前,将其缓存的 FlutterEngine
配置为使用自定义初始路由。以下示例演示了如何在缓存引擎中使用初始路由
class MyApplication : Application() {
lateinit var flutterEngine : FlutterEngine
override fun onCreate() {
super.onCreate()
// Instantiate a FlutterEngine.
flutterEngine = FlutterEngine(this)
// Configure an initial route.
flutterEngine.navigationChannel.setInitialRoute("your/route/here");
// Start executing Dart code to pre-warm the FlutterEngine.
flutterEngine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
)
// Cache the FlutterEngine to be used by FlutterActivity or FlutterFragment.
FlutterEngineCache
.getInstance()
.put("my_engine_id", flutterEngine)
}
}
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// Instantiate a FlutterEngine.
flutterEngine = new FlutterEngine(this);
// Configure an initial route.
flutterEngine.getNavigationChannel().setInitialRoute("your/route/here");
// Start executing Dart code to pre-warm the FlutterEngine.
flutterEngine.getDartExecutor().executeDartEntrypoint(
DartEntrypoint.createDefault()
);
// Cache the FlutterEngine to be used by FlutterActivity or FlutterFragment.
FlutterEngineCache
.getInstance()
.put("my_engine_id", flutterEngine);
}
}
通过设置导航通道的初始路由,关联的 FlutterEngine
会在 runApp()
Dart 函数的初始执行时显示所需的路由。
在 runApp()
初始执行后更改导航通道的初始路由属性无效。希望在不同的 Activity
和 Fragment
之间使用相同的 FlutterEngine
并切换这些显示之间的路由的开发人员,需要设置方法通道并明确指示其 Dart 代码更改 Navigator
路由。
显示启动画面
#Flutter 内容的初始显示需要一些等待时间,即使使用了预热好的 FlutterEngine
也是如此。为了帮助改善围绕此短暂等待时间的用户体验,Flutter 支持在 Flutter 渲染其第一帧之前显示启动画面(也称为“启动屏幕”。有关如何显示启动屏幕的说明,请参阅 启动屏幕指南。
使用指定的初始路由运行 Flutter
#Android 应用可能包含许多独立的 Flutter 体验,这些体验在不同的 FlutterFragment
中运行,并使用不同的 FlutterEngine
。在这些情况下,每个 Flutter 体验通常都以不同的初始路由(除 /
之外的路由)开始。为了便于此,FlutterFragment
的 Builder
允许您指定所需的初始路由,如所示
// With a new FlutterEngine.
val flutterFragment = FlutterFragment.withNewEngine()
.initialRoute("myInitialRoute/")
.build()
// With a new FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
.initialRoute("myInitialRoute/")
.build();
从指定的入口点运行 Flutter
#与不同的初始路由类似,不同的 FlutterFragment
可能希望执行不同的 Dart 入口点。在典型的 Flutter 应用中,只有一个 Dart 入口点:main()
,但您可以定义其他入口点。
FlutterFragment
支持指定给定 Flutter 体验要执行的所需 Dart 入口点。要指定入口点,请构建 FlutterFragment
,如所示
val flutterFragment = FlutterFragment.withNewEngine()
.dartEntrypoint("mySpecialEntrypoint")
.build()
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
.dartEntrypoint("mySpecialEntrypoint")
.build();
FlutterFragment
配置会导致执行名为 mySpecialEntrypoint()
的 Dart 入口点。请注意,括号 ()
不包含在 dartEntrypoint
String
名称中。
控制 FlutterFragment 的渲染模式
#FlutterFragment
可以使用 SurfaceView
渲染其 Flutter 内容,也可以使用 TextureView
。默认值为 SurfaceView
,其性能明显优于 TextureView
。但是,SurfaceView
不能插入 Android View
层次结构的中间。SurfaceView
必须是层次结构中最底层的 View
或最顶层的 View
。此外,在 Android N 之前的 Android 版本上,SurfaceView
无法进行动画处理,因为它们的布局和渲染与 View
层次结构的其余部分不同步。如果您的应用需要这两种用例中的任何一种,则需要使用 TextureView
而不是 SurfaceView
。通过使用 texture
RenderMode
构建 FlutterFragment
来选择 TextureView
// With a new FlutterEngine.
val flutterFragment = FlutterFragment.withNewEngine()
.renderMode(FlutterView.RenderMode.texture)
.build()
// With a cached FlutterEngine.
val flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
.renderMode(FlutterView.RenderMode.texture)
.build()
// With a new FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
.renderMode(FlutterView.RenderMode.texture)
.build();
// With a cached FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
.renderMode(FlutterView.RenderMode.texture)
.build();
使用所示配置,生成的 FlutterFragment
将其 UI 渲染到 TextureView
。
以透明方式显示 FlutterFragment
#默认情况下,FlutterFragment
使用 SurfaceView
以不透明背景进行渲染。(请参阅“控制 FlutterFragment
的渲染模式”。)对于 Flutter 未绘制的任何像素,该背景为黑色。出于性能原因,以不透明背景进行渲染是首选的渲染模式。在 Android 上以透明方式渲染 Flutter 会对性能产生负面影响。但是,许多设计需要 Flutter 体验中显示到基础 Android UI 的透明像素。因此,Flutter 支持 FlutterFragment
中的半透明。
要为 FlutterFragment
启用透明度,请使用以下配置构建它
// Using a new FlutterEngine.
val flutterFragment = FlutterFragment.withNewEngine()
.transparencyMode(FlutterView.TransparencyMode.transparent)
.build()
// Using a cached FlutterEngine.
val flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
.transparencyMode(FlutterView.TransparencyMode.transparent)
.build()
// Using a new FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
.transparencyMode(FlutterView.TransparencyMode.transparent)
.build();
// Using a cached FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
.transparencyMode(FlutterView.TransparencyMode.transparent)
.build();
FlutterFragment 与其 Activity 之间的关系
#某些应用选择将 Fragment
用作整个 Android 屏幕。在这些应用中,Fragment
控制系统 chrome(如 Android 的状态栏、导航栏和方向)是合理的。
在其他应用中,Fragment
用于表示 UI 的一部分。FlutterFragment
可用于实现抽屉内部、视频播放器或单个卡片。在这些情况下,FlutterFragment
影响 Android 系统 chrome 是不合适的,因为同一 Window
中还有其他 UI 部分。
FlutterFragment
带有一个概念,有助于区分 FlutterFragment
应该能够控制其宿主 Activity
的情况,以及 FlutterFragment
应该仅影响其自身行为的情况。要防止 FlutterFragment
将其 Activity
公开给 Flutter 插件,并防止 Flutter 控制 Activity
的系统 UI,请在 FlutterFragment
的 Builder
中使用 shouldAttachEngineToActivity()
方法,如所示
// Using a new FlutterEngine.
val flutterFragment = FlutterFragment.withNewEngine()
.shouldAttachEngineToActivity(false)
.build()
// Using a cached FlutterEngine.
val flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
.shouldAttachEngineToActivity(false)
.build()
// Using a new FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
.shouldAttachEngineToActivity(false)
.build();
// Using a cached FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
.shouldAttachEngineToActivity(false)
.build();
将false
传递给shouldAttachEngineToActivity()
的Builder
方法可以阻止Flutter与周围的Activity
进行交互。默认值为true
,这允许Flutter和Flutter插件与周围的Activity
进行交互。
除非另有说明,否则本网站上的文档反映了Flutter的最新稳定版本。页面最后更新于 2024-06-26。 查看源代码 或 报告问题.