iOS开发核心动画之星座幸运转盘

一. 星座转盘

1. 示意图

 

2. 设计思路

    每一个星座条是一个UIButton,设置按钮的宽高,设置position点在整个转盘的中点,再通过anchorPoint(0.5, 1)定位到position点

    每一个按钮上的图片通过截取图片获取


3. 代码

1> 通过一个xib描述转盘底座,关联到一个新创建的View类(LDWheelView.h),在View中加载xib进行初始化

  1. + (instancetype)wheel
  2. {
  3. return [[self alloc] init];
  4. }
  5. - (instancetype)initWithFrame:(CGRect)frame
  6. {
  7. if (self = [super initWithFrame:frame]) {
  8. self = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([LDWheelView class]) owner:nil options:nil] lastObject];
  9. }
  10. return self;
  11. }
  12. - (void)awakeFromNib
  13. {
  14. // 创建按钮
  15. [self setupBtn];
  16. }

2> 初始化过程中创建12星座按钮,并设置按钮tag

  1. - (void)setupBtn
  2. {
  3. // wheelImageV允许和用户互动
  4. self.wheelImageV.userInteractionEnabled = YES;
  5. CGFloat btnW = 68;
  6. CGFloat btnH = 143;
  7. CGFloat angle = 0;
  8. // 0.加载图片
  9. UIImage *imageNor = [UIImage imageNamed:@"LuckyAstrology"];
  10. UIImage *imageSel = [UIImage imageNamed:@"LuckyAstrologyPressed"];
  11. // 加载图片时只能加载圈1X倍的图片,而屏幕显示时圈2X倍的图片,根据屏幕计算出要倍数
  12. CGFloat scale = [UIScreen mainScreen].scale;
  13. CGFloat cutImageW = imageNor.size.width / 12 * scale;
  14. CGFloat cutImageH = imageNor.size.height *scale;
  15. for (int i = 0; i < 12; ++i) {
  16. // 1.创建按钮
  17. LDWheelBtn *btn = [LDWheelBtn buttonWithType:UIButtonTypeCustom];
  18. btn.tag = i;
  19. // 2.设置选中背景色
  20. [btn setBackgroundImage:[UIImage imageNamed:@"LuckyRototeSelected"] forState:UIControlStateSelected];
  21. // 3.设置位置和尺寸
  22. btn.bounds = CGRectMake(0, 0, btnW, btnH);
  23. btn.layer.anchorPoint = CGPointMake(0.5, 1);
  24. btn.layer.position = CGPointMake(self.wheelImageV.bounds.size.width * 0.5, self.wheelImageV.bounds.size.width * 0.5);
  25. // 4.旋转角度
  26. btn.transform = CGAffineTransformMakeRotation(angle / 180.0 * M_PI);
  27. // 每创建一个按钮旋转角度增加30
  28. angle += 30;
  29. // 5.设置图片
  30. CGImageRef imageRefNor = CGImageCreateWithImageInRect(imageNor.CGImage, CGRectMake(i * cutImageW, 0, cutImageW, cutImageH));
  31. CGImageRef imageRefSel = CGImageCreateWithImageInRect(imageSel.CGImage, CGRectMake(i * cutImageW, 0, cutImageW, cutImageH));
  32. [btn setImage:[UIImage imageWithCGImage:imageRefNor] forState:UIControlStateNormal];
  33. [btn setImage:[UIImage imageWithCGImage:imageRefSel] forState:UIControlStateSelected];
  34. // 5.添加到wheelImageV上
  35. [self.wheelImageV addSubview:btn];
  36. // 6.监听按钮
  37. [btn addTarget:self action:@selector(setSelect:) forControlEvents:UIControlEventTouchUpInside];
  38. if (i == 0) {
  39. [self setSelect:btn];
  40. }
  41. }
  42. }

注:扣取一张图片中某个位置为新图片

. 加载图片

  1. UIImage *imageNor = [UIImage imageNamed:@"LuckyAstrology"];

加载图片时只能加载圈1X倍的图片,而屏幕显示时圈2X倍的图片,根据屏幕计算出要倍数

  1. CGFloat scale = [UIScreen mainScreen].scale;

. 根据屏幕的倍数x原始图片的宽度/高度 ➗ 12 算出扣取图片的宽高

  1. CGFloat cutImageW = imageNor.size.width / 12 * scale;
  2. CGFloat cutImageH = imageNor.size.height *scale;

