Flutter啟動(dòng)頁(yè)(閃屏頁(yè))的具體實(shí)現(xiàn)及原理詳析

為什么要有啟動(dòng)頁(yè)?
在以下文章中,啟動(dòng)頁(yè)就是閃屏頁(yè)。
現(xiàn)在大部分App都有啟動(dòng)頁(yè),那么為什么要有啟動(dòng)頁(yè)?這是個(gè)值得思考的問(wèn)題,如果沒(méi)有啟動(dòng)頁(yè)會(huì)怎樣,大部分的App會(huì)白屏(也有可能是黑屏,主題設(shè)置有關(guān)系)非常短的時(shí)間,然后才能展示App的內(nèi)容。
那么問(wèn)題來(lái)了,一定要有啟動(dòng)頁(yè)嗎?答案:不是,而且是盡可能不要有啟動(dòng)頁(yè),因?yàn)閱?dòng)頁(yè)會(huì)讓用戶體驗(yàn)不夠連貫,甚至IOS在開(kāi)發(fā)手冊(cè)上就不推薦使用啟動(dòng)頁(yè)。
我們深入思考一下,既然不推薦為什么這樣流行,答案非常簡(jiǎn)單,啟動(dòng)頁(yè)的成本非常低,如果你想把的App啟動(dòng)優(yōu)化到一個(gè)非常短的時(shí)間,還是有一定成本的。
Android啟動(dòng)流程
為什么要談Android的啟動(dòng)流程呢?因?yàn)镕lutter啟動(dòng)的時(shí)候,依賴的是Android的運(yùn)行環(huán)境,其本質(zhì)是Activity上添加了一個(gè)FlutterView,F(xiàn)lutterView繼承SurfaceView,那么就容易理解了,F(xiàn)lutter的全部頁(yè)面都是渲染到了FlutterView上,如果不熟悉Flutter的啟動(dòng)流程可以參考Flutter啟動(dòng)流程 這篇文章,下面是對(duì)Flutter啟動(dòng)的一個(gè)簡(jiǎn)單描述。

在Flutter中,啟動(dòng)頁(yè)的作用是在FlutterView顯示第一幀之前,不要出現(xiàn)白屏,在FlutterView顯示第一幀之前,我們分成兩個(gè)階段,Android啟動(dòng)階段和Flutter啟動(dòng)階段,Android啟過(guò)程添加啟動(dòng)頁(yè)非常容易,在主題xml中添加android:windowBackground屬性,F(xiàn)lutter怎么添加啟動(dòng)頁(yè)呢?其實(shí)框架已經(jīng)幫助咱們實(shí)現(xiàn)好了,我下面就給大家說(shuō)一下原理。
Flutter啟動(dòng)頁(yè)具體實(shí)現(xiàn)和原理
創(chuàng)建一個(gè)SplashActivity,這Activity繼承FlutterActivity,重寫onCreate()方法,在onCreate()方法中調(diào)用GeneratedPluginRegistrant.registerWith() ,下面是啟動(dòng)頁(yè)的代碼。
public class SplashActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
}
在Manifest中添加SplashActivity作為App的啟動(dòng)Activity,設(shè)置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一定要設(shè)置成true,一定要設(shè)置成true,一定要設(shè)置成true重要的事情說(shuō)三遍,如果這個(gè)屬性設(shè)置成false,效果是這樣的。

從現(xiàn)象觀察,啟動(dòng)頁(yè)中間有一段時(shí)間黑屏,這個(gè)為什么呢?前面我們說(shuō)過(guò),F(xiàn)lutter的啟動(dòng)流程分成兩部分,一部分是Android啟動(dòng)階段,一個(gè)是Flutter的啟動(dòng)階段,這個(gè)黑屏就是Flutter的啟動(dòng)階段沒(méi)有啟動(dòng)頁(yè)所造成的。我們從源碼入手,詳細(xì)分析一下,下面是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
這個(gè)段代碼很容易理解,創(chuàng)建一個(gè)LaunchView,主要邏輯在createLaunchView()中,原理也很簡(jiǎn)單,根據(jù)主題中的R.attr.windowBackground屬性,生成一個(gè)Drawable,然后創(chuàng)建了一個(gè)View,并且把這個(gè)View的背景設(shè)置成Drawable。
注釋3
showSplashScreenUntilFirstFrame()是得到Manifet中io.flutter.app.android.SplashScreenUntilFirstFrame的屬性的值,如果是false,那么久返回一個(gè)空的的LaunchView,也就不會(huì)執(zhí)行注釋2的代碼。這就是我們上面說(shuō)的如果設(shè)置成false就顯示黑屏的原因。
注釋2
調(diào)用addLaunchView(),這方法也很簡(jiǎn)單,首先看注釋4,把LaunchView添加到當(dāng)前的Activity中,然后添加了一個(gè)監(jiān)聽(tīng),在注釋5處,這個(gè)監(jiān)聽(tīng)是當(dāng)FlutterView第一幀加載完成后回調(diào),回調(diào)做了什么事情呢?很簡(jiǎn)單,把LaunchView刪除了,顯示FlutterView的第一幀。
總結(jié)一下,就是把Android的啟動(dòng)頁(yè)生成一個(gè)Drawable,創(chuàng)建了一個(gè)LaunchView,把Drawable設(shè)置成LaunchView的背景,當(dāng)前的Activity添加這LaunchView,如果FlutterView的第一幀顯示了,把LaunchView刪除。
設(shè)置主題,下面是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>
最終效果如下,沒(méi)有黑屏,非常順滑。

