Dynamic Animator 、 CollectionViewLayout

1 给视图添加重力效果

1.1 问题

当给某个视图加上UIGravityBehavior重力行为之后,这个视图就具有重力,会如同掉入了无底洞,不断地下坠,不断的加速,本案例使用UIGravityBehavior重力行为给imageView添加重力行为,如图-1所示:

图-1

1.2 方案

首先在创建好的Xcode项目的Storyboard中拖放一个ImageView控件和两个Button控件,在右边栏的检查器中设置好ImageView的显示图片。

将ImageView控件关联成TRViewController的属性imageView,两个按钮关联成TRViewController的动作方法start和end。

其次在TRViewController中定义两个私有属性UIDynamicAnimator类型的animator和UIGravityBehavior类型的gravityBehavior。

然后在viewDidLoad方法中创建属性animator和gravityBehavior,将self.view设置为属性animator的referenceView引用视图,给属性imageView添加重力行为。

最后实现start方法和end方法,点击start按钮imageView开始执行重力行为往下坠,点击end按钮停止重力行为。

1.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:搭建Storyboard界面

首先在创建好的Xcode项目的Storyboard中拖放一个ImageView控件和两个Button控件,在右边栏的检查器中设置好ImageView的显示图片,Storyboard中的界面如图-2所示:

图-2

将ImageView控件关联成TRViewController的属性imageView,两个按钮关联成TRViewController的动作方法start和end,代码如下所示:

  1. @interface TRViewController ()
  2. @property (weak, nonatomic) IBOutlet UIImageView *imageView;
  3. @end

步骤二:定义animator和gravityBehavior属性

首先在TRViewController中定义两个私有属性UIDynamicAnimator类型的animator和UIGravityBehavior类型的gravityBehavior,代码如下所示:

  1. @interface TRViewController ()
  2. @property (weak, nonatomic) IBOutlet UIImageView *imageView;
  3. @property (strong, nonatomic)UIDynamicAnimator *animator;
  4. @property (strong, nonatomic)UIGravityBehavior *gravityBehavior;
  5. @end

然后在viewDidLoad方法中创建属性animator和gravityBehavior,将self.view设置为属性animator的referenceView引用视图,给属性imageView添加重力行为,代码如下所示:

  1. - (void)viewDidLoad
  2. {
  3. [super viewDidLoad];
  4. //将self.view设置为动力坐标系的参考
  5.     UIDynamicAnimator *animator = [[UIDynamicAnimator alloc]initWithReferenceView:self.view];
  6. //给imageView添加重力行为
  7. UIGravityBehavior *gravity = [[UIGravityBehavior alloc]initWithItems:@[self.imageView]];
  8. self.gravityBehavior = gravity;
  9. self.animator = animator;
  10. }

步骤三:实现start和end方法

当点击start按钮时imageView开始执行重力行为往下坠,在start方法中使用addBehavior:方法执行重力行为。

当点击end按钮时停止重力行为,在end方法中使用removeBehavior:方法将重力行为移除,代码如下所示:

  1. - (IBAction)start
  2. {
  3. //当点击start按钮时将重力行为添加到self.animator
  4. [self.animator addBehavior:self.gravityBehavior];
  5. }
  6. - (IBAction)end
  7. {
  8. //当点击end按钮时将重力行为移除
  9. [self.animator removeBehavior:self.gravityBehavior];
  10. }

1.4 完整代码

本案例中,TRViewController.m文件中的完整代码如下所示:

 
  1. #import "TRViewController.h"
  2. @interface TRViewController ()
  3. @property (weak, nonatomic) IBOutlet UIImageView *imageView;
  4. @property (strong, nonatomic)UIDynamicAnimator *animator;
  5. @property (strong, nonatomic)UIGravityBehavior *gravityBehavior;
  6. @end
  7. @implementation TRViewController
  8. - (void)viewDidLoad
  9. {
  10. [super viewDidLoad];
  11. //将self.view设置为动力坐标系的参考
  12.     UIDynamicAnimator *animator = [[UIDynamicAnimator alloc]initWithReferenceView:self.view];
  13. //给imageView添加重力行为
  14. UIGravityBehavior *gravity = [[UIGravityBehavior alloc]initWithItems:@[self.imageView]];
  15. self.gravityBehavior = gravity;
  16. self.animator = animator;
  17. }
  18. - (IBAction)start
  19. {
  20. //当点击start按钮时将重力行为添加到self.animator
  21. [self.animator addBehavior:self.gravityBehavior];
  22. }
  23. - (IBAction)end
  24. {
  25. //当点击end按钮时将重力行为移除
  26. [self.animator removeBehavior:self.gravityBehavior];
  27. }
  28. @end
 

2 给视图添加碰撞效果

2.1 问题

如果给某个视图添加了UICollisionBehavior碰撞行为,该视图就有碰撞能力,能与引用视图产生碰撞效果,本案例使用UICollisionBehavior给视图添加碰撞效果,如图-3所示:

图-3

2.2 方案

首先在创建好的Xcode项目的Storyboard中拖放一个ImageView控件,在右边栏的检查器中设置好ImageView的显示图片。将ImageView控件关联成TRCollisionViewController的属性boxImageView。

其次创建一个TRBackgroundView类继承至UIView,该类有一个UIBezierPath类型的公开属性path。在Storyboard中将View关联成TRBackgroundView。

在TRBackgroundView.m文件中重写initWithCoder:方法和drawRect:绘制方法,initWithCoder:方法中使用colorWithPatternImage:方法设置背景颜色,drawRect:方法中则根据path属性进行绘制。

然后在TRCollisionViewController类中定义一个UIDynamicAnimator类型的属性animator。

在viewDidLoad方法中使boxImageView旋转45度,使一个角朝下。

