您的位置:首页 > 移动开发 > IOS开发

iOS上一种图书翻页效果的实现(Leaves)详解

2015-03-08 21:05 369 查看
Leaves是由Tow Brow开发的一个简单的图书翻页控件,它巧妙地结合了镜像层、阴影层(用于半透明页)和渐变层(用于阴影)来实现图书的翻页效果。其翻页效果如下图所示:



特性

Leaves支持:

文本、图像、PDF等任何可被渲染到Graphics Context上的对象

通过拖动或点击来翻页

支持ipad和iphone大小的显示区域

Levels目前不支持以下特性

页面上的交互元素

轻扫动作



类和接口

Leaves中主要有三个类:LevelsView、LevelsViewController、LevelsCache:

LevelsCache:是一个辅助类,用于缓存显示页。它将显示的内容缓存为图片并保存。

LevelsView:是翻页视图,翻页的主要效果便在些实现。它定义了一系列的层对象,并通过操作这些层对象来实现翻页中各种效果。

LevelsViewController: LevelsView的控制器

类似于UITableView, LevelsView也有一个相关的数据源类(LeaveViewDataSource)与委托类(LeavesViewDelegate),它们分别有两个方法,如下所示



复制代码

@protocol LeavesViewDataSource <NSObject>

- (NSUInteger) numberOfPagesInLeavesView:(LeavesView*)leavesView;

- (void) renderPageAtIndex:(NSUInteger)index inContext:(CGContextRef)ctx;

@end

@protocol LeavesViewDelegate <NSObject>

@optional

- (void) leavesView:(LeavesView *)leavesView willTurnToPageAtIndex:(NSUInteger)pageIndex;

- (void) leavesView:(LeavesView *)leavesView didTurnToPageAtIndex:(NSUInteger)pageIndex;

@end







层树结构

LevelsView中的层树结构如下图所示:







每一个层(Layer)都有其特殊的用途,或作为内容的显示层,或作为阴影层,具体说明如下:

topPage层:显示当前页的内容。

topPageOverlay层:在翻页过程中,该层覆盖于topPage层上,且颜色偏暗,从而使topPage未翻转的部分变暗,有阴影的感觉。

topPageShadow层:在翻页过程中,该层用于表达topPage被翻转部分所形成的阴影。

topPageReverse层:翻页过程中,topPage被翻转部分的反面的容器层。

topPageReverseImage层:反面的内容页。在竖屏下,用于显示topPage被翻转部分的内容,这些内容被映射到该层,给人感觉书是透明的。在横屏下,显示的是下一页的内容。

topPageReverseOverlay层:该层用于覆盖topPageReverse层,效果与topPageOverlay类似。

topPageReverseShading层:该层在topPageReverse层右侧形成一个阴影。

bottomPage层:topPage页的下一页所在的层。

bottomPageShadow层:该层为在翻页过程中在 bottomPage左侧形成的一个阴影层。

leftPage层:该层为横屏模式下左侧页所在的层。

leftPageOverlay层:该层覆盖于为 leftPage层,效果与topPageOverlay类似。

由上可以看出,层树中的层主要分为三类:

内容显示层:topPage、topPageReverseImage、bottomPage、leftPage

阴影层:topPageShadow、topPageReverseShading、bottomPageShadow

覆盖层:topPageOverlay、topPageReverseOverlay、leftPageOverlay





图片缓存

Tow Brow在处理不同的内容(文本、图像、PDF)时显示时,所采取的方法是一样的。他将内容缓存为图像,并显示在屏幕上。基本方法是将内容写进CGContextRef中,然后根据CGContextRef中的信息创建图像,具体方法如下:



复制代码

-(CGImageRef) imageForPageIndex:(NSUInteger)pageIndex {

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate(NULL,

pageSize.width,

pageSize.height,

8, /* bits per component*/

pageSize.width * 4, /* bytes per row */

colorSpace,

kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);

CGColorSpaceRelease(colorSpace);

CGContextClipToRect(context, CGRectMake(0, 0, pageSize.width, pageSize.height));

[dataSource renderPageAtIndex:pageIndex inContext:context];

CGImageRef image = CGBitmapContextCreateImage(context);

CGContextRelease(context);

[UIImage imageWithCGImage:image];

CGImageRelease(image);

return image;

}





