Flutter启动页(闪屏页)的具体实现及原理详析
为什么要有启动页?
在以下⽂章中,启动页就是闪屏页。
现在⼤部分App都有启动页,那么为什么要有启动页?这是个值得思考的问题,如果没有启动页会怎样,⼤部分的App会⽩屏(也有可能是⿊屏,主题设置有关系)⾮常短的时间,然后才能展⽰App的内容。
那么问题来了,⼀定要有启动页吗?答案:不是,⽽且是尽可能不要有启动页,因为启动页会让⽤户体验不够连贯,甚⾄IOS 在开发⼿册上就不推荐使⽤启动页。
我们深⼊思考⼀下,既然不推荐为什么这样流⾏,答案⾮常简单,启动页的成本⾮常低,如果你想把的App启动优化到⼀个⾮常短的时间,还是有⼀定成本的。
Android启动流程
为什么要谈Android的启动流程呢?因为Flutter启动的时候,依赖的是Android的运⾏环境,其本质是Activity上添加了⼀个FlutterView,FlutterView继承SurfaceView,那么就容易理解了,Flutter的全部页⾯都是渲染到了FlutterView上,如果不熟悉Flutter的启动流程可以参考这篇⽂章,下⾯是对Flutter启动的⼀个简单描述。
在Flutter中,启动页的作⽤是在FlutterView显⽰第⼀帧之前,不要出现⽩屏,在FlutterView显⽰第⼀帧之前,我们分成两个阶段,Android启动阶段和Flutter启动阶段,Android启过程添加启动页⾮常容易,在主题xml中添加android:windowBackground 属性,Flutter怎么添加启动页呢?其实框架已经帮助咱们实现好了,我下⾯就给⼤家说⼀下原理。
Flutter启动页具体实现和原理
创建⼀个SplashActivity,这Activity继承FlutterActivity,重写onCreate()⽅法,在onCreate()⽅法中调
⽤isterWith(),下⾯是启动页的代码。
public class SplashActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
}
}
在Manifest中添加SplashActivity作为App的启动Activity,设置SplashActivity的主题是LaunchTheme。下⾯是Manifest的配置⽂件。
<activity
android:name=".SplashActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
meta-data的name = "io.flutter.app.android.SplashScreenUntilFirstFrame"的value⼀定要设置成true,⼀定要设置成true,⼀定要设置成true重要的事情说三遍,如果这个属性设置成false,效果是这样的。
从现象观察,启动页中间有⼀段时间⿊屏,这个为什么呢?前⾯我们说过,Flutter的启动流程分成两部分,⼀部分是Android 启动阶段,⼀个是Flutter的启动阶段,这个⿊屏就是Flutter的启动阶段没有启动页所造成的。我们从源码⼊⼿,详细分析⼀下,下⾯是FlutterActivityDelegate的部分源码。
public final class FlutterActivityDelegate
implements FlutterActivityEvents,
FlutterView.Provider,
PluginRegistry {
private static final String SPLASH_SCREEN_META_DATA_KEY = "io.flutter.app.android.SplashScreenUntilFirstFrame"; private View launchView;
@Override
public void onCreate(Bundle savedInstanceState) {
String[] args = Intent());
flutterView = ateFlutterView(activity);
if (flutterView == null) {
FlutterNativeView nativeView = ateFlutterNativeView();
flutterView = new FlutterView(activity, null, nativeView);
flutterView.setLayoutParams(matchParent);
activity.setContentView(flutterView);
launchView = createLaunchView();//1
if (launchView != null) {
addLaunchView();//2
}
}
}
private View createLaunchView() {
if (!showSplashScreenUntilFirstFrame()) {//3
return null;
}
final Drawable launchScreenDrawable = getLaunchScreenDrawableFromActivityTheme();
final View view = new View(activity);
view.setBackground(launchScreenDrawable);
return view;
}
private Drawable getLaunchScreenDrawableFromActivityTheme() {
//省略了部分代码
try {
Resources().sourceId);
} catch (NotFoundException e) {
return null;
}
}
private Boolean showSplashScreenUntilFirstFrame() {
try {
ActivityInfo activityInfo = PackageManager().getActivityInfo(
PackageManager.GET_META_DATA|PackageManager.GET_ACTIVITIES);
Bundle metadata = aData;
return metadata != null && Boolean(SPLASH_SCREEN_META_DATA_KEY);
} catch (NameNotFoundException e) {
return false;
}
}
private void addLaunchView() {
activity.addContentView(launchView, matchParent);//4
flutterView.addFirstFrameListener(new FlutterView.FirstFrameListener() {//5
@Override
public void onFirstFrame() {
FlutterActivityDelegate.this.launchView.animate()
.
alpha(0f)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
((ViewGroup) FlutterActivityDelegate.Parent())
.removeView(FlutterActivityDelegate.this.launchView);//5
}
});
}
});
activity.setTheme(android.R.style.Theme_Black_NoTitleBar);
}
flutter开发app}
注释1
这个段代码很容易理解,创建⼀个LaunchView,主要逻辑在createLaunchView()中,原理也很简单,根据主题中
的R.attr.windowBackground属性,⽣成⼀个Drawable,然后创建了⼀个View,并且把这个View的背景设置成Drawable。
注释3
showSplashScreenUntilFirstFrame()是得到Manifet中io.flutter.app.android.SplashScreenUntilFirstFrame的属性的值,如果是false,那么久返回⼀个空的的LaunchView,也就不会执⾏注释2的代码。这就是我们上⾯说的如果设置成false就显⽰⿊屏的原因。
注释2
调⽤addLaunchView(),这⽅法也很简单,⾸先看注释4,把LaunchView添加到当前的Activity中,然后添加了⼀个监听,在注释5处,这个监听是当FlutterView第⼀帧加载完成后回调,回调做了什么事情呢?很简单,把LaunchView删除了,显⽰FlutterView的第⼀帧。
总结⼀下,就是把Android的启动页⽣成⼀个Drawable,创建了⼀个LaunchView,把Drawable设置成LaunchView的背景,当前的Activity添加这LaunchView,如果FlutterView的第⼀帧显⽰了,把LaunchView删除。
设置主题,下⾯是LaunchTheme的代码。
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
</resources>
下⾯是launch_background的代码。
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="schemas.android/apk/res/android"
android:opacity="opaque">
<item>
<bitmap android:src="@mipmap/ic_launch_bg" />
</item>
<item
android:width="90dp"
android:height="90dp"
android:gravity="center">
<bitmap android:src="@mipmap/ic_launch_logo" />
</item>
</layer-list>
最终效果如下,没有⿊屏,⾮常顺滑。
总结
以上就是这篇⽂章的全部内容了,希望本⽂的内容对⼤家的学习或者⼯作具有⼀定的参考学习价值,谢谢⼤家对的⽀持。