在viewDidAppear:方法中创建属性animator,将self.view设置为属性animator的referenceView引用视图,给属性boxImageView添加重力行为和碰撞行为,使boxImageView和绘制出来的path进行碰撞。

最后通过实现UICollisionBehaviorDelegate的协议方法,当碰撞开始时boxImageView颜色发生改变。

2.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:拖放ImageView控件

首先在创建好的Xcode项目的Storyboard中拖放一个ImageView控件,在右边栏的检查器中设置好ImageView的显示图片。将ImageView控件关联成TRCollisionViewController的属性boxImageView,代码如下所示:

 
  1. @interface TRCollisionViewController () <UICollisionBehaviorDelegate>
  2. @property (weak, nonatomic) IBOutlet UIImageView *boxImageView;
  3. @end

步骤二:创建TRBackgroundView类

首先创建一个TRBackgroundView类继承至UIView,该类有一个UIBezierPath类型的公开属性path。在Storyboard中将View关联成TRBackgroundView,如图-4所示:

图-4

然后在TRBackgroundView.m文件中重写initWithCoder:方法和drawRect:绘制方法,initWithCoder:方法中使用colorWithPatternImage:方法设置背景颜色,drawRect:方法中则根据path属性进行绘制,代码如下所示:

 
  1. #import "TRBackgroundView.h"
  2. @implementation TRBackgroundView
  3. - (id)initWithCoder:(NSCoder *)aDecoder
  4. {
  5. self = [super initWithCoder:aDecoder];
  6. if(self){
  7. self.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"BackgroundTile"]];
  8. }
  9. return self;
  10. }
  11. - (void)drawRect:(CGRect)rect
  12. {
  13. [[UIColor redColor]setFill];
  14. [[UIColor greenColor]setStroke];
  15. [self.path stroke];
  16. [self.path fill];
  17. }
  18. @end

步骤三:添加重力行为和碰撞行为

首先在TRCollisionViewController类中定义一个UIDynamicAnimator类型的私用属性animator,代码如下所示:

 
  1. @interface TRCollisionViewController () <UICollisionBehaviorDelegate>
  2. @property (weak, nonatomic) IBOutlet UIImageView *boxImageView;
  3. @property (strong, nonatomic) UIDynamicAnimator *animator;
  4. @end

然后在viewDidLoad方法中使boxImageView旋转45度,使boxImageView的一个角朝下,代码如下所示:

 
  1. - (void)viewDidLoad
  2. {
  3. [super viewDidLoad];
  4. self.boxImageView.transform = CGAffineTransformRotate(self.boxImageView.transform, M_PI_4);
  5. }

最后在viewDidAppear:方法中创建属性animator,将self.view设置为属性animator的referenceView引用视图,给属性boxImageView添加重力行为和碰撞行为,使boxImageView和绘制出来的path进行碰撞,代码如下所示:

 
  1. - (void)viewDidAppear:(BOOL)animated
  2. {
  3. UIDynamicAnimator *animator = [[UIDynamicAnimator alloc]initWithReferenceView:self.view];
  4. self.animator = animator;
  5. //创建重力行为
  6. UIGravityBehavior *gravity = [[UIGravityBehavior alloc]initWithItems:@[self.boxImageView]];
  7. //重力的强度
  8. gravity.magnitude = 0.1;
  9. //重力的方向
  10. gravity.gravityDirection = CGVectorMake(0, 1);
  11. [animator addBehavior:gravity];
  12. //创建碰撞行为
  13. UICollisionBehavior *collision = [[UICollisionBehavior alloc]initWithItems:@[self.boxImageView]];
  14. //将引用视图的四周翻译成可碰撞的四个边
  15. collision.translatesReferenceBoundsIntoBoundary = YES;
  16. //设置delegate
  17. collision.collisionDelegate = self;
  18. //用UIBezierPath创建一个形状并具有碰撞行为
  19. UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(20, 450, 280, 30) cornerRadius:10.0];
  20. TRBackgroundView *myView = (TRBackgroundView *)self.view;
  21. myView.path = path;
  22. [myView setNeedsDisplay];
  23. [collision addBoundaryWithIdentifier:@"MyPathIdenrifier" forPath:path];
  24. [animator addBehavior:collision];
  25. }

步骤四:实现协议方法

