逃离博客园,搬运一篇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 实现。