由于APP启动、Flutter初始化的时延造成第一个画面呈现明显滞后,所以需要一个过渡页面——启动页,直至flutter的第一个页面渲染完成。
官方推荐方案:
方案一:
初始化应用
@drawable/launch_background @drawable/normal_background
在AndroidManifest.xml中设置FlutterActivity
在AndroidManifest.xml中,设置theme的 FlutterActivity,以推出主题。然后,将元数据元素添加到所需的位置,FlutterActivity 以指示Flutter在适当的时间从启动主题切换到正常主题。
android:name=".MyActivity" android:theme="@style/LaunchTheme" // ... > android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/NormalTheme" />
效果:
隐藏Navigation Bar之后的效果:
从视频可以看出APP启动之后body部分变成了黑色,而通知栏区域显示正常,而body部分正式flutter开始渲染的部分,分析一下flutterActivity:
protected void onCreate(@Nullable Bundle savedInstanceState) { this.switchLaunchThemeForNormalTheme(); super.onCreate(savedInstanceState); this.lifecycle.handleLifecycleEvent(Event.ON_CREATE); this.delegate = new FlutterActivityAndFragmentDelegate(this); this.delegate.onAttach(this); this.delegate.onActivityCreated(savedInstanceState); this.configureWindowForTransparency(); this.setContentView(this.createFlutterView()); this.configureStatusBarForFullscreenFlutterExperience();}
当置空setContentView接口时,启动页正常了,黑色区域变成了目标view,猜测是这个函数里面在设置View的时候做了特殊操作,进一步查看:
View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { Log.v("FlutterActivityAndFragmentDelegate", "Creating FlutterView."); this.ensureAlive(); if (this.host.getRenderMode() == RenderMode.surface) { FlutterSurfaceView flutterSurfaceView = new FlutterSurfaceView(this.host.getActivity(), this.host.getTransparencyMode() == TransparencyMode.transparent); this.host.onFlutterSurfaceViewCreated(flutterSurfaceView); this.flutterView = new FlutterView(this.host.getActivity(), flutterSurfaceView); } else { FlutterTextureView flutterTextureView = new FlutterTextureView(this.host.getActivity()); this.host.onFlutterTextureViewCreated(flutterTextureView); this.flutterView = new FlutterView(this.host.getActivity(), flutterTextureView); } this.flutterView.addOnFirstFrameRenderedListener(this.flutterUiDisplayListener); this.flutterSplashView = new FlutterSplashView(this.host.getContext()); if (VERSION.SDK_INT >= 17) { this.flutterSplashView.setId(View.generateViewId()); } else { this.flutterSplashView.setId(486947586); } this.flutterSplashView.displayFlutterViewWithSplash(this.flutterView, this.host.provideSplashScreen()); Log.v("FlutterActivityAndFragmentDelegate", "Attaching FlutterEngine to FlutterView."); this.flutterView.attachToFlutterEngine(this.flutterEngine); return this.flutterSplashView;}
这个函数里面new了一个FlutterSurfaceView,它被add到了FlutterView,而FlutterView又被添加到了FlutterSplashView,最终反馈的是FlutterSplashView,如果对SurfaceView有一点了解的就能猜到,它渲染的时候默认就是黑色,所以这个是不是启动页被黑色的SurfaceView覆盖导致的异常?
override open fun setContentView(view: View) { if(view is ViewGroup) { val v0 = view.getChildAt(0) if(v0 is ViewGroup) { val v1 = v0.getChildAt(0) if(v1 is SurfaceView) { v1.setZOrderOnTop(true) v1.getHolder().setFormat(PixelFormat.TRANSLUCENT) } } } super.setContentView(view)}
运行之后发现黑色区域不见了,启动页正常显示。
WHY
分析FlutterSurfaceView:
private void init() { if (this.renderTransparently) { this.getHolder().setFormat(-2); this.setZOrderOnTop(true); } this.getHolder().addCallback(this.surfaceCallback); this.setAlpha(0.0F);}
在FlutterSurfaceView的构造函数中做了上面操作,只是有条件:this.renderTransparently,它来自:
FlutterSurfaceView flutterSurfaceView = new FlutterSurfaceView(this.host.getActivity(), this.host.getTransparencyMode() == TransparencyMode.transparent);
FlutterActivity:
@NonNullpublic TransparencyMode getTransparencyMode() { return this.getBackgroundMode() == BackgroundMode.opaque ? TransparencyMode.opaque : TransparencyMode.transparent;}@NonNullprotected BackgroundMode getBackgroundMode() { return this.getIntent().hasExtra("background_mode") ? BackgroundMode.valueOf(this.getIntent().getStringExtra("background_mode")) : BackgroundMode.opaque;}
可以通过Intent方式传入,意外的发现,在此Activity里面有两个公开的Builder类:
public static class NewEngineIntentBuilder { private final Class extends FlutterActivity> activityClass; private String initialRoute = "/"; private String backgroundMode; public NewEngineIntentBuilder(@NonNull Class extends FlutterActivity> activityClass) { this.backgroundMode = FlutterActivityLaunchConfigs.DEFAULT_BACKGROUND_MODE; this.activityClass = activityClass; } @NonNull public FlutterActivity.NewEngineIntentBuilder initialRoute(@NonNull String initialRoute) { this.initialRoute = initialRoute; return this; } @NonNull public FlutterActivity.NewEngineIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) { this.backgroundMode = backgroundMode.name(); return this; } @NonNull public Intent build(@NonNull Context context) { return (new Intent(context, this.activityClass)).putExtra("route", this.initialRoute).putExtra("background_mode", this.backgroundMode).putExtra("destroy_engine_with_activity", true); }}
通过他可以帮助参数传入,这让意味着需要手动启动。
使用点9图片作为启动页:
如果启动页是一张使用点9效果的图片,又出现异常(bug这么多?):
方案二:
设置Flutter启动Drawable:使用SplashScreenDrawable参数
FlutterActivity要显示一个Drawable作为启动画面,以下元数据添加到关联FlutterActivity的AndroidManifest.xml。
android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/my_splash" />
android:name="io.flutter.app.FlutterApplication" android:label="flutter_test_0" android:icon="@mipmap/ic_launcher"> android:name=".MainActivity" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"> android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/launch_background" />
@drawable/launch_background
android:gravity="bottom|center_horizontal" android:drawable="@mipmap/x_launch"/>
效果如下,完美呈现:
但当为点9图片时,又出现异常,点9区域将被遗弃,flutter将只占用之外的区域,这是flutter的bug:
最终方案:
一、新增一个过渡Activity:
android:name=".LaunchActivity" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:screenOrientation="portrait" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
class LaunchActivity: Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val intent = Intent() intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); intent.setClass(this, MainActivity::class.java) if(savedInstanceState != null) { intent.putExtras(savedInstanceState) } startActivity(intent) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) { overridePendingTransition(0, 0) } } override fun onPause() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) { overridePendingTransition(0, 0) } super.onPause() finish() }}
二、重新设置FutterActivity
android:name=".MainActivity" android:launchMode="singleTop" android:theme="@style/FlutterTheme" android:screenOrientation="portrait" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"> android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@mipmap/papp_launch_nine" />
三、设置theme
@null @null @null @null @null @null @null @null @null @null @null @null @style/noAnimation @drawable/launch_background @style/noAnimation
四、设置背景Drawable
更新:
新版本Flutter V1.22.2已解决:
android:name=".MainActivity" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"> android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/NormalTheme" /> android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/launch_background" />
@drawable/launch_background @android:color/white
使用点9图片或者普通图片:
layer-list>