实现UICollisionBehaviorDelegate的协议方法beganContactForItem: withBoundaryIdentifier:atPoint:,完碰撞开始时boxImageView颜色发生改变,代码如下所示:

 
  1. - (void)collisionBehavior:(UICollisionBehavior*)behavior beganContactForItem:(id <UIDynamicItem>)item withBoundaryIdentifier:(id <NSCopying>)identifier atPoint:(CGPoint)p
  2. {
  3. UIImageView *box = (UIImageView *)item;
  4. box.tintColor = [UIColor redColor];
  5. // imageWithRenderingMode:IOS7提供的新的功能,设置一个UIImage在渲染时是否使用当前视图的Tint Color
  6. //UIImageRenderingModeAlwaysTemplate表示始终根据Tint Color绘制图片,忽略图片的颜色信息。
  7. box.image = [box.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
  8. NSLog(@"%@,%@,point:(%.0f, %.0f", item, identifier, p.x, p.y);
  9. }

2.4 完整代码

本案例中,TRCollisionViewController.m文件中的完整代码如下所示:

 
  1. #import "TRCollisionViewController.h"
  2. #import "TRBackgroundView.h"
  3. @interface TRCollisionViewController () <UICollisionBehaviorDelegate>
  4. @property (weak, nonatomic) IBOutlet UIImageView *boxImageView;
  5. @property (strong, nonatomic) UIDynamicAnimator *animator;
  6. @end
  7. @implementation TRCollisionViewController
  8. - (void)viewDidLoad
  9. {
  10. [super viewDidLoad];
  11. self.boxImageView.transform = CGAffineTransformRotate(self.boxImageView.transform, M_PI_4);
  12. }
  13. - (void)viewDidAppear:(BOOL)animated
  14. {
  15. UIDynamicAnimator *animator = [[UIDynamicAnimator alloc]initWithReferenceView:self.view];
  16. self.animator = animator;
  17. //重力行为
  18. UIGravityBehavior *gravity = [[UIGravityBehavior alloc]initWithItems:@[self.boxImageView]];
  19. //重力的强度
  20. gravity.magnitude = 0.1;
  21. //重力的方向
  22. gravity.gravityDirection = CGVectorMake(0, 1);
  23. [animator addBehavior:gravity];
  24. //碰撞行为
  25. UICollisionBehavior *collision = [[UICollisionBehavior alloc]initWithItems:@[self.boxImageView]];
  26. //将引用视图的四周翻译成可碰撞的四个边
  27. collision.translatesReferenceBoundsIntoBoundary = YES;
  28. collision.collisionDelegate = self;
  29. //用UIBezierPath创建一个形状并具有碰撞行为
  30. UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(20, 450, 280, 30) cornerRadius:10.0];
  31. TRBackgroundView *myView = (TRBackgroundView *)self.view;
  32. myView.path = path;
  33. [myView setNeedsDisplay];
  34. [collision addBoundaryWithIdentifier:@"MyPathIdenrifier" forPath:path];
  35. [animator addBehavior:collision];
  36. }
  37. - (void)collisionBehavior:(UICollisionBehavior*)behavior beganContactForItem:(id <UIDynamicItem>)item withBoundaryIdentifier:(id <NSCopying>)identifier atPoint:(CGPoint)p
  38. {
  39. UIImageView *box = (UIImageView *)item;
  40. box.tintColor = [UIColor redColor];
  41. // imageWithRenderingMode:IOS7提供的新的功能,设置一个UIImage在渲染时是否使用当前视图的Tint Color
  42. //UIImageRenderingModeAlwaysTemplate表示始终根据Tint Color绘制图片,忽略图片的颜色信息。
  43. box.image = [box.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
  44. NSLog(@"%@,%@,point:(%.0f, %.0f", item, identifier, p.x, p.y);
  45. }
  46. - (void)collisionBehavior:(UICollisionBehavior*)behavior endedContactForItem:(id <UIDynamicItem>)item withBoundaryIdentifier:(id <NSCopying>)identifier
  47. {
  48. NSLog(@"....");
  49. }
  50. @end
 

本案例中,TRBackgroundView.h文件中的完整代码如下所示:

  1. #import<UIKit/UIKit.h>
  2. @interface TRBackgroundView : UIView
  3. @property (nonatomic, strong) UIBezierPath *path;
  4. @end
 

本案例中,TRBackgroundView.m文件中的完整代码如下所示:

 
  1. #import "TRBackgroundView.h"
  2. @implementation TRBackgroundView
  3. - (id)initWithCoder:(NSCoder *)aDecoder
  4. {
  5. self = [super initWithCoder:aDecoder];
  6. if(self){
  7. self.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"BackgroundTile"]];
  8. }
  9. return self;
  10. }
  11. - (void)drawRect:(CGRect)rect
  12. {
  13. [[UIColor redColor]setFill];
  14. [[UIColor greenColor]setStroke];
  15. [self.path stroke];
  16. [self.path fill];
  17. }
  18. @end
 

3 给视图添加吸附效果

3.1 问题

UIAttachmentBehavior吸附行为,一个视图产生某种行为后相关联的对象也随之一起运动,本案例使用UIAttachmentBehavior给视图添加吸附效果,是两个视图产生关联行为,如图-5所示:

图-5

3.2 方案

首先在创建好的Xcode项目的Storyboard中拖放一个ImageView控件,在右边栏的检查器中设置好ImageView的显示图片。将ImageView控件关联成TRAttachmentViewController的属性boxImageView。

本案例同样需要一个TRBackgroundView类,直接使用上一个案例的TRBackgroundView类即可,在Storyboard中将View关联成TRBackgroundView。

然后在TRAttachmentViewController类中定义三个属性分别为UIDynamicAnimator类型的animator,UIGravityBehavior类型的gravityBehavior,以及UIAttachmentBehavior类型的attachmentBehavior,并且使用重写setter的方法进行初始化。

在Storyboard中给View添加UIPanGestureRecognizer类型的手势,关联成pan:方法。

最后在TRAttachmentViewController中实现pan:方法,每次触摸屏幕则在boxImageView的中心点和手指触摸点绘制一条直线,并且boxImageView和手指触摸点产生关联的动力行为。

3.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:拖放ImageView控件

首先在创建好的Xcode项目的Storyboard中拖放一个ImageView控件,在右边栏的检查器中设置好ImageView的显示图片。将ImageView控件关联成TRAttachmentViewController的属性boxImageView,代码如下所示:

 
  1. @interface TRAttachmentViewController ()
  2. @property (weak, nonatomic) IBOutlet UIImageView *boxImageView;
  3. @end

本案例同样需要一个TRBackgroundView类,直接使用上一个案例的TRBackgroundView类即可,在Storyboard中将View关联成TRBackgroundView。

步骤二:定义属性

