首页 > 代码库 > 【iOS开发每日小笔记(三)】利用iOS7 UIKit Dynamics 仿Zaker客户端首页动态效果

【iOS开发每日小笔记(三)】利用iOS7 UIKit Dynamics 仿Zaker客户端首页动态效果

这篇文章是我的【iOS开发每日小笔记】系列中的一片,记录的是今天在开发工作中遇到的,可以用很短的文章或很小的demo演示解释出来的小心得小技巧。该分类的文章,内容涉及的知识点可能是很简单的、或是用很短代码片段就能实现的,但在我看来它们可能会给用户体验、代码效率得到一些提升,或是之前自己没有接触过的技术,很开心的学到了,放在这里得瑟一下(^_^)。其实,90%的作用是帮助自己回顾、记忆、复习。如果看官觉得太easy,太碎片,则可以有两个选择:1,移步【iOS探究】分类,对那里的文章进行斧正;2,在本文的评论里狠狠吐槽,再关掉页面!感谢!

 

一直很喜欢Zaker这款应用,不论是iPad版本还是iPhone版,都让阅读很有效率。Zaker的左右滑动切换上下文,下拉到顶后退出阅读等等这些手势操作也让单手操作很方便。Zaker的封面,下拉后会从上掉落,到屏幕底部时,会有一个像球一样回弹的效果,印象中之前有看到有模仿的demo,但是之前也没仔细研究过。

最近后知后觉看到iOS 7的新特性 UIKit Dynamics后,眼前一亮,感觉几行代码就能把原先可能需要复杂动画代码才能达到的效果做出来啊!赶紧动手试试。

由于是【笔记】分类,对于UIKit Dynamics我就不去做全面的讲解,自己觉得对于这个2013年的“新”东西,还没能完全掌握精通。那么如果你碰巧看到此文,又想好好认识一下UIKit Dynamics这个东西,建议你可以看看下面几篇文章:

1,初次尝新,想3分钟上手:http://onevcat.com/2013/06/uikit-dynamics-started/

2,想稍微详细的入门一下:http://www.raywenderlich.com/zh-hans/52617/uikit-%E5%8A%9B%E5%AD%A6%E6%95%99%E7%A8%8B

3,老外的文章:http://www.objc.io/issue-5/collection-views-and-uidynamics.html

 

