逃離部落格園,搬運一篇2015年做手遊時期的舊文。
簡約至上 少寫程式碼
為什麼需要閃屏
- 手機應用程式不應該有閃屏, Google Android 自家的 App 據說已經全面禁用閃屏。
- 中國大量手機應用程式,或者說相關從業人員依舊堅持必須存在一個閃屏圖片的審美。
為什麼需要原生實現
- UnrealEngine3 需要
UE3 初始化和場景切換時,渲染執行緒暫停,因此需要使用原生方案顯示圖片或視訊來過渡等待。 - cocos2d-x 需要
cocos2d-x 論壇相關討論帖 - Unity3D
大概類似,未考證。
為什麼不應該使用 Splash Activity
“你搜到的都是錯的”
網上搜尋 Android 閃屏實現方案,99%的結果都是介紹如何使用一個簡單的 Activity
實現,再過渡切換到真正的 GameActivity
。這種方案僅限教學,實際應用有很多弊端。
- 這種方案要求在
AndroidManifest.xml
中配置的LaunchActivity
必須是SplashActivity
。
當需要實現帶引數Intent
啟動時,SplashActivity
需要正確地傳遞引數(Intent
)給GameActivity
。繁瑣。 - 遊戲使用
NDK
開發,OpenGL
,UI View
,thread
需要跟GameActivity's SurfaceView
繫結。
SplashActivity
顯示期間,GameActivity
無法被載入,因此也無法並行載入遊戲引擎相關例項。導致閃屏過後,GameActivity
仍需一個載入介面用於過渡等待GameEngine
的啟動耗時。 - 遊戲需要接入各種 SDK。很多 SDK 要求在
GameActivity
的生命週期插入諸多 hook 事件程式碼。
例如onCreate(), onStart(), onResume(), onPause(), onStop(), onDestroy()
等等,這些是常用hook
位置。
SplashActivity
方案使相關邏輯實現更復雜。
一種適合遊戲的簡單閃屏實現方案
- 使用一個全屏
Dialog
Android Dialog
擁有獨立的Window
,與GameView
無耦合。 - 遮蔽
User Input Event
Dialog
預設接收所有User Input Event
,不需要傳遞給GameView
,因此與遊戲邏輯無耦合。 - 實現動畫
可以很方便的使用各種原生Android Animation
,實現可用的過渡動畫呈現。 - 動畫結束後自動消失
Dialog
可以自我管理生命週期,再次與遊戲無耦合。 - 並行載入遊戲例項
GameSurfaceView and GameEngine
可以在Dialog
顯示期間,後臺並行載入,無耦合,且真正達到非同步和節省時間的目標。
程式碼示例
- 建立全屏 Dialog
public class NSSplashDialog extends Dialog {
private PercentFrameLayout mLayout = null;
private ImageView mImageView = null;
public NSSplashDialog(Context context) {
super(context, android.R.style.Theme_NoTitleBar_Fullscreen);
setContentView(R.layout.splash);
mLayout = (PercentFrameLayout)findViewById(R.id.layout_splash);
mImageView = (ImageView)this.findViewById(R.id.iv_splash);
}
}
複製程式碼
2 . 遮蔽 User Input Event
setCanceledOnTouchOutside(false);
setCancelable(false);
複製程式碼
3 . 實現動畫
private AlphaAnimation mAnimation = null;
private int mBitmapIndex = 0;
mAnimation = new AlphaAnimation(0.0f, 1.0f); //fade in, fade out
mAnimation.setDuration(2000);//2 seconds
mAnimation.setRepeatCount(3); //show 4 images
mAnimation.setAnimationListener(new Animation.AnimationListener(){
@Override
public void onAnimationStart(Animation animation) {
mBitmapIndex = 0;
mLayout.setBackgroundColor(Color.WHITE);
mImageView.setImageDrawable(BitmapUtil.loadDrawable(getContext(), R.drawable.splash0));
}
@Override
public void onAnimationEnd(Animation animation) {
mBitmapIndex = 0;
kick(false);
}
@Override
public void onAnimationRepeat(Animation animation) {
mBitmapIndex++;
switch(mBitmapIndex) {
case 1:
mLayout.setBackgroundColor(Color.WHITE);
mImageView.setImageDrawable(BitmapUtil.loadDrawable(getContext(), R.drawable.splash1));
break;
case 2:
mLayout.setBackgroundColor(Color.BLACK);
mImageView.setImageDrawable(BitmapUtil.loadDrawable(getContext(), R.drawable.splash2));
break;
case 3:
mLayout.setBackgroundColor(Color.BLACK);
mImageView.setImageDrawable(BitmapUtil.loadDrawable(getContext(), R.drawable.splash3));
break;
default:
break;
}
}
});
mImageView.setAnimation(mAnimation);
複製程式碼
4 . 動畫結束後自動消失
在onAnimationEnd()
中呼叫kick(false)
,即關閉自己。
實測發現部分系統有bug:onAnimationEnd() 和 cancel() 可能會死迴圈,因此新增保護邏輯判斷 hasEnded()
public void kick(boolean show) {
if(show) {
show();
mAnimation.start();
} else {
if(!mAnimation.hasEnded()) {
mAnimation.cancel();
}
dismiss();
}
}
複製程式碼
5 . 閃屏與遊戲並行載入
GameActivity
生命週期中, 在 onCreate()
建立 SplashDialog
例項,在 onDestroy()
清除 SplashDialog
例項。
//Create
mSplashDialog = new NSSplashDialog(this);
mSplashDialog.kick(true);
//Destroy
if(mSplashDialog != null && mSplashDialog.isShowing()) {
mSplashDialog.kick(false);
}
複製程式碼
Loading View
閃屏說完了,最後提一下 Loading View
。
上面說到 UE3 在場景切換時,需要使用平臺原生介面做過渡展示。根據業務需求不同,可能有時候不便複用 SplashDialog
,那可以使用一個獨立 layout View
實現。