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頁下一頁的內容。在翻轉螢幕時,會根據方向重新調整內容的顯示。
參考資料:App Store-safe Page Curl animations
源碼:https://github.com/brow/leaves