好了下面开始“老访”一个Zaker iPhone客户端的下拉封面(这里可以看到demo:https://github.com/pigpigdaddy/UIKitDynamicFakeZakerDemo)

首先引用“http://onevcat.com/2013/06/uikit-dynamics-started/”的表述:
“UIKit动力学其实就是UIKit的一套动画和交互体系。我们现在进行UI动画基本都是使用CoreAnimation或者UIView animations。而UIKit动力学最大的特点是将现实世界动力驱动的动画引入了UIKit,比如重力,铰链连接,碰撞,悬挂等效果。一言蔽之,即是,将2D物理引擎引入了人UIKit。需要注意,UIKit动力学的引入,并不是以替代CA或者UIView动画为目的的,在绝大多数情况下CA或者UIView动画仍然是最优方案,只有在需要引入逼真的交互设计的时候,才需要使用UIKit动力学它是作为现有交互设计和实现的一种补充而存在的。”

UIKit Dynamics使用起来可谓非常简单,其主要的步骤和要素有以下几点:

1,UIDynamicItem:一个力学物体,也就是实现了<UIDynamicItem>委托的对象(比如UIView本身就实现了<UIDynamicItem>,因此UIView的子类就是一个UIDynamicItem);

2,UIDynamicBehavior:【引用自onevcat】“动力行为的描述,用来指定UIDynamicItem应该如何运动,即定义适用的物理规则。一般我们使用这个类的子类对象来对一组UIDynamicItem应该遵守的行为规则进行描述”,意思就是UIDynamicBehavior有很多子类,比如UIGravityBehavior负责重力、UICollisionBehavior负责碰撞等,我们可以用这些类来对UIDynamicItem施加不同运动行为;

3,UIDynamicAnimator;【引用自onevcat】“动画的播放者,动力行为(UIDynamicBehavior)的容器,添加到容器内的行为将发挥作用”,也就是说,最后的“动画”播放,是需要讲上面的所有UIDynamicBehavior及其子类,放入这个UIDynamicAnimator容器内部,才能进行统一播放的;

4,ReferenceView:【引用自onevcat】“等同于力学参考系,如果你的初中物理不是语文老师教的话,我想你知道这是啥..只有当想要添加力学的UIView是ReferenceView的子view时,动力UI才发生作用”。

5,另外,需要注意的是,UIDynamicBehavior的子类通常有很多参数可以设置,如摩擦力、弹性系数、重力方向、碰撞范围等等,还有物体触发某些状态的众多Protocal可以回调使用。以上这些配合使用可以实现很多发杂的动画效果。

OK,明确了上面这些,我们可以设计我们的demo了,我在空项目中创建了ViewController,用于window的rootViewController,如下:

 1 #import "ViewController.h" 2  3 @interface ViewController ()<UICollisionBehaviorDelegate> 4  5 @property (nonatomic, strong)UIDynamicAnimator *animator; 6  7 @end 8  9 @implementation ViewController10 11 - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil12 {13     self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];14     if (self) {15         // Custom initialization16     }17     return self;18 }19 20 - (void)viewDidLoad21 {22     [super viewDidLoad];23     // Do any additional setup after loading the view.24     25     [self initFakeZakerView];26 }27 28 - (void)initFakeZakerView29 {30     UIView *zakerFaceView = [[UIView alloc] initWithFrame:CGRectMake(0, -self.view.bounds.size.height, self.view.bounds.size.width, self.view.bounds.size.height)];31     zakerFaceView.backgroundColor = [UIColor darkGrayColor];32     [self.view addSubview:zakerFaceView];// ******* 1 *******33     34     self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];// ******* 2 *******35     UIGravityBehavior* gravityBeahvior = [[UIGravityBehavior alloc] initWithItems:@[zakerFaceView]];36     gravityBeahvior.magnitude = 6.0;37     [self.animator addBehavior:gravityBeahvior];// ******* 3 *******38     39     UICollisionBehavior* collisionBehavior = [[UICollisionBehavior alloc] initWithItems:@[zakerFaceView]];40     [collisionBehavior addBoundaryWithIdentifier:@"collisionBoundary" fromPoint:CGPointMake(0, self.view.bounds.size.height+1) toPoint:CGPointMake(self.view.bounds.size.width, self.view.bounds.size.height+1)];41     collisionBehavior.collisionDelegate = self;42     [self.animator addBehavior:collisionBehavior];// ******* 4 *******43     44     UIDynamicItemBehavior* itemBehaviour = [[UIDynamicItemBehavior alloc] initWithItems:@[zakerFaceView]];45     itemBehaviour.elasticity = 0.4;46     [self.animator addBehavior:itemBehaviour];// ******* 5 *******47 }48 49 @end

解释一下上面5个步骤:

1,我创建了一个当前Controller.View大小的子View,设置为深灰色,并把它加到屏幕上方以外,用于一会儿从上面掉落下来;

2,初始化我的UIDynamicAnimator property变量,(这里需要注意一点,该变量是@property strong指针,确保出了本函数作用域,动画仍然能正常进行,不能使用局部strong指针哦,因为这样的话,动画是无法播放的),接着使用self.view作为参考系View。

3,要想屏幕上方以外的深灰色View掉落,必然需要一个重力,因此UIGravityBehavior便出场了。首先设置好item,也就是我的zakerFaceView。接着,设置好重力加速度的“描述值”,gravityBeahvior.magnitude = 6.0;这里我自创了“描述值”这个名词,因为:Apple定义了自己的重力学定律,将牛顿力学中的单位米每平方秒用像素每平方秒代替。但是,正像http://www.raywenderlich.com/zh-hans/52617/uikit-%E5%8A%9B%E5%AD%A6%E6%95%99%E7%A8%8B里说的一样:“你真的需要了解这些么?未必,你只需要知道更大的 g 意味着掉落得更快,但是了解背后的数学原理有利无弊。”所以我用“描述值”来表述magnitude这个参数,这个值越大,物体(item)掉落的速度越快。这里经过尝试,gravityBeahvior.magnitude = 6.0;时效果与Zaker的封面掉落效果最接近。

4,碰撞监测,界面掉落,需要检测到碰撞到界面下边缘,于是UICollisionBehavior便登场了。还是一样,首先设置item为zakerFaceView。接着设置碰撞的边缘的物体边缘,UICollisionBehavior提供了以下几种:

1,@property (nonatomic, readwrite) BOOL translatesReferenceBoundsIntoBoundary;2,- (void)setTranslatesReferenceBoundsIntoBoundaryWithInsets:(UIEdgeInsets)insets;3,- (void)addBoundaryWithIdentifier:(id <NSCopying>)identifier forPath:(UIBezierPath*)bezierPath;4,- (void)addBoundaryWithIdentifier:(id <NSCopying>)identifier fromPoint:(CGPoint)p1 toPoint:(CGPoint)p2;

分别解释一下:1,是否将Reference View的Bounds范围作为碰撞的界限;2,设置Reference View的Bounds范围作为碰撞的界限的UIEdgeInsets;3,添加一个碰撞边界,该边界是一个贝赛尔曲线(很方便吧?);4,添加一个碰撞边界,该边界是一个点到另一个点的直线。

在本例中,我将使用最后一个方法,添加碰撞边界为

CGPointMake(0, self.view.bounds.size.height+1) toPoint:CGPointMake(self.view.bounds.size.width, self.view.bounds.size.height+1)

意义在于将边界设置为“底边外一个CGPoint”的一条线。

最后设置碰撞的代理,在本例中,我没有实现代理中的方法,因为没有用到。不过可以顺带看一下有以下代理方法,用于检测碰撞的各个事件:

1 - (void)collisionBehavior:(UICollisionBehavior*)behavior beganContactForItem:(id <UIDynamicItem>)item1 withItem:(id <UIDynamicItem>)item2 atPoint:(CGPoint)p;2 - (void)collisionBehavior:(UICollisionBehavior*)behavior endedContactForItem:(id <UIDynamicItem>)item1 withItem:(id <UIDynamicItem>)item2;3 4 // The identifier of a boundary created with translatesReferenceBoundsIntoBoundary or setTranslatesReferenceBoundsIntoBoundaryWithInsets is nil5 - (void)collisionBehavior:(UICollisionBehavior*)behavior beganContactForItem:(id <UIDynamicItem>)item withBoundaryIdentifier:(id <NSCopying>)identifier atPoint:(CGPoint)p;6 - (void)collisionBehavior:(UICollisionBehavior*)behavior endedContactForItem:(id <UIDynamicItem>)item withBoundaryIdentifier:(id <NSCopying>)identifier;

5,弹跳设置

能掉落了,能碰撞了,还差一个:弹跳。

通过UIDynamicItemBehavior可设置item的如下参数:

1 @property (readwrite, nonatomic) CGFloat elasticity; // Usually between 0 (inelastic) and 1 (collide elastically) 2 @property (readwrite, nonatomic) CGFloat friction; // 0 being no friction between objects slide along each other3 @property (readwrite, nonatomic) CGFloat density; // 1 by default4 @property (readwrite, nonatomic) CGFloat resistance; // 0: no velocity damping5 @property (readwrite, nonatomic) CGFloat angularResistance; // 0: no angular velocity damping6 @property (readwrite, nonatomic) BOOL allowsRotation; // force an item to never rotate

我们现在需要的是弹性elasticity,OK经过尝试,itemBehaviour.elasticity = 0.4;比较合适。

别忘了每次要把UIDynamicBehavior子类加到self.animator中。

运行,一切正常运行!效果就像上面的Gif图片现实的那样!