注意:php
啓動頁(launch screen)和閃屏頁(splash screen)不是一個概念。html
如今大部分App都有啓動頁,那麼爲何要有啓動頁?這是個值得思考的問題,若是沒有啓動頁會怎樣,大部分的App會白屏(也有多是黑屏,主題設置有關係)很是短的時間,而後才能展現App的主要內容。java
有不少人說閃屏和啓動頁是同一個概念,還有人說要避免使用閃屏,那麼咱們到底用不用閃屏呢?下面我就簡單說一下閃屏和啓動頁的區別。android
IOSios
根據iOS Human Interface Guidelines (2014-03-10) (iOS 7 Fin)文檔中有這樣的一句描述As much as possible, avoid displaying a splash screen or other startup experience,看來IOS是不太喜歡讓設計用啓動頁。IOS的同窗必定會有疑問,iOS Human Interface Guidelines 最新版Launch Screen又有這樣一句話,Every app must supply a launch screen,臥槽這是啥?自相矛盾嗎?下面咱們儘量的解決這個問題,閃屏和啓動頁根本就不是一個概念。git
閃屏(splash screen)指的是違反了App的設計規範的第一屏,這個第一屏看起來與應用程序的設計徹底不一樣!例如,可能包含靜態文本,如版權聲明或版本信息,還有就是應用已經啓動還繼續顯示logo(我就見過這樣的代碼,寫個定時器,2秒後跳轉到主頁),廣告等內容,而不是顯示用戶可操做的UI。github
啓動頁(launch screen)是指在符合設計規範的第一屏,這個第一屏看起來與應用程序的設計基本相同,而且短暫顯示後跳轉到用戶可用的UI。app
Android框架
Android在MATERIAL DESIGN - Launch screen也有本身的設計規範,主要有兩個方式,一個是顯示品牌和UI佔位符。ide
因此答案是:在開發中不要用閃屏,可是必定要有啓動頁。
特別感謝 高洋 同窗的提問,讓我重新思考了上面的問題。
爲何要談Android的啓動流程呢?由於Flutter啓動的時候,依賴的是Android的運行環境,其本質是Activity上添加了一個FlutterView,FlutterView繼承SurfaceView,那麼就容易理解了,Flutter的所有頁面都是渲染到了FlutterView上,若是不熟悉Flutter的啓動流程能夠參考我寫的Flutter啓動流程 這篇文章,下面是對Flutter啓動的一個簡單描述。
在Flutter中,啓動頁的做用是在FlutterView顯示第一幀以前,不要出現白屏,在FlutterView顯示第一幀以前,咱們分紅兩個階段,Android啓動階段和Flutter啓動階段,Android啓過程添加啓動頁很是容易,在主題xml中添加android:windowBackground
屬性,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>
複製代碼
最終效果以下,沒有黑屏,很是順滑。