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