在TRAttachmentViewController类中定义三个属性分别为UIDynamicAnimator类型的animator,UIGravityBehavior类型的gravityBehavior,以及UIAttachmentBehavior类型的attachmentBehavior,并且使用重写setter的方法进行初始化,代码如下所示:

 
  1. @interface TRAttachmentViewController ()
  2. @property (weak, nonatomic) IBOutlet UIImageView *boxImageView;
  3. @property (strong, nonatomic) UIDynamicAnimator *animator;
  4. @property (strong, nonatomic) UIGravityBehavior *gravityBehavior;
  5. @property (strong, nonatomic) UIAttachmentBehavior *attachmentBehavior;
  6. @end
  7. //重写setter方法
  8. - (UIDynamicAnimator *)animator
  9. {
  10. if(!_animator)_animator = [[UIDynamicAnimator alloc]initWithReferenceView:self.view];
  11. return _animator;
  12. }
  13. - (UIGravityBehavior *)gravityBehavior
  14. {
  15. if (!_gravityBehavior) {
  16. _gravityBehavior = [[UIGravityBehavior alloc]initWithItems:@[self.boxImageView]];
  17. }
  18. return _gravityBehavior;
  19. }
  20. - (UIAttachmentBehavior *)attachmentBehavior
  21. {
  22. CGPoint attachedToAnchor = CGPointMake(self.boxImageView.center.x, self.boxImageView.frame.origin.y - 50);
  23. if (!_attachmentBehavior) {
  24. //使用initWithItem:attachedToAnchor:方法进行初始化,使boxImageView和attachedToAnchor产生关联行为
  25. _attachmentBehavior = [[UIAttachmentBehavior alloc]initWithItem:self.boxImageView attachedToAnchor:attachedToAnchor];
  26. }
  27. return _attachmentBehavior;
  28. }

步骤三:添加手势,实现关联动力行为

首先在Storyboard中给View添加UIPanGestureRecognizer类型的手势,关联成pan:方法。

然后在TRAttachmentViewController中实现pan:方法,该方法中根据手势的状态在boxImageView的中心点和手指触摸点绘制一条直线,并且boxImageView和手指触摸点产生关联的动力行为,代码如下所示:

 
  1. - (IBAction)pan:(UIPanGestureRecognizer *)sender
  2. {
  3. if (sender.state == UIGestureRecognizerStateBegan) {
  4. //手势开始添加关联行为和重力行为
  5. [self.animator addBehavior:self.gravityBehavior];
  6. self.attachmentBehavior.anchorPoint = [sender locationInView:self.view];
  7. //阻尼
  8. self.attachmentBehavior.damping = 0.1;
  9. //频率
  10. self.attachmentBehavior.frequency = 1.0;
  11. [self.animator addBehavior:self.attachmentBehavior];
  12. [self drawLine];
  13. [self.view setNeedsDisplay];
  14. }
  15. if(sender.state == UIGestureRecognizerStateChanged){
  16. CGPoint location = [sender locationInView:self.view];
  17. //手指移动过程中改变anchorPoint
  18. self.attachmentBehavior.anchorPoint = location;
  19. [self drawLine];
  20. [self.view setNeedsDisplay];
  21. }else if(sender.state == UIGestureRecognizerStateEnded){
  22. //手势结束移除关联行为和重力行为
  23. [self.animator removeBehavior:self.attachmentBehavior];
  24. [self.animator removeBehavior:self.gravityBehavior];
  25. ((TRBackgroundView *)self.view).path = nil;
  26. [self.view setNeedsDisplay];
  27. }
  28. }
  29. - (void)drawLine
  30. {
  31. __weak UIAttachmentBehavior *weakAttachmentBehavior = self.attachmentBehavior;
  32. __weak UIImageView *weakBoxImageView = self.boxImageView;
  33. __weak TRBackgroundView *weakMyView = (TRBackgroundView *)self.view;
  34. self.attachmentBehavior.action = ^{
  35. UIBezierPath *path = [UIBezierPath bezierPath];
  36. [path moveToPoint:weakAttachmentBehavior.anchorPoint];
  37. [path addLineToPoint:weakBoxImageView.center];
  38. path.lineWidth = 2;
  39. weakMyView.path = path;
  40. [weakMyView setNeedsDisplay];
  41. };
  42. }

3.4 完整代码

本案例中,TRAttachmentViewController.m文件中的完整代码如下所示:

 
  1. #import "TRAttachmentViewController.h"
  2. #import "TRBackgroundView.h"
  3. @interface TRAttachmentViewController ()
  4. @property (weak, nonatomic) IBOutlet UIImageView *boxImageView;
  5. @property (strong, nonatomic) UIDynamicAnimator *animator;
  6. @property (strong, nonatomic) UIGravityBehavior *gravityBehavior;
  7. @property (strong, nonatomic) UIAttachmentBehavior *attachmentBehavior;
  8. @end
  9. @implementation TRAttachmentViewController
  10. - (UIDynamicAnimator *)animator
  11. {
  12. if(!_animator)_animator = [[UIDynamicAnimator alloc]initWithReferenceView:self.view];
  13. return _animator;
  14. }
  15. - (UIGravityBehavior *)gravityBehavior
  16. {
  17. if (!_gravityBehavior) {
  18. _gravityBehavior = [[UIGravityBehavior alloc]initWithItems:@[self.boxImageView]];
  19. }
  20. return _gravityBehavior;
  21. }
  22. - (UIAttachmentBehavior *)attachmentBehavior
  23. {
  24. CGPoint attachedToAnchor = CGPointMake(self.boxImageView.center.x, self.boxImageView.frame.origin.y - 50);
  25. if (!_attachmentBehavior) {
  26. _attachmentBehavior = [[UIAttachmentBehavior alloc]initWithItem:self.boxImageView attachedToAnchor:attachedToAnchor];
  27. }
  28. return _attachmentBehavior;
  29. }
  30. - (IBAction)pan:(UIPanGestureRecognizer *)sender
  31. {
  32. if (sender.state == UIGestureRecognizerStateBegan) {
  33. [self.animator addBehavior:self.gravityBehavior];
  34. self.attachmentBehavior.anchorPoint = [sender locationInView:self.view];
  35. //阻尼
  36. self.attachmentBehavior.damping = 0.1;
  37. //频率
  38. self.attachmentBehavior.frequency = 1.0;
  39. [self.animator addBehavior:self.attachmentBehavior];
  40. [self drawLine];
  41. [self.view setNeedsDisplay];
  42. }
  43. if(sender.state == UIGestureRecognizerStateChanged){
  44. CGPoint location = [sender locationInView:self.view];
  45. self.attachmentBehavior.anchorPoint = location;
  46. [self drawLine];
  47. [self.view setNeedsDisplay];
  48. }else if(sender.state == UIGestureRecognizerStateEnded){
  49. [self.animator removeBehavior:self.attachmentBehavior];
  50. [self.animator removeBehavior:self.gravityBehavior];
  51. ((TRBackgroundView *)self.view).path = nil;
  52. [self.view setNeedsDisplay];
  53. }
  54. }
  55. - (void)drawLine
  56. {
  57. __weak UIAttachmentBehavior *weakAttachmentBehavior = self.attachmentBehavior;
  58. __weak UIImageView *weakBoxImageView = self.boxImageView;
  59. __weak TRBackgroundView *weakMyView = (TRBackgroundView *)self.view;
  60. self.attachmentBehavior.action = ^{
  61. UIBezierPath *path = [UIBezierPath bezierPath];
  62. [path moveToPoint:weakAttachmentBehavior.anchorPoint];
  63. [path addLineToPoint:weakBoxImageView.center];
  64. path.lineWidth = 2;
  65. weakMyView.path = path;
  66. [weakMyView setNeedsDisplay];
  67. };
  68. }
  69. @end
 

