说明:以前本人参与个一个电子杂志项目,当时要求实现模拟现实生活中的杂志翻页动画效果,别人推荐了这篇文章,最后达到了我想要的效果,今天尝试把这篇文章翻译了一下。希望对英语水平不太好的同行有帮助。如果你的英语水平足够好,我推荐你阅读英文原文,网址是:http://www.codeproject.com/KB/GDI-plus/TurnThePage.aspx,同时希望大家原谅本人的翻译水平。
介绍
图
1
翻页效果
这篇文章用来介绍如何在电子书或者电子相册中并列显示图片时创建一种翻页效果,这种翻页效果模仿了现实中的书本翻页情况。
背景
写作这篇文章的灵感来自于一个可重用的用于显示图片的组件的需求。我使用了C#和GDI+来提高我对.net下图形图象编程的理解。
创建这种效果的技术背景来自于一篇文章《FalshMX中的翻页效果》,网址是:
http://www.oreillynet.com/pub/a/javascript/2004/09/03/flashhacks.html
,作者是
Sham Bhangal
。
Sham
在文章里如何在动画效果中使用对称线来控制页面的可见部分。尽管在
Flash MX
中和在
GDI+
中的处理有些不同,但是利用对称线来计算页面的可见部分这个概念是相同的。
在这篇文章中,我们假定第3页和第4页是当前页,并且下一个动作是要翻到第5页和第6页。换句话说,我们要展现的是第4页的翻页效果。
动画技巧
下图阐述了在整个动画中的关键区域和变化区域。
图2 关键区域和参数
整个翻页动画可以概括为:
1、完全绘制出第3页和第4页。在绘制完成之后,B和C部分将会遮挡住第4页的原来可见部分。
2、计算出对称线和B和C的裁剪区域。
3、绘制B部分。这是下面的一页的部分区域(第6页)。
4、根据热点进行相应转换,并且进行相应旋转。
5、绘制C部分。这是第4页在翻页过程中显示的它的背面(第5页)的内容。
当前页
热点
对称线
对称线代表翻页时页面的折痕。它是用于计算在翻页时参与的页面可见部分。在程序中,对称线控制两件事情:
1、B和C部分的裁剪区域。
1、B和C部分的裁剪区域。
2、C部分进行转换的坐标原点。
对称线可以用下面两个等式来描述:
a
= 45 + ( (45 *x) / PAGE_WIDTH )
h = x Tan ( a )
注意角度
a会随着x的变化而变化。这个等式表明当x=0时
a
= 45
度并且当x=
PAGE_WIDTH
时
a
= 90
度。
当动画开始时,
x=0
,因此
B
和
C
部分并不可见。随着
x
的增加,对称线就形成了直角三角形的斜边。三角形由三个长度确定:对称线、
x
和高度
h
。图
2
展示了这样一种情况。
随着x的增加,
将会出现h>=
PAGE_WIDTH
的情况。当这种情况出现后,对称线与页面相交的区域将会由三角形变成梯形。梯形的高度就是
PAGE_WIDTH
。图
1
就是这种情况。
不管是梯形还是三角形,这个闭合路线都指出了B和C这两个裁减区域。要想看到运行中看到这种带有边框的效果,在提供的源代码中将
INCLUDE_DRAW_GRAPHICS_PATH
这个参数设置为true就行了。程序将会在闭合路径的外面绘制出一个金色的轮廓。
A部分区域
这部分是要翻动的页面在裁减出B和C部分之后的可见部分(第4页)。
B部分区域
B部分来自于正在翻动的页面的之下的另一页面,在这个例子中指的是第6页的可见部分。B部分区域就是被对称线与页面形成的闭合曲线所裁减的部分。
在这个部分之上的页面(第
4
页)的相应部分将会被直接裁减掉。显而易见,随着
x
的增加(
a
也随着增加),这个可见部分将会越来越大。
要想看
A
和
B
部分区域的关系,请将
INCLUDE_UNDERSIDE_PAGE_IN_ANIMATION变量
设置为
false
。这将把
C
部分从动画效果中去掉。
C部分区域
C部分区域的图象表示了正在翻动的页面的背面的图象。在这个例子中,C部分区域代表了第5页的可见部分。C部分也是通过对称线指定的,但是是在页面的这一边(对称线的左边)。举个例子,当翻动第4页时,B部分将会是第6页的右下部分,而C部分将会是第5页的左下部分。图3展示了B和C之间的关系。
![](https://i-blog.csdnimg.cn/blog_migrate/7ecbdccb66cf296371b4c5afbaa7bf61.png)
图3 B和C之间的关系
在绘制C部分区域时,我们用了一个后台的图象来辅助,就是
pageUndersideImage。这个新图象的裁减区域通过对称线来确定。第5页的图象被绘制在这个新的缓冲区里面,这个新建的图象将会靠近B部分区域绘制。
连接B和C部分区域
当C部分的后台图像已经准备好,它通过下面的步骤绘制:
首先调整系统坐标到热点。
将系统坐标旋转
180-2a
度,请看图
4
了解它们的关系。
将含有C部分区域的图像绘制到相应坐标(
-x,-PAGE_HEIGHT)。图5标明了这种旋转。
图 4 调整角度以匹配对称线
![](https://i-blog.csdnimg.cn/blog_migrate/1cd608f002f9260d12dfd7f8a7a08ed1.png)
![](https://i-blog.csdnimg.cn/blog_migrate/a2dc112a3022e1e5c7d8e8a308d225ea.png)
图 4 调整角度以匹配对称线
![](https://i-blog.csdnimg.cn/blog_migrate/1cd608f002f9260d12dfd7f8a7a08ed1.png)
图
5
旋转坐标系
我发现沿着对称线排列B和C部分区域非常具有挑战性因为当前页面的颜色容易出“血边”(因为第4页的一些红色像素)。当我绘制B或者C部分区域时,我用了一个变通方法:指定图形对象的pixeloffsetmode以pixeloffsetmode.half方式绘制。
g.PixelOffsetMode= PixelOffsetMode.Half;
我还发现,在绘制
C
部分区域之前,增加一个像素(或者减掉一个像素)来旋转坐标也可以防止出“血边”。
PathTranslationMatrix.Translate((
float
)hotSpot.Origin.X+1,
(
float
)hotSpot.Origin.Y);
要使用pixeloffsetmode变通方法,请将源代码中的
USE_PIXEL_MODE_OFFSET设置为true。
绘制动画
每一帧动画都是在
timer1_Tick方法中绘制到缓冲区中的
CurrentShownBitmap对象上。
CurrentShownBitmap方法仅仅是将绘制到屏幕上。
主要功能说明
计算动画中每一帧里B和C部分的图象都是由以下方法完成的:
private GraphicsPath GetPageUnderGraphicsPath( int x,
private GraphicsPath GetPageUnderGraphicsPath( int x,
ref
double
a,
int
height,
int
width,
bool
isUnderSide, TurnType type)
参数x代表从页面边缘到热点处的距离(前面已经说明),height和width参数代表当前页面要显示的高度和宽度。isUnderSide参数用来告诉程序是否在计算C部分区域(正在翻动的那一页的背面),最后一个参数type是表示页面是左翻页还是右翻页。基本上,isUnderSide和type参数是用来获取图形路径的正确性的,参数a如上所述是代表当前角度的。参数a传递的是引用,随后将在绘制C之前用于旋转坐标(见图4和图5)。
使用代码
安装控件
为了简单起见,这个控件有自己的图片。初始化图象的代码在
LoadSamples()方法中。这个方法在Sample类的构造方法中调用。
控制翻页的速度和数目
翻页的速度是通过一个timer来控制的,public属性
TickSpeed能用来控制翻页速度(毫秒为单位),动画的帧数是通过每个timer间隔热点移动的距离来控制的。public属性暴露给外界以控制已经移动的距离x。
调整高度
沿着背页的顶部裁减是一个问题,public属性
HeightAdjustment用于在控件顶部留一点空白区域便于裁剪。
开始动画
这个组件公开两个方法来开始动画。
animateRightPageTurn()
and
animateLeftPageTurn()
.
注意事项
我本来试图解当
h=PAGE_HEIGHT时的非线性方程
h = x Tan( 45 + ((45 * (x)) / PAGE_WIDTH) )中的x的值,我的这个蛮力办法的代码包含在源代码中。是不是还有一个更优雅的办法呢?
说明:程序的源代码可以到
CSDN下载中心下载。
----
2019年11月29日更新:不好意思,CSDN升级之后下载链接发生了变化,首次发这篇博文的时候是2008年,距今有10年了,我想或许还有其它更好的办法。