ios开发——经典翻页库Leaves源码解析

在做文本app的时候经常会用到翻页效果,我了解的翻页效果有三种。最基本的是用transition动画的CurlDown和CurlUp来实现,这种实现非常简单,但是不能控制过程。然后是类似开源库Leaves提供的方法一样,可以控制中间过程,不过只能左右水平翻页。再者就是apple的iBooks以及目前大多数电子书应用都能实现的“最真实的翻页”,不但可以水平控制,而且可以有“折角”,效果也很流畅。

第三种的实现难度略大,暂且不提,这里一起看一下经典的Leaves库,这个库的代码写得非常清晰易懂,适合初学者学习。


代码结构:


Leaves代码遵循MVC设计模式。LeavesCache是model,主要功能是获得当前页图片、缓存某一页图片以及对数据的清空等操作。LeavesView是View,主要是展示平常状态下以及翻页过程中的图像、层等内容,是接下来详细研究的类。LeavesViewController是Controller,一般当我们整个视图都是LeavesView时我们可以继承这个控制器,这个控制器已经将dataSource设为自己,所以我们只需重写dataSource协议方法即可。


LeavesCache类:



其中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;<span style="white-space:pre">	</span>//表示出于未翻页状态
        _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方法中通过计算判断移动方向,然后再对层位置、阴影位置等做出调整 - -||   想想就觉得复杂。。。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值