ios開發——經典翻頁庫Leaves源碼解析
在做文本app的時候經常會用到翻頁效果,我瞭解的翻頁效果有三種。最基本的是用transition動畫的CurlDown和CurlUp來實現,這種實現非常簡單,但是不能控制過程。然後是類似開源庫Leaves提供的方法一樣,可以控制中間過程,不過只能左右水平翻頁。再者就是apple的iBooks以及目前大多數電子書應用都能實現的“最真實的翻頁”,不但可以水平控制,而且可以有“折角”,效果也很流暢。
第三種的實現難度略大,暫且不提,這裡一起看一下經典的Leaves庫,這個庫的代碼寫得非常清晰易懂,適合初學者學習。
代碼結構:
Leaves代碼遵循MVC設計模式。LeavesCache是model,主要功能是獲得當前頁圖片、緩衝某一頁圖片以及對資料的清空等操作。LeavesView是View,主要是展示平常狀態下以及翻頁過程中的映像、層等內容,是接下來詳細研究的類。LeavesViewController是Controller,一般當我們整個視圖都是LeavesView時我們可以繼承這個控制器,這個控制器已經將dataSource設為自己,所以我們只需重寫dataSource協議方法即可。
<喎?http://www.bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+TGVhdmVzQ2FjaGXA4KO6PC9wPgo8cD48aW1nIHNyYz0="http://www.2cto.com/uploadfile/Collfiles/20141012/201410120913426.png" alt="\">
其中pageSize指每一頁的大小,在view中被設定為self.bounds。dataSource為資料來源。
載入圖片的方法是,定義一個字典pageCache用來儲存已經被載入過的映像,每次需要擷取圖片時從這裡面擷取,如果為nil,則說明或者是第一次載入,或者已經被刪掉了,需要調用私人方法imageForPageIndex:來重新擷取。
該方法中用Quartz2D來截取螢幕獲得圖片,這裡返回的是CGImageRef,目的是方便為圖層的contents賦值。
有了這兩個操作,剩下的就簡單了,主要就是調用這兩個方法進行載入、預先載入、清空等。
而最後的minimizeToPageIndex:方法,正如其注釋:Uncache all pages except previous, current, and next(除去當前頁、前一頁和後一頁以外其餘全部清除),目的是節省空間的,在View中每次翻到某一頁即調用該方法。
LeavesView:
在LeavesView的標頭檔中對每個屬性和方法都做了詳盡的注釋,比如點擊進入下一頁的間距targetWidth,是否支援預先載入的backgroundRendering等。關鍵是實現檔案中的一些私人屬性的理解:
關於其中各種層和陰影的理解,都體現在接下來的方法initCommon中,該方法是初始化這些層並為其設定基本屬性的方法:
- (void)initCommon {self.clipsToBounds = YES; //最頂層_topPage = [[CALayer alloc] init];_topPage.masksToBounds = YES;_topPage.contentsGravity = kCAGravityLeft;_topPage.backgroundColor = [[UIColor whiteColor] CGColor]; //翻頁過程中的最頂層_topPageOverlay = [[CALayer alloc] init];_topPageOverlay.backgroundColor = [[[UIColor blackColor] colorWithAlphaComponent:0.2] CGColor]; //翻頁過程中壓在上一頁上的陰影層_topPageShadow = [[CAGradientLayer alloc] init];_topPageShadow.colors = [NSArray arrayWithObjects:(id)[[[UIColor blackColor] colorWithAlphaComponent:0.6] CGColor],(id)[[UIColor clearColor] CGColor],nil]; //映射的起止點,類比PS裡漸層的滑鼠開始點與鬆開點_topPageShadow.startPoint = CGPointMake(1,0.5);_topPageShadow.endPoint = CGPointMake(0,0.5); //書頁背面的一層_topPageReverse = [[CALayer alloc] init];_topPageReverse.backgroundColor = [[UIColor whiteColor] CGColor];_topPageReverse.masksToBounds = YES; //書背面顯示倒影圖片的一層_topPageReverseImage = [[CALayer alloc] init];_topPageReverseImage.masksToBounds = YES;_topPageReverseImage.contentsGravity = kCAGravityRight; //書背面頁的最頂層_topPageReverseOverlay = [[CALayer alloc] init];_topPageReverseOverlay.backgroundColor = [[[UIColor whiteColor] colorWithAlphaComponent:0.8] CGColor]; //書背面頁的陰影層_topPageReverseShading = [[CAGradientLayer alloc] init];_topPageReverseShading.colors = [NSArray arrayWithObjects:(id)[[[UIColor blackColor] colorWithAlphaComponent:0.6] CGColor],(id)[[UIColor clearColor] CGColor],nil];_topPageReverseShading.startPoint = CGPointMake(1,0.5);_topPageReverseShading.endPoint = CGPointMake(0,0.5); //下一頁的一層_bottomPage = [[CALayer alloc] init];_bottomPage.backgroundColor = [[UIColor whiteColor] CGColor];_bottomPage.masksToBounds = YES; //壓在下一頁上的陰影層_bottomPageShadow = [[CAGradientLayer alloc] init];_bottomPageShadow.colors = [NSArray arrayWithObjects: (id)[[[UIColor blackColor] colorWithAlphaComponent:0.6] CGColor], (id)[[UIColor clearColor] CGColor], nil];_bottomPageShadow.startPoint = CGPointMake(0,0.5);_bottomPageShadow.endPoint = CGPointMake(1,0.5);[_topPage addSublayer:_topPageShadow];[_topPage addSublayer:_topPageOverlay];[_topPageReverse addSublayer:_topPageReverseImage];[_topPageReverse addSublayer:_topPageReverseOverlay];[_topPageReverse addSublayer:_topPageReverseShading];[_bottomPage addSublayer:_bottomPageShadow];[self.layer addSublayer:_bottomPage];[self.layer addSublayer:_topPage];[self.layer addSublayer:_topPageReverse];_leafEdge = 1.0;//表示出於未翻頁狀態 _backgroundRendering = NO;_pageCache = [[LeavesCache alloc] initWithPageSize:self.bounds.size];}
其中用到了CoreAnimation的相關知識,比如普通層CALayer,控制色彩坡形的CAGradientLayer等。
初始化完成以後便可以在layoutSubview方法中為其設定位置和填充資料,這裡分別抽成了兩個方法:
- (void)setLayerFrames {self.topPage.frame = CGRectMake(self.layer.bounds.origin.x, self.layer.bounds.origin.y, self.leafEdge * self.bounds.size.width, self.layer.bounds.size.height); self.topPageReverse.frame = CGRectMake(self.layer.bounds.origin.x + (2*self.leafEdge-1) * self.bounds.size.width, self.layer.bounds.origin.y, (1-self.leafEdge) * self.bounds.size.width,self.layer.bounds.size.height); self.bottomPage.frame = self.layer.bounds; self.topPageShadow.frame = CGRectMake(self.topPageReverse.frame.origin.x - 40, 0, 40, self.bottomPage.bounds.size.height); self.topPageReverseImage.frame = self.topPageReverse.bounds;self.topPageReverseImage.transform = CATransform3DMakeScale(-1, 1, 1); self.topPageReverseOverlay.frame = self.topPageReverse.bounds; self.topPageReverseShading.frame = CGRectMake(self.topPageReverse.bounds.size.width - 50, 0, 50 + 1,self.topPageReverse.bounds.size.height); self.bottomPageShadow.frame = CGRectMake(self.leafEdge * self.bounds.size.width, 0, 40, self.bottomPage.bounds.size.height); self.topPageOverlay.frame = self.topPage.bounds;}
剛讀到這裡的時候可能會對self.leafEdge的控制感到迷惑,到了touch相關方法的時候就能瞭解了。
設定圖片的方法:
有了這兩個輔助方法,就可以在layoutSubview方法中完成基本介面了
接下來還有兩個值得注意的地方,其餘的均為輔助方法了。
一個是對頁數的控制,即self.currentPageIndex。currentPageIndex仿照數組,是從0開始的。而且重寫了setter:
也就是說每次設定currentPageIndex都要重新設定圖片,所以設定currentPageIndex的原則是:如果是上一頁,則直接設定,因為對於上一頁而言,本頁面正好是bottomLayer對應的層,而對於下一頁,只有當完全翻頁以後才能設定currentPageIndex,否則圖片就不相符了。說起來抽象,具體看代碼:
touchesBegin:withEvent:方法
touchesEnded:withEvent:方法
還有一個問題就是在touch過程中對leafEdge的控制
這樣就可以實現對層的位置(見上述方法)、以及層陰影的透明度等進行控制了。
可以在touchesMoved方法中列印一下leafEdge,這樣可以直觀的看到該屬性是如何控制這些的。
LeavesViewController:
這個控制器就沒什麼特別的了,僅僅是將LeavesView添加到了視圖中並設定了代理,抽象實現了方法而已
通過這種方法的話可以想象那種完美的翻頁效果該如何?:在touchesMove方法中通過計算判斷移動方向,然後再對層位置、陰影位置等做出調整 - -"| 想想就覺得複雜。。。