当然程序没有缓存所有页的内容,而是根据横竖屏的不同缓存适当数量的内容。每次翻页时会重新整理缓存中的内容。



翻页动画实现

在Leaves中,翻页的基本原理其实很简单:翻页过程中,根据手指的划动来不断的调整层树结构中每个层的frame,翻页结束后,重新调整内容显示层所显示的内容。

为此,LevelsView中设置了一个leafEdge变量,该变量是手指在屏幕上划动时Touch Point在屏幕x轴上的百分比位置,这个操作在touchesMoved:withEvent中完成:



复制代码

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {

......

UITouch *touch = [event.allTouches anyObject];

CGPoint touchPoint = [touch locationInView:self];

[CATransaction begin];

[CATransaction setValue:[NSNumber numberWithFloat:0.07]

forKey:kCATransactionAnimationDuration];

self.leafEdge = touchPoint.x / self.bounds.size.width;

[CATransaction commit];

}





而在leafEdge的set方法中,我们根据leafEdge的值来重新设定各个Layer的frame属性



复制代码

- (void) setLayerFrames {

CGRect rightPageBoundsRect = self.layer.bounds;

CGRect leftHalf, rightHalf;

CGRectDivide(rightPageBoundsRect, &leftHalf, &rightHalf, CGRectGetWidth(rightPageBoundsRect) / 2.0f, CGRectMinXEdge);

if (self.mode == LeavesViewModeFacingPages) {

rightPageBoundsRect = rightHalf;

}

topPage.frame = CGRectMake(rightPageBoundsRect.origin.x,

rightPageBoundsRect.origin.y,

leafEdge * rightPageBoundsRect.size.width,

rightPageBoundsRect.size.height);

topPageReverse.frame = CGRectMake(rightPageBoundsRect.origin.x + (2*leafEdge-1) * rightPageBoundsRect.size.width,

rightPageBoundsRect.origin.y,

(1-leafEdge) * rightPageBoundsRect.size.width,

rightPageBoundsRect.size.height);

......

}





最后便是当手指离开屏幕时,如何处理翻页结果(将当前页翻过去还是没有翻过去)。这个操作在 这个操作在touchesEnded:withEvent中完成



复制代码

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

......

UITouch *touch = [event.allTouches anyObject];

CGPoint touchPoint = [touch locationInView:self];

BOOL dragged = distance(touchPoint, touchBeganPoint) > [self dragThreshold];

[CATransaction begin];

float duration;

if ((dragged && self.leafEdge < 0.5) || (!dragged && [self touchedNextPage])) {

[self willTurnToPageAtIndex:currentPageIndex + numberOfVisiblePages];

self.leafEdge = 0;

duration = leafEdge;

......

}

else {

[self willTurnToPageAtIndex:currentPageIndex];

self.leafEdge = 1.0;

duration = 1 - leafEdge;

.......

}

[CATransaction setValue:[NSNumber numberWithFloat:duration]

forKey:kCATransactionAnimationDuration];

[CATransaction commit];

}





如果需要在翻页后执行某些操作(如在屏幕上显示当前页数等),则可以在继承自 LevelsViewController的控制器中实现leavesView:didTurnToPageAtIndex方法。



在此需要注意的就是 topPageReverseImage在竖屏时做了如下的变换



复制代码

topPageReverseImage.contentsGravity = kCAGravityRight;

topPageReverseImage.transform = CATransform3DMakeScale(-1, 1, 1);



从而使topPageReverseImage显示的内容让人感觉是透过纸张,看到topPage的内容。



横屏与竖屏

Leaves还有一个特点就是其支持横屏时,能同时看到两页的内容(该效果是由Ole Begemann改进的)。该改进最关键的地方就是增加了leftPage层,同时在横屏显示时将屏幕一分为二,在左侧显示leftPage。同进在翻页的过程中,topPageReverseImage显示的是topPage页下一页的内容。在翻转屏幕时,会根据方向重新调整内容的显示。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: