SplashScreen

VC++工程中加入 SplashScreen 原理释解


作者:Abbey

摘要
  本文旨在剖析VC++工程中加入 SplashScreen 的原理,并给出在VC++ MFC(exe)工程中加入 SplashScreen 的步骤。

关键字 SplashScreen,原理

环境:Windows 98SE/2000,VC++ 6.0
  SplashScreen - 我们使用 Word 之类的软件在启动的短暂时间里就会看到它的身影。它通常用以在程序启动时显示程序及用户名称,版权信息等。我也不知道它准确的名称是什么(是闪屏吗?),就这样称呼吧。也许你也想在自己的工程里加入这样的特性,本文将以创建实际工程的方式逐步剖析其实现原理。

注意:为避免实际所使用工程名给类或对象名带来的干扰,除非特别说明,在本文中将使用基类名如CWinApp、CMainFrame、CDialog来代替实际工程中的相应派生类名进行描述。
  Visual C++是一个相当强大的C++开发工具,它内嵌了对SplashScreen的支持。但是在MFC EXE类型工程中只是对带有主框架类的SDI或MDI工程提供了这一支持,基于对话框类的工程则被排除在外。现在让我们开始吧。第一步是在SDI工程中加入SplashScreen。
  首先利用AppWizard生成一个SDI工程,除了其中Docking ToolBar必须选择外(我认为这是MFC的一个Bug,当然这与本文讨论的SplashScreen没有关系),其他的文档-视图支持、状态条之类的都可以不要,这样可以尽量减少无用的代码。
  通过IDE中的菜单Project->Add to Project->Components and Controls,我们就可以从Visual C++ Components中选择Splash Screen这个组件插入工程。



  在点击了"Insert"后会弹出一个如下图所示的对话框,这是设置插入该工程中的SplashScreen的类名、显示用位图的ID及文件名,采用缺省值即可。



  通过以上几步的操作,就会在工程目录下生成Splash.CPP和Splash.H文件,这便是CSplashWnd类的实现文件与头文件。同时工程中CWinApp与CMainFrame类中的部分代码也会被修改,以实现CSplashWnd窗口的消息处理。
  接着我们来看看 CSplashWnd 类的声明与主要的代码(已经过删减):


//类的声明

class CSplashWnd : public CWnd
{
CSplashWnd();
~CSplashWnd();
virtual void PostNcDestroy();

static void EnableSplashScreen(BOOL bEnable = TRUE);
static void ShowSplashScreen(CWnd* pParentWnd = NULL);
static BOOL PreTranslateAppMessage(MSG* pMsg);
BOOL Create(CWnd* pParentWnd = NULL);
void HideSplashScreen();

afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnPaint();
afx_msg void OnTimer(UINT nIDEvent);

CBitmap m_bitmap; //SplashScreen窗口显示用的位图对象
static BOOL c_bShowSplashWnd; //是否要显示SplashScreen的标志量
static CSplashWnd* c_pSplashWnd;
};
//是否使用SplashScreen void CSplashWnd::EnableSplashScreen(BOOL bEnable)
{
c_bShowSplashWnd = bEnable;
}

//创建CsplashWnd对象,并调用Create()创建窗口 void CSplashWnd::ShowSplashScreen(CWnd* pParentWnd)
{
//如果不要显示SplashScreen或SplashWnd对象已经被创建则返回
if (!c_bShowSplashWnd || c_pSplashWnd != NULL)
return;

c_pSplashWnd = new CSplashWnd;

if (!c_pSplashWnd->Create(pParentWnd))
delete c_pSplashWnd;
else
c_pSplashWnd->UpdateWindow();
}

//装入SplashScreen欲显示位图,通过CreateEx()激发OnCreate()完成窗口创建与设置 BOOL CSplashWnd::Create(CWnd* pParentWnd)
{
if (!m_bitmap.LoadBitmap(IDB_SPLASH))
return FALSE;

BITMAP bm;
m_bitmap.GetBitmap(&bm);

return CreateEx(0, AfxRegisterWndClass(0,AfxGetApp()->LoadStandardCursor(IDC_ARROW)), NULL, WS_POPUP | WS_VISIBLE, 0, 0, bm.bmWidth, bm.bmHeight, pParentWnd->GetSafeHwnd(), NULL);
}

