ios開發——經典翻頁庫Leaves源碼解析 – iPhone手機開發技術文章 iPhone軟體開發教學課程

在做文本app的時候經常會用到翻頁效果,我瞭解的翻頁效果有三種。最基本的是用transition動畫的CurlDown和CurlUp來實現,這種實現非常簡單,但是不能控制過程。然後是類似開源庫Leaves提供的方法一樣,可以控制中間過程,不過隻能左右水平翻頁。再者就是apple的iBooks以及目前大多數電子書應用都能實現的“最真實的翻頁”,不但可以水平控制,而且可以有“折角”,效果也很流暢。

第三種的實現難度略大,暫且不提,這裡一起看一下經典的Leaves庫,這個庫的代碼寫得非常清晰易懂,適合初學者學習。

代碼結構:

Leaves代碼遵循MVC設計模式。LeavesCache是model,主要功能是獲得當前頁圖片、緩存某一頁圖片以及對數據的清空等操作。LeavesView是View,主要是展示平常狀態下以及翻頁過程中的圖像、層等內容,是接下來詳細研究的類。LeavesViewController是Controller,一般當我們整個視圖都是LeavesView時我們可以繼承這個控制器,這個控制器已經將dataSource設為自己,所以我們隻需重寫dataSource協議方法即可。

vcD4KPHA+TGVhdmVzQ2FjaGXA4KO6PC9wPgo8cD48aW1nIHNyYz0=”/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方法中通過計算判斷移動方向,然後再對層位置、陰影位置等做出調整 – -“| 想想就覺得復雜。。。

發佈留言