1,目标


实现类似360悬浮窗口这样的效果,当窗口在屏幕边缘时,鼠标移开,就自动向边缘隐藏,鼠标放上去,就又平滑显示出来。


正常状态:

android 360悬浮球 打开360悬浮球_悬浮窗

边缘自动隐藏:

android 360悬浮球 打开360悬浮球_移出_02


2,原理


首先是实现圆角或椭圆这种不规则形状的窗口,可以参考另一篇文章:

MFC实现不规则窗口

 然后需要给没有标题栏的窗口增加拖拽移动的功能,这个就是自己手动发送一个消息,使windows认为鼠标在标题条上

对于窗口的移动显示隐藏,使用了定时器。

其中有一些做判断的函数,如判断在窗口在屏幕某个边缘,判断鼠标是否在窗口内部等。


3,实现


①新建MFC对话框程序Test360.去掉默认控件和属性中的边框。参考上面所说的文章实现一个带圆角及背景图片的窗口。

android 360悬浮球 打开360悬浮球_圆角_03

由于这里还是截图然后用PS简单选择了个范围,所以还有毛边,若是有美工原图或PS仔细些,是没问题的。


②给Dlg类CTest360Dlg添加一条消息响应OnLButtonDown,在其中传送WM_NCLBUTTONDOWN消息,达到拖动效果。

void CTest360Dlg::OnLButtonDown(UINT nFlags, CPoint point)
{
	CDialog::OnLButtonDown(nFlags, point);

	// 实现拖动窗口
	// 发送WM_NCLBUTTONDOWN消息
	// 使Windows认为鼠标在标题条上
	// 或SendMessage(WM_SYSCOMMAND,SC_MOVE | HTCAPTION,0);   
	PostMessage(WM_NCLBUTTONDOWN,HTCAPTION,MAKELPARAM(point.x, point.y)); 
}



③添加几个判断窗口是否在屏幕边缘的函数:

//是否靠近屏幕左边缘
BOOL CTest360Dlg::NearLeftBorder()
{
	CRect rc;
	GetWindowRect(rc);
	//窗口左边界在屏幕左边界20像素内都算“靠近”
	if (rc.left < 20)
	{
		return TRUE;
	}
	return FALSE;
}
//是否靠近屏幕上边缘
BOOL CTest360Dlg::NearUpBorder()
{
	CRect rc;
	GetWindowRect(rc);
	if(rc.top<20)
	{
		return TRUE;
	}
	return FALSE;
}
//是否靠近右边缘
BOOL CTest360Dlg::NearRightBorder()
{
	CRect rc;
	GetWindowRect(rc);
	int nWidth = GetSystemMetrics(SM_CXSCREEN);
	if (rc.left>nWidth - rc.Width())
	{
		return TRUE;
	}
	return FALSE;
}



④判断鼠标是否在窗口内。

BOOL CTest360Dlg::MouseInWnd()
{
	CRect rc;
	GetWindowRect(rc);
	POINT pt;
	GetCursorPos(&pt);
	if (PtInRect(&rc,pt))
	{
		return TRUE;
	}
	return FALSE;
}

⑤定义一个定时器,

#define TIMER_MOVE 1

在CTest360Dlg::OnInitDialog()中启动:

BOOL CTest360Dlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
	//  执行此操作
	SetIcon(m_hIcon, TRUE);			// 设置大图标
	SetIcon(m_hIcon, FALSE);		// 设置小图标


	//设置窗口形状
	SetRegion(GetDC(),IDB_BITMAP_360RGN,RGB(0,0,0));

	//初始时居中
	CenterWindow();

	//设置定时器,处理悬浮窗的显隐移动
	SetTimer(TIMER_MOVE,10,NULL);

	return TRUE; 
}



处理如下:

