zoukankan      html  css  js  c++  java
  • 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
     
  • 相关阅读:
    02-计算字符个数-2
    01-计算字符个数-1
    30-Ubuntu-用户权限-01-用户和权限的基本概念
    2-数据分析-matplotlib-1-概述
    1-数据分析简述
    05_二进制、八进制、十进制与十六进制转换
    29-Ubuntu-远程管理命令-03-SSH工作方式简介
    28-Ubuntu-远程管理命令-02-查看网卡的配置信息
    27-Ubuntu-远程管理命令-01-关机和重启
    2-JDK环境变量配置和验证
  • 原文地址:https://www.cnblogs.com/52190112cn/p/5049470.html
Copyright © 2011-2022 走看看