Flutter启动页(闪屏页)的具体实现及原理详析
为什么要有启动页?
在以下文章中,启动页就是闪屏页。
现在大部分app都有启动页,那么为什么要有启动页?这是个值得思考的问题,如果没有启动页会怎样,大部分的app会白屏(也有可能是黑屏,主题设置有关系)非常短的时间,然后才能展示app的内容。
那么问题来了,一定要有启动页吗?答案:不是,而且是尽可能不要有启动页,因为启动页会让用户体验不够连贯,甚至ios在开发手册上就不推荐使用启动页。
我们深入思考一下,既然不推荐为什么这样流行,答案非常简单,启动页的成本非常低,如果你想把的app启动优化到一个非常短的时间,还是有一定成本的。
android启动流程
为什么要谈android的启动流程呢?因为flutter启动的时候,依赖的是android的运行环境,其本质是activity上添加了一个flutterview,flutterview继承surfaceview,那么就容易理解了,flutter的全部页面都是渲染到了flutterview上,如果不熟悉flutter的启动流程可以参考flutter启动流程 这篇文章,下面是对flutter启动的一个简单描述。
在flutter中,启动页的作用是在flutterview显示第一帧之前,不要出现白屏,在flutterview显示第一帧之前,我们分成两个阶段,android启动阶段和flutter启动阶段,android启过程添加启动页非常容易,在主题xml中添加android:windowbackground属性,flutter怎么添加启动页呢?其实框架已经帮助咱们实现好了,我下面就给大家说一下原理。
flutter启动页具体实现和原理
创建一个splashactivity,这activity继承flutteractivity,重写oncreate()方法,在oncreate()方法中调用generatedpluginregistrant.registerwith()
,下面是启动页的代码。
public class splashactivity extends flutteractivity { @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); generatedpluginregistrant.registerwith(this); } }
在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 = getargsfromintent(activity.getintent()); fluttermain.ensureinitializationcomplete(activity.getapplicationcontext(), args); flutterview = viewfactory.createflutterview(activity); if (flutterview == null) { flutternativeview nativeview = viewfactory.createflutternativeview(); 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 { return activity.getresources().getdrawable(typedvalue.resourceid); } catch (notfoundexception e) { return null; } } private boolean showsplashscreenuntilfirstframe() { try { activityinfo activityinfo = activity.getpackagemanager().getactivityinfo( activity.getcomponentname(), packagemanager.get_meta_data|packagemanager.get_activities); bundle metadata = activityinfo.metadata; return metadata != null && metadata.getboolean(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.this.launchview.getparent()) .removeview(flutteractivitydelegate.this.launchview);//5 } }); } }); activity.settheme(android.r.style.theme_black_notitlebar); } }
注释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="http://schemas.android.com/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>
最终效果如下,没有黑屏,非常顺滑。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。