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

使用 C# 实现的翻页效果

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (44投票s)

2006年2月25日

CPOL

8分钟阅读

viewsIcon

294432

downloadIcon

15235

本文演示了如何使用 GDI+ 创建翻页效果。

引言

图 1 - 翻页动画。

本文介绍了一种创建翻页效果的方法,该效果在并排显示位图时非常有用,就像它们是书籍或相册中的页面一样。翻页效果可以实现模拟页面之间的真实过渡。

背景

撰写本文的灵感来自于创建一个可重用的相册组件来显示图片的愿望。我选择使用 C# 和 GDI+ 来增强我对 .NET 环境中图形编程的理解。

创建此效果的技术背景来自于 Sham Bhangal(Flash Hacks 的作者)撰写的《Flash MX 中的翻页效果》一文。Sham 的文章使用对称线来控制参与动画的页面可见区域。Flash MX 和 GDI+ 方法之间存在差异;然而,计算并使用对称线来控制可见页面区域的概念是相同的。

在整篇文章中,假设当前页面是第 3 页和第 4 页,动画将显示过渡到第 5 页和第 6 页。换句话说,动画将显示第 4 页被翻转。

动画技术

下图说明了动画中使用的重要区域和变量

图 2 - 区域和重要参数。

整个翻页动画可以总结为

  1. 绘制当前页面 3 和 4 的全部内容。绘制区域 B 和 C 后第 4 页的可见部分将代表区域 A。
  2. 计算对称线和区域 B 和 C 的裁剪区域。
  3. 绘制区域 B。这是正在翻转的页面(第 6 页)“下方”页面的可见部分。
  4. 将坐标转换为热点,并旋转坐标。
  5. 绘制区域 C。这是正在翻转的页面(第 5 页)“背面”页面的可见部分。

当前页面

《Flash MX 中的翻页效果》一文指出,当前页面不参与动画。这意味着对于动画的每一帧,当前页面(3 和 4)首先被绘制,不进行裁剪或任何变换。可见区域 B 和 C 将绘制在当前页面的上方。

热点

我从《Flash MX 中的翻页效果》一文中借用了“热点”一词。热点代表页面折叠在水平轴上的位置,它是对称线开始的点。在动画过程中,热点始终从页面边缘移动到书的中心。热点到页面边缘的距离 (x) 在每一帧动画中都会增加。当热点到达书本中心时,动画将停止;此时 x = PAGE_WIDTH。

对称线

对称线代表页面折叠。它用于控制参与动画的页面的可见部分。在 .NET 中,对称线控制两件事:

  1. 包围区域 B 和 C 的裁剪区域。
  2. 区域 C 的已转换坐标原点。

对称线可以用以下方程描述:

  • a = 45 + ( (45 *x) / PAGE_WIDTH )
  • h = x Tan ( a )

请注意,随着距离 x 的增加,角度 a 也会增加。方程显示,当 x = 0 时,a = 45 度;当 x = PAGE_WIDTH 时,a = 90 度。

当动画开始时,x 将为 0。因此,区域 B 和 C 将不可见。随着 x 的增加,对称线将形成一个三角形的斜边。由对称线、距离“x”和计算出的高度“h”界定的三角形将形成一个封闭路径。图 2 显示了这种情况的一个示例。

随着 x 继续增加,条件 h>=PAGE_HEIGHT 将变为真。此时,对称线下方的路径将从三角形变为梯形。梯形的高度将由 PAGE_HEIGHT 限制。图 1 是这种情况的一个示例。

封闭路径(三角形或梯形)将定义区域 B 和 C 的裁剪区域。要查看此图形路径的实际效果,请在提供的源代码中将常量 INCLUDE_DRAW_GRAPHICS_PATH 设置为 true。这将在此路径周围绘制金色轮廓。

区域 A

这是正在翻转的页面(第 4 页)在绘制了区域 B 和 C 之后的可见区域。

区域 B

区域 B 来自表示正在翻转的页面“下方”的页面图像。在此示例中,区域 B 是第 6 页的可见部分。通过将动画帧的裁剪区域设置为对称线下方的封闭路径来绘制区域 B。然后,该页面的位图将直接绘制在正在翻转的页面上。显而易见,随着 x 的增加(以及角度 a 的增加),该页面的可见部分会增多。