4 给视图添加闪的效果

4.1 问题

本案例使用UISnapBehavior给视图添加闪的效果,点击屏幕某处imageView就闪向某处,如图-6所示:

图-6

4.2 方案

首先在创建好的Xcode项目的Storyboard中拖放一个ImageView控件,在右边栏的检查器中设置好ImageView的显示图片。将ImageView控件关联成TRSnapViewController的属性boxImageView。

从对象库中拖放一个TapGestureRecognizer类型的手势到Storyboard的场景中,并将手势关联成TRSnapViewController的动作方法tap:。

其次在TRSnapViewController中定义两个私有属性UIDynamicAnimator类型的animator和UISnapBehavior类型的snapBehavior。重写setter方法对animator属性进行初始化。

最后实现tap:方法,该方法中创建snapBehavior行为,使boxImageView闪向手指触摸的地方。

4.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:搭建Storyboard界面

首先在创建好的Xcode项目的Storyboard中拖放一个ImageView控件,在右边栏的检查器中设置好ImageView的显示图片。将ImageView控件关联成TRSnapViewController的属性boxImageView,代码如下所示:

  1. @interface TRSnapViewController ()
  2. @property (weak, nonatomic) IBOutlet UIImageView *boxImageView;
  3. @end

然后从对象库中拖放一个TapGestureRecognizer类型的手势到Storyboard的场景中,并将手势关联成TRSnapViewController的动作方法tap:。

步骤二:定义属性animator和snapBehavior

首先在TRSnapViewController中定义两个私有属性UIDynamicAnimator类型的animator和UISnapBehavior类型的snapBehavior。

然后重写setter方法对animator属性进行初始化,代码如下所示:

 
  1. @interface TRSnapViewController ()
  2. @property (weak, nonatomic) IBOutlet UIImageView *boxImageView;
  3. @property (strong, nonatomic) UIDynamicAnimator *animator;
  4. @property (strong, nonatomic) UISnapBehavior *snapBehavior;
  5. @end

最后实现tap:方法,该方法中创建snapBehavior行为,使boxImageView闪向手指触摸的地方,代码如下所示:

  1. - (IBAction)tap:(UITapGestureRecognizer *)sender
  2. {
  3. CGPoint point = [sender locationInView:self.view];
  4. [self.animator removeBehavior:self.snapBehavior];
  5. self.snapBehavior = [[UISnapBehavior alloc]initWithItem:self.boxImageView snapToPoint:point];
  6. [self.animator addBehavior:self.snapBehavior];
  7. }

4.4 完整代码

本案例中,TRSnapViewController.m文件中的完整代码如下所示:

 
  1. #import "TRSnapViewController.h"
  2. @interface TRSnapViewController ()
  3. @property (weak, nonatomic) IBOutlet UIImageView *boxImageView;
  4. @property (strong, nonatomic) UIDynamicAnimator *animator;
  5. @property (strong, nonatomic) UISnapBehavior *snapBehavior;
  6. @end
  7. @implementation TRSnapViewController
  8. - (UIDynamicAnimator *)animator
  9. {
  10. if (!_animator) {
  11. _animator = [[UIDynamicAnimator alloc]initWithReferenceView:self.view];
  12. }
  13. return _animator;
  14. }
  15. - (IBAction)tap:(UITapGestureRecognizer *)sender
  16. {
  17. CGPoint point = [sender locationInView:self.view];
  18. [self.animator removeBehavior:self.snapBehavior];
  19. self.snapBehavior = [[UISnapBehavior alloc]initWithItem:self.boxImageView snapToPoint:point];
  20. [self.animator addBehavior:self.snapBehavior];
  21. }
  22. @end
 

5 给视图添加推(拍)效果和UIDynamicItemBehavior

5.1 问题

本案例使用UIPushBehavior和UIDynamicItemBehavior给视图添加pushBehavior行为和dynamicItemBehavior行为,使视图可以在某个动作结束后,继续沿轨迹运动,如图-7所示:

图-7

5.2 方案

首先在创建好的Xcode项目的Storyboard中拖放一个ImageView控件,在右边栏的检查器中设置好ImageView的显示图片。将ImageView控件关联成TRPushViewController的属性boxImageView。

从对象库中拖放一个PanGestureRecognizer类型的手势到Storyboard的场景中,并将手势关联成TRPushViewController的动作方法panAction:。