. 扣取图片 CGImageCreateWithImageInRect 方法

  1. CGImageRef imageRefNor = CGImageCreateWithImageInRect(imageNor.CGImage, CGRectMake(i * cutImageW, 0, cutImageW, cutImageH));

3> 设置选中按钮, 在初始化过程中创建按钮并监听按钮,现在实现监听方法

定义按钮属性记录上一个按钮

  1. /** 记录上一个按钮 */
  2. @property (nonatomic, weak) LDWheelBtn *preBtn;

实现监听方法:

. 将上一个按钮selected设置为NO

. 将当前按钮selected设置为YES

. 将当前按钮赋值给上一个按钮

  1. - (void)setSelect:(LDWheelBtn *)btn
  2. {
  3. // 1.设置上一个按钮selected为NO
  4. self.preBtn.selected = NO;
  5. // 2.设置当前按钮selected为YES
  6. btn.selected = YES;
  7. // 3.将当前按钮赋值给上一个按钮
  8. self.preBtn = btn;
  9. }

4> 由于点击按钮不需要高亮状态,所以就需要自定义按钮重写设置按钮高亮方法

. 自定义按钮创建一个类(LDWheelBtn.h)继承UIButton

  1. // 取消按钮高亮状态
  2. - (void)setHighlighted:(BOOL)highlighted
  3. {
  4. }

. 重置按钮上图片的位置和尺寸

  1. //返回按钮当中图片的尺寸位置.
  2. //contentRect:当前按钮的尺寸位置 .
  3. - (CGRect)imageRectForContentRect:(CGRect)contentRect{
  4. CGFloat btnW = 40;
  5. CGFloat btnH = 50;
  6. CGFloat btnX = (contentRect.size.width - btnW) * 0.5;
  7. CGFloat btnY = 25;
  8. return CGRectMake(btnX, btnY, btnW, btnH);
  9. }

. 由于按钮是旋转的就有一些重叠,重叠部分会影响点击,因此要在自定义按钮上拦截事件响应

用一种取巧的方法,让按钮重叠部分不能响应事件,即设置一个区域(未重叠区域)才能响应事件

,重写按钮的hitText方法来判定

  1. -(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
  2. CGRect rect = CGRectMake(0, 0, self.bounds.size.width, 50);
  3. if (CGRectContainsPoint(rect, point)) {
  4. return [super hitTest:point withEvent:event];
  5. }else{
  6. return nil;
  7. }
  8. }

5> 在控制器中点击开始按钮转盘开始转动,调用WheelView中提供的开始转动方法

. 开始转动将定时器的paused属性设置为NO,设置为YES为暂停动画

  1. //开始旋转
  2. - (void)start
  3. {
  4. self.link.paused = NO;
  5. }

. 定义定时器属性

  1. /** 定时器.*/
  2. @property (nonatomic ,weak) CADisplayLink *link;

. 懒加载创建定时器(CADisplayLink 这个定时器1s刷新60次)

  1. -(CADisplayLink *)link{
  2. if (_link == nil) {
  3. //添加定时器.
  4. CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];
  5. [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
  6. _link = link;
  7. }
  8. return _link;
  9. }

. 实现定时器监听方法,将转盘旋转(UIView旋转)

  1. - (void)update{
  2. //让转盘开始旋转
  3. self.contentView.transform = CGAffineTransformRotate(self.contentView.transform, M_PI / 250.0);
  4. }

6> 选择一个星座,点击中间的开始选号,当转盘停止旋转时,星座指针指向正上方

. 点击开始选号,创建基本动画,将转盘旋转N圈

  1. //开始选号
  2. - (IBAction)chooseNum:(id)sender {
  3. //创建动画对象
  4. CABasicAnimation *anim = [CABasicAnimation animation];
  5. //设置动画的属性值
  6. anim.keyPath = @"transform.rotation";
  7. anim.toValue = @(M_PI * 4);
  8. anim.delegate = self;
  9. anim.duration = 0.5;
  10. [self.contentView.layer addAnimation:anim forKey:nil];
  11. }

. 实现动画停止后的方法,来将指针回转到正上方

  1. //当动画结束时调用.
  2. -(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
  3. //动画结束时,选择按钮指向最上方.
  4. //动画结束时,让选中的按钮倒着旋转回去.
  5. CGAffineTransform transform = self.preBtn.transform;
  6. CGFloat angle = atan2(transform.b, transform.a);
  7. self.contentView.transform = CGAffineTransformMakeRotation(-angle);
  8. }


原文地址:https://www.cnblogs.com/Xfsrn/p/5000359.html