要查看区域 A 与区域 B 的关系,请将常量 INCLUDE_UNDERSIDE_PAGE_IN_ANIMATION 设置为 false。这只是从动画中移除了区域 C。

区域 C

区域 C 来自表示正在翻转的页面背面的图像。在此示例中,区域 C 是第 5 页的可见部分。区域 C 由对称线定义,但它位于页面的另一侧。例如,当第 4 页被翻转时,区域 B 将是第 6 页的右下部分,而区域 C 将是第 5 页的左下部分。图 3 显示了区域 B 和区域 C 的图形路径之间的关系。

图 3 - 区域 B 和 C 之间的关系。

使用一个屏幕外的位图 pageUndersideImage 来绘制区域 C。新位图的裁剪区域由对称线下方的图形路径定义。第 5 页的图像被绘制到这个新缓冲区中。这个新创建的图像将被绘制在区域 B 的旁边。

将区域 C 缝合到区域 B

当区域 C 的屏幕外图像准备好后,就可以将其绘制到当前的动画帧上。

  1. 首先,将坐标系平移到热点。
  2. 然后,将坐标系旋转 180-2a 度。请参见图 4 了解此关系。
  3. 在坐标 (-x,-PAGE_HEIGHT) 处绘制包含区域 C 的图像。图 5 说明了在旋转坐标系上绘制区域 C。

图 4 - 匹配对称线的旋转角度。

图 5 - 旋转坐标系

我发现缝合区域 B 和 C 具有挑战性,因为当前页面的颜色会沿着对称线(在这种情况下是第 4 页的几个红色像素)渗出。我实现的解决方法包括在绘制区域 B 或 C 时,将图形对象的 PixelOffsetMode 设置为 PixelOffsetMode.Half

g.PixelOffsetMode= PixelOffsetMode.Half;

我还发现,在将坐标平移到热点之前,为区域 C 添加 1 像素(或减去 1 像素)也可以防止颜色渗漏。

PathTranslationMatrix.Translate((float)hotSpot.Origin.X+1, 
                                   (float)hotSpot.Origin.Y);

要使用 PixelOffsetMode 解决方法,请在提供的源程序中将布尔值 USE_PIXEL_MODE_OFFSET 设置为 true

绘制动画

动画的每一帧都在 timer1_Tick 事件处理程序中的一个屏幕外缓冲区 CurrentShownBitmap 中绘制。OnPaint 事件处理程序只是将 CurrentShownBitmap 的内容绘制到屏幕上。

关键功能描述

动画每一帧的区域 B 和区域 C 的图形路径计算由以下函数执行:

private GraphicsPath GetPageUnderGraphicsPath(int x, 
                   ref double a, int height, int width, 
                   bool isUnderSide, TurnType type)

参数 x 表示热点到页面边缘的距离(如上所述)。heightwidth 参数指示当前页面的高度和宽度。isUnderside 参数用于告诉例程正在计算的图形路径是否为区域 C(正在翻转页面的背面)。最后一个参数 type 表示当前动画是针对左页还是右页翻转。本质上,isUndersidetype 用于正确确定图形路径的方向。参数 a 表示当前的 a 角度,如上所述。a 的值从该函数返回,并在稍后用于在绘制区域 C 之前旋转坐标轴(参见图 4 和图 5)。

使用代码

设置控件

为了简单起见,此控件包含自己的位图。位图的初始化在 LoadSamples() 函数中进行。此函数在示例窗体的构造函数中调用。

控制动画帧率和帧数

动画的帧率由计时器控制。TickSpeedpublic 属性可用于控制速率(以毫秒为单位)。动画帧数由计时器每次滴答移动热点的距离控制。MoveXBypublic 属性用于控制 x 移动的距离。

调整高度

背页图像(页面)顶部的裁剪是一个问题。引入了 HeightAdjustmentpublic 属性,以便在控件顶部留出更多空间来解决裁剪问题。

开始动画

此组件提供了两个启动动画的方法:animateRightPageTurn()animateLeftPageTurn()

关注点

  • 我最初尝试为 h = x Tan( 45 + ((45 * (x)) / PAGE_WIDTH) ) 这个非线性方程求解 h=PAGE_HEIGHT 时的 x 值。我的暴力求解尝试包含在本文章的源代码中。对此问题是否有更优雅的解决方案?

历史

  • 2006-02-25
    • 初始版本。
© . All rights reserved.