其次在TRPushViewController中定义两个私有属性UIDynamicAnimator类型的animator和UIPushBehavior类型pushBehavior。重写setter方法对animator属性进行初始化。

然后创建碰撞行为和push行为,由于碰撞行为需要计算视图边距,需要获取self.topLayoutGuide.length的值,所以将碰撞行为和push行为的代码写在viewDidAppear:方法中。

最后实现tap:方法,该方法中boxImageView随着手指滑动实现碰撞和推动,并具有摩擦和反弹效果。

5.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:搭建Storyboard界面

首先在创建好的Xcode项目的Storyboard中拖放一个ImageView控件,在右边栏的检查器中设置好ImageView的显示图片。将ImageView控件关联成TRPushViewController的属性boxImageView,代码如下所示:

 
  1. @interface TRPushViewController ()
  2. @property (weak, nonatomic) IBOutlet UIImageView *boxImageView;
  3. @end

从对象库中拖放一个PanGestureRecognizer类型的手势到Storyboard的场景中,并将手势关联成TRPushViewController的动作方法panAction:。

步骤二:定义属性animator和pushBehavior

在TRPushViewController中定义两个私有属性UIDynamicAnimator类型的animator和UIPushBehavior类型pushBehavior,代码如下所示:

 
  1. @interface TRPushViewController ()
  2. @property (weak, nonatomic) IBOutlet UIImageView *boxImageView;
  3. @property (strong, nonatomic) UIDynamicAnimator *animator;
  4. @property (strong, nonatomic)UIPushBehavior *pushBehavior;
  5. @end

然后重写setter方法对animator属性进行初始化,代码如下所示:

 
  1. - (UIDynamicAnimator *)animator
  2. {
  3. if(!_animator)_animator = [[UIDynamicAnimator alloc]initWithReferenceView:self.view];
  4. return _animator;
  5. }

属性三:创建动力行为,实现手势的动作方法

首先创建碰撞行为和push行为,由于碰撞行为需要计算视图边距,需要获取self.topLayoutGuide.length的值,所以将碰撞行为和push行为的代码写在viewDidAppear:方法中,代码如下所示:

 
  1. - (void)viewDidAppear:(BOOL)animated
  2. {
  3. [super viewDidAppear:animated];
  4. //创建碰撞行为
  5. UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc]initWithItems:@[self.boxImageView]];
  6. [collisionBehavior setTranslatesReferenceBoundsIntoBoundaryWithInsets:UIEdgeInsetsMake(self.topLayoutGuide.length, 0, self.bottomLayoutGuide.length, 0)];
  7. [self.animator addBehavior:collisionBehavior];
  8. //创建pushBehavior行为,UIPushBehaviorModeContinuous模式表示推力持续施加
  9. self.pushBehavior = [[UIPushBehavior alloc]initWithItems:@[self.boxImageView] mode:UIPushBehaviorModeContinuous];
  10. [self.animator addBehavior:self.pushBehavior];
  11. }

然后实现tap:方法,该方法中boxImageView随着手指滑动实现碰撞和推动,代码如下所示:

 
  1. - (IBAction)panAction:(UIPanGestureRecognizer *)sender {
  2. CGPoint point = [sender locationInView:self.view];
  3. CGPoint center = self.boxImageView.center;
  4. //计算移动的方向
  5. CGFloat angle = atan2(center.y - point.y, center.x - point.x) + M_PI;
  6. self.pushBehavior.angle = angle;
  7. //计算移动的速度
  8. CGFloat distance = sqrt(powf(point.x - center.x, 2) + powf(point.y - center.y, 2));
  9. self.pushBehavior.magnitude = distance / 10;
  10. //激活
  11. self.pushBehavior.active = YES;
  12. }

最后添加dynamicItemBehavior行为,是视图在移动过程中产生摩擦和反弹,代码如下所示:

 
  1. - (IBAction)panAction:(UIPanGestureRecognizer *)sender {
  2. CGPoint point = [sender locationInView:self.view];
  3. CGPoint center = self.boxImageView.center;
  4. //计算移动的方向
  5. CGFloat angle = atan2(center.y - point.y, center.x - point.x) + M_PI;
  6. self.pushBehavior.angle = angle;
  7. //计算移动的速度
  8. CGFloat distance = sqrt(powf(point.x - center.x, 2) + powf(point.y - center.y, 2));
  9. self.pushBehavior.magnitude = distance / 10;
  10. //激活
  11. self.pushBehavior.active = YES;
  12. //添加UIDynamicItemBehavior
  13. UIDynamicItemBehavior *itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[self.boxImageView]];
  14. //设置摩擦力大小
  15. itemBehavior.friction = 0.2;
  16. //允许旋转
  17. itemBehavior.allowsRotation = YES;
  18. //设置旋转速度
  19. [itemBehavior addAngularVelocity:M_PI_4 forItem:self.boxImageView];
  20. [self.animator addBehavior:itemBehavior];
  21. }

5.4 完整代码