//销毁窗口,刷新框架 void CSplashWnd::HideSplashScreen()
{
DestroyWindow();
AfxGetMainWnd()->UpdateWindow();
}

//利用窗口创建结构创建窗口,并设置定时器在750ms后触发OnTimer() int CSplashWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CWnd::OnCreate(lpCreateStruct) == -1)
return -1;

CenterWindow(); //窗口居中显示
SetTimer(1, 750, NULL); //设置定时器

return 0;
}

//将键盘和鼠标消息传递给CSplashWnd对象,以销毁窗口BOOL CSplashWnd::PreTranslateAppMessage(MSG* pMsg)
{
if (c_pSplashWnd == NULL)
return FALSE;

if (pMsg->message == WM_KEYDOWN ||
pMsg->message == WM_SYSKEYDOWN ||
pMsg->message == WM_LBUTTONDOWN ||
pMsg->message == WM_RBUTTONDOWN ||
pMsg->message == WM_MBUTTONDOWN ||
pMsg->message == WM_NCLBUTTONDOWN ||
pMsg->message == WM_NCRBUTTONDOWN ||
pMsg->message == WM_NCMBUTTONDOWN)
{
c_pSplashWnd->HideSplashScreen();
return TRUE;
}
return FALSE;
}

void CSplashWnd::OnTimer(UINT nIDEvent)
{
HideSplashScreen();
}

再看看CWinApp和CMainFrame类中发生了什么样的改变:
(1)在CWinApp::InitInstance()中调用CSplashWnd::EnableSplashScreen()设置c_bShowSplashWnd;
在PreTranslateMessage()中调用CSplashWnd::PreTranslateAppMessage(),将键盘和鼠标消息传递给CSplashWnd对象,从而进一步调用CSplashWnd::HideSplashScreen()实现SplashScreen窗口的自身销毁。
(2)在CMainFrame对象的OnCreate()中调用CSplashWnd::ShowSplashScreen()创建一个静态的SplashScreen窗口对象c_pSplashWnd,并设置其父窗口为CMainFrame。在这个过程中,CSplashWnd自身会通过创建来设置一个定时器,然后定时器在第一个周期触发时便调用HideSplashScreen()销毁自己。
(3) 而CMainFrame对象的窗口创建消息则是由CWinApp对象在InitInstance()中通过
m_pMainWnd->ShowWindow()调用触发的。
  整个过程可以用下图表示,基本原理就是由CMainFrame来创建CSplashWnd,然后由CSplashWnd自己的定时器触发定时消息来销毁窗口。所以 CSplashWnd 的加入与SDI还是MDI都没有关系。



  第二步,我们再来看看如何在基于对话框的工程中加入 SplashScreen。
通过对以上SDI工程中加入SplashScreen原理的剖析,我想大家也想到如何在基于对话框的工程中加入这一特性了。其实质就是由CDialog类完成SDI工程中CMainFrame类的工作,实现步骤如下:
(1)利用ClassWizard为CMyDialog添加WM_CREATE消息的处理函数OnCreate();(这里使用CMyDialog是为了与函数内的基类名CDialog区别。) int CMyDialog::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CDialog::OnCreate(lpCreateStruct) == -1)
return -1;
CSplashWnd::ShowSplashScreen(this);

return 0;
}
(2)利用ClassWizard为CWinApp添加消息转发处理函数PreTranslateMessage(); BOOL CWinApp::PreTranslateMessage(MSG* pMsg)
{
if (CSplashWnd::PreTranslateAppMessage(pMsg))
return TRUE;

return CWinApp::PreTranslateMessage(pMsg);
}