void CTest360Dlg::OnTimer(UINT_PTR nIDEvent)
{
	if (nIDEvent == TIMER_MOVE)
	{
		//鼠标按着的,就怎么都不移动
		if (GetKeyState(VK_LBUTTON)<0)
		{
			return;
		}
		//靠近屏幕上边缘
		if (NearUpBorder())
		{
			//根据鼠标动作进行窗口的移动(鼠标进入区域就向下平移显示,鼠标离开就向上平移隐藏)
			MoveUp();
			return;
		}
		//靠近屏幕左边缘
		if (NearLeftBorder())
		{
			//根据鼠标动作进行窗口的移动(鼠标进入区域就向右平移显示,鼠标离开就向左平移隐藏)
			MoveLeft();
			return;
		}
		//靠近屏幕右边缘
		if (NearRightBorder())
		{
			//根据鼠标动作进行窗口的移动(鼠标进入区域就向左平移显示,鼠标离开就向右平移隐藏)
			MoveRight();
			return;
		}
	}
	
	CDialog::OnTimer(nIDEvent);
}



其中GetKeyState先强行过滤掉鼠标按下,让这种情况不移动。避免刚拖动窗口到屏幕边缘时鼠标还没松开就直接开始移动了。

3个Move函数,是真正按像素移动窗口的地方,包括来回(出屏幕和进屏幕)。原理是一样的,看明白一个就OK了。

void CTest360Dlg::MoveUp()
{
	CRect rc;
	GetWindowRect(rc);
	//鼠标进入则下移,显示出来
	if(MouseInWnd())
	{
		int height = rc.Height();
		if (rc.top>=0)
		{
			rc.top = 0;
		}
		else
		{
			rc.top++;
		}
		rc.bottom = rc.top + height;
		MoveWindow(rc);
	}
	//鼠标在别处,窗口就往上移出屏幕
	else
	{
		int height = rc.Height();
		//窗口向上移动一像素,如果快隐藏(露20)就不移了
		if (rc.top<= 20 - height)
		{
			rc.top = 20 - height;
			ShowWindow(SW_HIDE);
			m_upDlg->m_Test360Dlg = this;
			m_upDlg->DoModal();
		}
		else
		{
			rc.top--;
		}
		rc.bottom = rc.top + height;
		MoveWindow(rc);
	}
}
void CTest360Dlg::MoveLeft()
{
	CRect rc;
	GetWindowRect(rc);
	//鼠标进入则下移,显示出来
	if(MouseInWnd())
	{
		int width = rc.Width();
		if (rc.left>=0)
		{
			rc.left = 0;
		}
		else
		{
			rc.left++;
		}
		rc.right = rc.left + width;
		MoveWindow(rc);
	}
	//鼠标在别处,窗口就往上移出屏幕
	else
	{
		int width = rc.Width();
		//窗口向左移动一像素,如果快隐藏(留20像素)就不移了
		if (rc.left<= 20 - width)
		{
			rc.left = 20 - width;
		}
		else
		{
			rc.left--;
		}
		rc.right = rc.left + width;
		MoveWindow(rc);
	}
}
void CTest360Dlg::MoveRight()
{
	CRect rc;
	GetWindowRect(rc);
	int sysWidth = GetSystemMetrics(SM_CXSCREEN);
	//鼠标在窗口内则窗口左移,显示出来
	if(MouseInWnd())
	{
		int width = rc.Width();
		if (rc.left<= sysWidth - width)
		{
			rc.left = sysWidth - width;
		}
		else
		{
			rc.left--;
		}
		rc.right = rc.left + width;
		MoveWindow(rc);
	}
	//鼠标没在窗口上,窗口就往右移出屏幕
	else
	{
		int width = rc.Width();
		//窗口向右移动一像素,如果快隐藏了(还留20像素)就不移了
		if (rc.left>= sysWidth - 20)
		{
			rc.left = sysWidth - 20;
		}
		else
		{
			rc.left++;
		}
		rc.right = rc.left + width;
		MoveWindow(rc);
	}

}

对MoveUp做说明:

当Timer中判断到窗口在屏幕上边缘时,进入MoveUp,如果此时鼠标进入窗口内,窗口就往下方移动直到完全显示;如果鼠标离开窗口,那么窗口会立即往上隐藏,直到留下一小截。 360官方软件现在是换了个半圆形的窗口“趴”在屏幕边上。这里主要是模拟触发移动的效果。


4,效果

几张截图

左侧:

android 360悬浮球 打开360悬浮球_圆角_04

上侧:

android 360悬浮球 打开360悬浮球_圆角_05

右侧:

android 360悬浮球 打开360悬浮球_移出_06


5,源码


MFC模拟360悬浮窗加速球Test360_VS2008工程.rar