本案例中,TRPushViewController.m文件中的完整代码如下所示:

 
  1. #import "TRPushViewController.h"
  2. @interface TRPushViewController ()
  3. @property (weak, nonatomic) IBOutlet UIImageView *boxImageView;
  4. @property (strong, nonatomic) UIDynamicAnimator *animator;
  5. @property (strong, nonatomic)UIPushBehavior *pushBehavior;
  6. @end
  7. @implementation TRPushViewController
  8. - (UIDynamicAnimator *)animator
  9. {
  10. if(!_animator)_animator = [[UIDynamicAnimator alloc]initWithReferenceView:self.view];
  11. return _animator;
  12. }
  13. - (void)viewDidAppear:(BOOL)animated
  14. {
  15. [super viewDidAppear:animated];
  16. //创建碰撞行为
  17. UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc]initWithItems:@[self.boxImageView]];
  18. [collisionBehavior setTranslatesReferenceBoundsIntoBoundaryWithInsets:UIEdgeInsetsMake(self.topLayoutGuide.length, 0, self.bottomLayoutGuide.length, 0)];
  19. [self.animator addBehavior:collisionBehavior];
  20. //创建pushBehavior行为,UIPushBehaviorModeContinuous模式表示推力持续施加
  21. self.pushBehavior = [[UIPushBehavior alloc]initWithItems:@[self.boxImageView] mode:UIPushBehaviorModeContinuous];
  22. [self.animator addBehavior:self.pushBehavior];
  23. }
  24. - (IBAction)panAction:(UIPanGestureRecognizer *)sender {
  25. CGPoint point = [sender locationInView:self.view];
  26. CGPoint center = self.boxImageView.center;
  27. //计算移动的方向
  28. CGFloat angle = atan2(center.y - point.y, center.x - point.x) + M_PI;
  29. self.pushBehavior.angle = angle;
  30. //计算移动的速度
  31. CGFloat distance = sqrt(powf(point.x - center.x, 2) + powf(point.y - center.y, 2));
  32. self.pushBehavior.magnitude = distance / 10;
  33. //激活
  34. self.pushBehavior.active = YES;
  35. //添加UIDynamicItemBehavior
  36. UIDynamicItemBehavior *itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[self.boxImageView]];
  37. //设置摩擦力大小
  38. itemBehavior.friction = 0.2;
  39. //允许旋转
  40. itemBehavior.allowsRotation = YES;
  41. //设置旋转速度
  42. [itemBehavior addAngularVelocity:M_PI_4 forItem:self.boxImageView];
  43. [self.animator addBehavior:itemBehavior];
  44. }
  45. @end
 

6 使用集合视图的布局实现Cell滚动时候的动画效果

6.1 问题

集合视图的布局是其精髓,相当于集合视图的大脑和中枢,负责设置集合视图的一些属性,包括位置、尺寸、透明度、层级关系、形状等。本案例将使用集合视图的布局实现单元格滚动时的动画效果,如图-8所示:

图-8

6.2 方案

首先创建一个SingleViewApplication项目,在StoryBoard中拖放一个CollectionViewController作为根视图控制器,并于TRViewController进行绑定,TRViewController继承至UICollectionViewController。

其次创建一个TRCell类,继承至UICollectionViewCell,TRCell有一个公开属性UILabel类型的label。然后重写initWithFrame:方法,通过frame参数计算出每个cell中子视图label的frame,并且设置cell的contentView的layer属性。

然后创建一个TRCollectionViewCustomLayout继承至UICollectionViewFlowLayout。重写初始化方法initWithCoder:对布局类进行一些初始化的设置,例如滚动方向、间距、item的大小等。

UICollectionViewLayoutAttributes是一个非常重要的类包含了cell布局信息,包括边框、中心点、大小、形状、透明度、层次关系以及是否隐藏等信息。本案例在TRCollectionViewCustomLayout类中重写layoutAttributesForElementsInRect:方法,获取每一个Cell的布局属性,通过attributes.transform3D实现Cell的缩放动画。

最后在TRViewController的viewDidLoad方法中注册集合视图的cell,TRViewController遵守集合视图协议,并且实现协议方法给集合视图加载数据。

6.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:创建集合视图项目

首先创建一个SingleViewApplication项目,在StoryBoard中拖放一个CollectionViewController作为根视图控制器,并于TRViewController进行绑定,TRViewController继承至UICollectionViewController,Storyboard中的界面如图-9所示:

图-9

步骤二:创建TRCell类

首先创建一个TRCell类,继承至UICollectionViewCell,TRCell有一个公开属性UILabel类型的label,代码如下所示:

 
  1. @interface TRCell : UICollectionViewCell
  2. @property (nonatomic, strong) UILabel *label;
  3. @end

然后重写initWithFrame:方法,通过frame参数计算出每个cell中子视图label的frame,并且设置cell的contentView的layer属性,代码如下所示:

 
  1. - (id)initWithFrame:(CGRect)frame
  2. {
  3. self = [super initWithFrame:frame];
  4. if (self) {
  5. //创建并设置label属性
  6. self.label = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
  7. self.label.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  8. self.label.textAlignment = NSTextAlignmentCenter;
  9. self.label.backgroundColor = [UIColor lightGrayColor];
  10. self.label.textColor = [UIColor blackColor];
  11. [self.contentView addSubview:self.label];
  12. //设置contentView的layer属性,添加圆角和边框
  13. self.contentView.layer.borderWidth = 1.0f;
  14. self.contentView.layer.cornerRadius = 8.0;
  15. self.contentView.layer.masksToBounds = YES;
  16. self.contentView.layer.borderColor = [UIColor whiteColor].CGColor;
  17. }
  18. return self;
  19. }

步骤三:创建布局类TRCollectionViewCustomLayout

首先创建一个TRCollectionViewCustomLayout继承至UICollectionViewFlowLayout。重写初始化方法initWithCoder:对布局类进行一些初始化的设置,例如滚动方向、间距、item的大小等,代码如下所示:

 
  1. - (id)initWithCoder:(NSCoder *)aDecoder
  2. {
  3. self = [super initWithCoder:aDecoder];
  4. if (self) {
  5. [self initiate];
  6. }
  7. return self;
  8. }
  9. - (void)initiate
  10. {
  11. //设置Cell的大小
  12. self.itemSize = CGSizeMake(100, 100);
  13. //设置CollectionView的滚动方向
  14. self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
  15. //设置CollectionView的内边距
  16. self.sectionInset = UIEdgeInsetsMake(100, 0, 100, 0);
  17. //设置Cell之间的间距
  18. self.minimumLineSpacing = 50;
  19. }