(3)CWinApp::InitInstance()中加入如下调用: CSplashWnd::EnableSplashScreen(TRUE);
(4)当然你还需要将上一个SDI工程中生成的Splash.CPP与Splash.H文件拷贝到当前工程目录下,并利用Project->Add to Project->Files将这两个文件引入工程。同时还要在CWinApp与CMainFrame的实现文件中#include "Splash.H"。
(5)然后在资源管理器里添加一个ID为IDB_SPLASH的位图。由于VC++的IDE只能显示256色以下的位图,所以如果你想显示一幅真彩色的位图,就请用Import方式导入一幅预先制作好的位图。当然VC++会提示位图已经成功导入,只是无法在IDE的位图编辑器中显示,而在程序运行时就会显示了。如果你想象Word那样显示用户名等信息,可以在CSplashWnd::Create()中装载位图之后增加自己的代码来修改位图。
  通过以上这几步操作,我们就完成了在基于对话框的工程中加入SplashScreen的工作。清楚了吗?
### Android 中 Splash Screen 的实现与用法 #### 1. 官方提供的 Splash Screen API 从 Android 12 开始,官方提供了新的 Splash Screen API,旨在统一应用启动画面的显示方式[^1]。此 API 在应用启动时自动展示一个基于应用图标和主题颜色生成的默认启动画面。如果开发者未进行任何自定义设置,则会直接使用系统默认的启动画面。 #### 2. 自定义 Splash Screen 的退出动画 开发者可以通过 `SplashScreen` 类来定制启动画面的退出动画。以下是一个示例代码,展示如何在启动画面消失时添加自定义动画效果: ```kotlin override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) customizeSplashScreenExit() } private fun customizeSplashScreenExit() { val splashScreen = installSplashScreen() splashScreen.setOnExitAnimationListener { splashScreenView -> Log.d("Splash", "SplashScreen#onSplashScreenExit view:$splashScreenView") // 添加自定义动画 ObjectAnimator.ofFloat(splashScreenView, View.ALPHA, 1f, 0f).apply { duration = 1000 doOnEnd { Log.d("Splash", "SplashScreen#remove after sleeping") splashScreenView.remove() } start() } } } ``` 上述代码通过 `ObjectAnimator` 动画库为启动画面的退出过程设置了淡出效果[^2]。 #### 3. 兼容性问题 如果在 Android 12 或更高版本中仍然保留旧版自定义 Splash Screen 的实现逻辑,则可能会导致双重启动画面的现象。为了避免这种情况,建议在 AndroidManifest.xml 文件中移除旧版 Splash Activity 的声明,并确保所有启动画面相关的逻辑迁移到新的 API 中[^1]。 #### 4. 屏幕适配方案 随着全面屏和刘海屏设备的普及,Splash Screen 的适配成为一个重要的考虑因素。传统的 drawable 资源文件夹可能无法完美适配各种屏幕比例。一种推荐的解决方案是使用 `layer-list` 来定义背景资源,结合矢量图形(Vector Drawable)以实现更灵活的适配[^3]。 以下是一个使用 `layer-list` 定义启动画面背景的示例: ```xml <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@android:color/white" /> <item> <bitmap android:gravity="center" android:src="@drawable/ic_launcher_foreground" /> </item> </layer-list> ``` 此配置将白色背景与居中的应用图标组合在一起,适用于不同分辨率和屏幕比例的设备。 #### 5. 实现自定义 Splash Screen(旧版方法) 对于不支持新 API 的低版本 Android 系统,可以通过创建一个独立的 Splash Activity 来实现启动画面功能。以下是一个简单的实现示例: ```kotlin class SplashScreenActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_splash_screen) Handler(Looper.getMainLooper()).postDelayed({ startActivity(Intent(this, MainActivity::class.java)) finish() }, 2000) // 延迟 2 秒后跳转到主界面 } } ``` 对应的布局文件 `activity_splash_screen.xml` 可以包含一个 ImageView 或其他视图组件来展示启动画面内容。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值