65.9K
CodeProject 正在变化。 阅读更多。
Home

Windows Mobile 上的视图过渡动画

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.67/5 (5投票s)

2008年6月6日

CPOL

4分钟阅读

viewsIcon

41649

downloadIcon

442

如何在 Windows Mobile WTL 应用程序中为子视图转换添加动画。

SlideView/slideview.gif

引言

本文展示了如何在 WTL 8.0 Windows Mobile 应用程序中为子视图转换添加动画。

背景

研究本文代码的灵感来自于阅读 Tim Brook 的文章,该文章介绍了如何在 WTL SDI 应用程序中切换视图。这项技术对于 Windows Mobile 应用程序非常有用,因为该平台不支持 MDI(尽管您可以使用标签视图)。

在测试了 Tim 的想法后,我考虑为子视图转换添加动画。这比仅仅替换旧视图会更好。为什么不增加一些视觉效果,使其更具吸引力?此外,开发人员可能希望使用子视图转换动画来向用户传达空间导航的概念:当用户深入了解更详细的信息时,新视图可能会向上滚动显示;当返回上一级时,向上滚动关闭。

使用代码

为了方便开发人员,我将所有必需的代码都封装到了混入类中,这些类必须包含在主框架以及所有将被框架托管的动画子视图窗口的公共继承列表中。

主框架代码

编码主框架非常简单。首先,将 CChildViewAnimate 模板类添加到主框架的继承列表中

class CMainFrame :
    public CFrameWindowImpl<CMainFrame>, 
    public CUpdateUI<CMainFrame>,
    public CChildViewAnimate<CMainFrame>,
    public CMessageFilter, 
    public CIdleHandler

接下来,您需要对 PreTranslateMessage 处理程序进行一个小小的更改

virtual BOOL PreTranslateMessage(MSG* pMsg)
{
    if(CFrameWindowImpl<CMainFrame>::PreTranslateMessage(pMsg))
        return TRUE;

    return ChildPreTranslateMessage(pMsg);
}

调用 ChildPreTranslateMessage 将执行转移到当前子视图的消息翻译(对子对话框很有用)。

最后,您必须在框架的 OnCreate 处理程序中初始化当前视图指针

...
m_hWndClient = m_viewForm.Create(m_hWnd);
m_pView = &m_viewForm;
...

m_pView 成员指向当前视图,并被 CChildViewAnimate 代码使用。

子视图代码

在子视图类上,您只需将 CChildView 类模板添加到继承列表中,并可选地覆盖 PreTranslateMessage 虚方法。这是示例的窗体视图类

class CSlideFormView :
    public CDialogImpl<CSlideFormView>,
    public CChildView<CSlideFormView>
{
public:
    enum { IDD = IDD_SLIDEVIEW_FORM };

    virtual BOOL PreTranslateMessage(MSG* pMsg)
    {
        if(!::IsWindow(m_hWnd))
            return FALSE;
        return CWindow::IsDialogMessage(pMsg);
    }

    BEGIN_MSG_MAP(CSlideFormView)
    END_MSG_MAP()
};

动画子视图

子视图转换通过向左、向右、向上或向下滑动来动画化。旧视图和新视图一起滚动,以使用户感觉连续移动。

在我们可以将新视图滚动到位之前,必须先创建它。创建新视图时,重要的是不要立即显示它,所以我选择为每个新视图提供一个与当前视图大小相同的矩形,但将其向右偏移,这样它就不会显示在当前视图之上。您可以在示例应用程序中看到这一点,其中树形视图和列表视图都被创建

CWindow wndView(*m_pView);

wndView.GetClientRect(&rc);

nWidth   = rc.Width();
rc.left  += nWidth;
rc.right += nWidth;

hWnd = m_viewList.Create(m_hWnd, &rc, NULL, dwStyle);

成功创建新的子视图后,您可以通过调用以下方法来动画化它:

SwitchToView(&m_viewList, m_nAnimate);

m_nAnimate 成员变量存储了预定义的动画模式之一

enum ViewAnimation
{
    None,        // Don't animate the child transitions
    ScrollLeft,  // Scroll the views to the left
    ScrollRight, // Scroll the views to the right
    ScrollUp,    // Scroll the views up
    ScrollDown   // Scroll the views down
};

SwitchToView 返回时,活动子视图已根据 Tim 的更改子窗口 ID 的方法正确设置。

实现

子视图动画在 CChildViewAnimate 类的受保护的 AnimateView 方法中实现。代码首先将子视图区域的内容渲染到位图,然后使用它来绘制主框架的客户端区域。滚动过程只是将主框架客户端区域的滚动与将新子视图移到位同步。我这样设计这个过程是为了避免在滚动两个视图时重新绘制它们(可能会更慢)。

此外,当新的子窗口移动到位时,代码会确保仅使在任何给定滚动步骤中显示的窗口的新部分失效。这也助于使整个过程更顺畅。

最后,有一个值得提及的方法:ScrollWait。在滚动主框架客户端区域时,我发现滚动的位图越少,滚动速度越快(很容易理解为什么)。这意味着我早期使用此代码的经验得到了加速:滚动速度提高了。这对用户来说是一种不愉快的体验,所以我找到了一种非常简单的方法来实现看起来恒定的滚动速度。第一次滚动使用 GetTickCount 进行计时,所有后续滚动都通过一个非常简单的 CPU 占用循环进行“时间填充”。

void ScrollWait(DWORD dwTickIni, DWORD dwTickEnd)
{
    DWORD dwTickDel = dwTickEnd - dwTickIni;

    if(dwTickDel > m_dwMaxTick)
    {
        m_dwMaxTick = dwTickDel;
    }
    else
    {
        // Preserve original timing (yes, this burns the CPU)
        while(GetTickCount() - dwTickIni < m_dwMaxTick)
            ;
    }
}

m_dwMaxTick 存储一次滚动在给定动画中所需的最大滴答数。不算很巧妙,但很有效。

关注点

从示例可以看出,有三种不同的视图可以动画化:窗体、列表和树。如果您查看树的实现,您会发现它与列表的实现非常不同:树被托管在另一个窗口中。虽然列表视图窗口是子视图,但树视图必须托管在另一个窗口中,原因非常奇怪:它在滚动到视图时无法正确绘制。为什么?我实际上不知道……

最后,您可能对包含的 CSelectionBar 类感兴趣。这是 Pocket PC 标题栏的一个粗略的 WTL 初版实现(我已经在 MFC 中编写了一个实现,但它在 WTL 中看起来好多了!)。它还没有一套完整的特性,但我保证一旦完成就会在这里发布。

历史

  • 2008-06-12 - 添加了说明性动画。
  • 2008-06-06 - 首次发布。
© . All rights reserved.