然后在TRCollectionViewCustomLayout类中重写layoutAttributesForElementsInRect:方法,获取每一个Cell的布局属性,通过attributes.transform3D实现Cell的缩放动画,代码如下所示:

 
  1. //当边界改变时重新计算布局
  2. - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
  3. {
  4. return YES;
  5. }
  6. //返回所有Cell的布局属性
  7. - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
  8. {
  9. //获取每一个Cell的布局属性
  10. NSArray *array = [super layoutAttributesForElementsInRect:rect];
  11. CGRect visibleRect;
  12. visibleRect.origin = self.collectionView.contentOffset;
  13. visibleRect.size = self.collectionView.bounds.size;
  14. for (UICollectionViewLayoutAttributes *attributes in array) {
  15. CGFloat distance = CGRectGetMidX(visibleRect) - attributes.center.x;
  16. CGFloat distance2 = distance / 100;
  17. if(ABS(distance) < 100){
  18. CGFloat zoom = 1 + 0.3* (1 - ABS(distance2));
  19. attributes.transform3D = CATransform3DMakeScale(zoom, zoom, 1);
  20. }
  21. }
  22. return array;
  23. }

步骤四:遵守委托协议,实现协议方法

首先在TRViewController的viewDidLoad方法中注册集合视图的cell,代码如下所示:

 
  1. static NSString *cellIdentifier = @"MyCell";
  2. - (void)viewDidLoad
  3. {
  4. [super viewDidLoad];
  5.     [self.collectionView registerClass:[TRCell class] forCellWithReuseIdentifier:cellIdentifier];
  6. }

然后TRViewController遵守集合视图协议,并且实现协议方法给集合视图加载数据,代码如下所示:

 
  1. -(NSInteger)collectionView:(UICollectionView *)collectionView
  2. numberOfItemsInSection:(NSInteger)section
  3. {
  4. return 60;
  5. }
  6. -(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
  7. cellForItemAtIndexPath:(NSIndexPath *)indexPath
  8. {
  9. TRCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
  10. cell.label.text = [NSString stringWithFormat:@"%d", indexPath.row];
  11. return cell;
  12. }

6.4 完整代码

本案例中,TRViewController.m文件中的完整代码如下所示:

 
  1. #import "TRViewController.h"
  2. #import "TRCell.h"
  3. @implementation TRViewController
  4. static NSString *cellIdentifier = @"MyCell";
  5. - (void)viewDidLoad
  6. {
  7. [super viewDidLoad];
  8.     [self.collectionView registerClass:[TRCell class] forCellWithReuseIdentifier:cellIdentifier];
  9. }
  10. - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
  11. {
  12. return 60;
  13. }
  14. - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
  15. {
  16. TRCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
  17. cell.label.text = [NSString stringWithFormat:@"%d", indexPath.row];
  18. return cell;
  19. }
  20. @end
 

本案例中,TRCell.h文件中的完整代码如下所示:

 
  1. #import<UIKit/UIKit.h>
  2. @interface TRCell : UICollectionViewCell
  3. @property (nonatomic, strong) UILabel *label;
  4. @end
 

本案例中,TRCell.m文件中的完整代码如下所示:

 
  1. #import "TRCell.h"
  2. @implementation TRCell
  3. - (id)initWithFrame:(CGRect)frame
  4. {
  5. self = [super initWithFrame:frame];
  6. if (self) {
  7. self.label = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
  8. self.label.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  9. self.label.textAlignment = NSTextAlignmentCenter;
  10. self.label.backgroundColor = [UIColor lightGrayColor];
  11. self.label.textColor = [UIColor blackColor];
  12. [self.contentView addSubview:self.label];
  13. self.contentView.layer.borderWidth = 1.0f;
  14. self.contentView.layer.cornerRadius = 8.0;
  15. self.contentView.layer.masksToBounds = YES;
  16. self.contentView.layer.borderColor = [UIColor whiteColor].CGColor;
  17. }
  18. return self;
  19. }
  20. @end
 

本案例中,TRCollectionViewCustomLayout.m文件中的完整代码如下所示:

 
  1. #import "TRCollectionViewCustomLayout.h"
  2. @implementation TRCollectionViewCustomLayout
  3. - (id)initWithCoder:(NSCoder *)aDecoder
  4. {
  5. self = [super initWithCoder:aDecoder];
  6. if (self) {
  7. [self initiate];
  8. }
  9. return self;
  10. }
  11. - (void)initiate
  12. {
  13. //设置Cell的大小
  14. self.itemSize = CGSizeMake(100, 100);
  15. //设置CollectionView的滚动方向
  16. self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
  17. //设置CollectionView的内边距
  18. self.sectionInset = UIEdgeInsetsMake(100, 0, 100, 0);
  19. //设置Cell之间的间距
  20. self.minimumLineSpacing = 50;
  21. }
  22. //当边界改变时重新计算布局
  23. - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
  24. {
  25. return YES;
  26. }
  27. //返回所有Cell的布局属性
  28. - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
  29. {
  30. //获取每一个Cell的布局属性
  31. NSArray *array = [super layoutAttributesForElementsInRect:rect];
  32. CGRect visibleRect;
  33. visibleRect.origin = self.collectionView.contentOffset;
  34. visibleRect.size = self.collectionView.bounds.size;
  35. for (UICollectionViewLayoutAttributes *attributes in array) {
  36. CGFloat distance = CGRectGetMidX(visibleRect) - attributes.center.x;
  37. CGFloat distance2 = distance / 100;
  38. if(ABS(distance) < 100){
  39. CGFloat zoom = 1 + 0.3* (1 - ABS(distance2));
  40. attributes.transform3D = CATransform3DMakeScale(zoom, zoom, 1);
  41. }
  42. }
  43. return array;
  44. }
  45. @end
 
原文地址:https://www.cnblogs.com/52190112cn/p/5049470.html