逃离博客园,搬运一篇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 outmAnimation.setDuration(2000);//2 secondsmAnimation.setRepeatCount(3); //show 4 imagesmAnimation.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
实现。