總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
- 使用Flutter開(kāi)發(fā)的抖音國(guó)際版實(shí)例代碼詳解
- Flutter進(jìn)階之實(shí)現(xiàn)動(dòng)畫(huà)效果(一)
- Flutter 超實(shí)用簡(jiǎn)單菜單彈出框 PopupMenuButton功能
- flutter InkWell實(shí)現(xiàn)水波紋點(diǎn)擊效果
- 詳解Flutter WebView與JS互相調(diào)用簡(jiǎn)易指南
- Flutter持久化存儲(chǔ)之?dāng)?shù)據(jù)庫(kù)存儲(chǔ)(sqflite)詳解
- Flutter中如何加載并預(yù)覽本地的html文件的方法
- flutter日期選擇器 flutter時(shí)間選擇器
- 詳解flutter之網(wǎng)絡(luò)請(qǐng)求dio,請(qǐng)求,攔截器簡(jiǎn)單示例
- Flutter實(shí)現(xiàn)抖音點(diǎn)贊效果
相關(guān)文章
Android實(shí)現(xiàn)新增及編輯聯(lián)系人的方法
這篇文章主要介紹了Android實(shí)現(xiàn)新增及編輯聯(lián)系人的方法,是Android應(yīng)用開(kāi)發(fā)常見(jiàn)的功能,需要的朋友可以參考下2014-07-07
Android實(shí)現(xiàn)倒計(jì)時(shí)結(jié)束后跳轉(zhuǎn)頁(yè)面功能
最近在工作中遇到一個(gè)需求,需要在倒計(jì)時(shí)一段時(shí)間后進(jìn)行跳轉(zhuǎn)頁(yè)面,通過(guò)查找相關(guān)資料發(fā)現(xiàn)其中涉及的知識(shí)還不少,所以分享出來(lái),下面這篇文章主要給大家介紹了關(guān)于Android實(shí)現(xiàn)倒計(jì)時(shí)結(jié)束后跳轉(zhuǎn)頁(yè)面功能的相關(guān)資料,需要的朋友可以參考下。2017-11-11
Retrofit2.0 實(shí)現(xiàn)圖文(參數(shù)+圖片)上傳方法總結(jié)
本篇文章主要介紹了Retrofit2.0 實(shí)現(xiàn)圖文(參數(shù)+圖片)上傳方法總結(jié),具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08
Android巧用ViewPager實(shí)現(xiàn)左右循環(huán)滑動(dòng)圖片
這篇文章主要為大家詳細(xì)介紹了Android巧用ViewPager實(shí)現(xiàn)左右循環(huán)滑動(dòng)圖片的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-05-05
Android ViewFlipper的詳解及實(shí)例
這篇文章主要介紹了Android ViewFlipper的詳解及實(shí)例的相關(guān)資料,通過(guò)本文希望能幫助大家理解這部分內(nèi)容,需要的朋友可以參考下2017-08-08
android使用handler ui線程和子線程通訊更新ui示例
這篇文章主要介紹了android使用handler ui線程和子線程通訊更新ui的方法,大家參考使用吧2014-01-01
Android百度地圖應(yīng)用之創(chuàng)建顯示地圖
這篇文章主要為大家詳細(xì)介紹了Android百度地圖應(yīng)用之創(chuàng)建顯示地圖,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-06-06
Android實(shí)現(xiàn)擴(kuò)展Menu的方法
這篇文章主要介紹了Android實(shí)現(xiàn)擴(kuò)展Menu的方法,涉及Android操作menu菜單的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10

