iOS开发核心动画之星座幸运转盘
一. 星座转盘
1. 示意图
2. 设计思路
每一个星座条是一个UIButton,设置按钮的宽高,设置position点在整个转盘的中点,再通过anchorPoint(0.5, 1)定位到position点
每一个按钮上的图片通过截取图片获取
3. 代码
1> 通过一个xib描述转盘底座,关联到一个新创建的View类(LDWheelView.h),在View中加载xib进行初始化
+ (instancetype)wheel
{
return [[self alloc] init];
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
self = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([LDWheelView class]) owner:nil options:nil] lastObject];
}
return self;
}
- (void)awakeFromNib
{
// 创建按钮
[self setupBtn];
}
2> 初始化过程中创建12星座按钮,并设置按钮tag
- (void)setupBtn
{
// wheelImageV允许和用户互动
self.wheelImageV.userInteractionEnabled = YES;
CGFloat btnW = 68;
CGFloat btnH = 143;
CGFloat angle = 0;
// 0.加载图片
UIImage *imageNor = [UIImage imageNamed:@"LuckyAstrology"];
UIImage *imageSel = [UIImage imageNamed:@"LuckyAstrologyPressed"];
// 加载图片时只能加载圈1X倍的图片,而屏幕显示时圈2X倍的图片,根据屏幕计算出要倍数
CGFloat scale = [UIScreen mainScreen].scale;
CGFloat cutImageW = imageNor.size.width / 12 * scale;
CGFloat cutImageH = imageNor.size.height *scale;
for (int i = 0; i < 12; ++i) {
// 1.创建按钮
LDWheelBtn *btn = [LDWheelBtn buttonWithType:UIButtonTypeCustom];
btn.tag = i;
// 2.设置选中背景色
[btn setBackgroundImage:[UIImage imageNamed:@"LuckyRototeSelected"] forState:UIControlStateSelected];
// 3.设置位置和尺寸
btn.bounds = CGRectMake(0, 0, btnW, btnH);
btn.layer.anchorPoint = CGPointMake(0.5, 1);
btn.layer.position = CGPointMake(self.wheelImageV.bounds.size.width * 0.5, self.wheelImageV.bounds.size.width * 0.5);
// 4.旋转角度
btn.transform = CGAffineTransformMakeRotation(angle / 180.0 * M_PI);
// 每创建一个按钮旋转角度增加30
angle += 30;
// 5.设置图片
CGImageRef imageRefNor = CGImageCreateWithImageInRect(imageNor.CGImage, CGRectMake(i * cutImageW, 0, cutImageW, cutImageH));
CGImageRef imageRefSel = CGImageCreateWithImageInRect(imageSel.CGImage, CGRectMake(i * cutImageW, 0, cutImageW, cutImageH));
[btn setImage:[UIImage imageWithCGImage:imageRefNor] forState:UIControlStateNormal];
[btn setImage:[UIImage imageWithCGImage:imageRefSel] forState:UIControlStateSelected];
// 5.添加到wheelImageV上
[self.wheelImageV addSubview:btn];
// 6.监听按钮
[btn addTarget:self action:@selector(setSelect:) forControlEvents:UIControlEventTouchUpInside];
if (i == 0) {
[self setSelect:btn];
}
}
}
注:扣取一张图片中某个位置为新图片
. 加载图片
UIImage *imageNor = [UIImage imageNamed:@"LuckyAstrology"];
. 加载图片时只能加载圈1X倍的图片,而屏幕显示时圈2X倍的图片,根据屏幕计算出要倍数
CGFloat scale = [UIScreen mainScreen].scale;
. 根据屏幕的倍数x原始图片的宽度/高度 ➗ 12 算出扣取图片的宽高
CGFloat cutImageW = imageNor.size.width / 12 * scale;
CGFloat cutImageH = imageNor.size.height *scale;
. 扣取图片 CGImageCreateWithImageInRect 方法
CGImageRef imageRefNor = CGImageCreateWithImageInRect(imageNor.CGImage, CGRectMake(i * cutImageW, 0, cutImageW, cutImageH));
3> 设置选中按钮, 在初始化过程中创建按钮并监听按钮,现在实现监听方法
定义按钮属性记录上一个按钮
/** 记录上一个按钮 */
@property (nonatomic, weak) LDWheelBtn *preBtn;
实现监听方法:
. 将上一个按钮selected设置为NO
. 将当前按钮selected设置为YES
. 将当前按钮赋值给上一个按钮
- (void)setSelect:(LDWheelBtn *)btn
{
// 1.设置上一个按钮selected为NO
self.preBtn.selected = NO;
// 2.设置当前按钮selected为YES
btn.selected = YES;
// 3.将当前按钮赋值给上一个按钮
self.preBtn = btn;
}
4> 由于点击按钮不需要高亮状态,所以就需要自定义按钮重写设置按钮高亮方法
. 自定义按钮创建一个类(LDWheelBtn.h)继承UIButton
// 取消按钮高亮状态
- (void)setHighlighted:(BOOL)highlighted
{
}
. 重置按钮上图片的位置和尺寸
//返回按钮当中图片的尺寸位置.
//contentRect:当前按钮的尺寸位置 .
- (CGRect)imageRectForContentRect:(CGRect)contentRect{
CGFloat btnW = 40;
CGFloat btnH = 50;
CGFloat btnX = (contentRect.size.width - btnW) * 0.5;
CGFloat btnY = 25;
return CGRectMake(btnX, btnY, btnW, btnH);
}
. 由于按钮是旋转的就有一些重叠,重叠部分会影响点击,因此要在自定义按钮上拦截事件响应
用一种取巧的方法,让按钮重叠部分不能响应事件,即设置一个区域(未重叠区域)才能响应事件
,重写按钮的hitText方法来判定
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
CGRect rect = CGRectMake(0, 0, self.bounds.size.width, 50);
if (CGRectContainsPoint(rect, point)) {
return [super hitTest:point withEvent:event];
}else{
return nil;
}
}
5> 在控制器中点击开始按钮转盘开始转动,调用WheelView中提供的开始转动方法
. 开始转动将定时器的paused属性设置为NO,设置为YES为暂停动画
//开始旋转
- (void)start
{
self.link.paused = NO;
}
. 定义定时器属性
/** 定时器.*/
@property (nonatomic ,weak) CADisplayLink *link;
. 懒加载创建定时器(CADisplayLink 这个定时器1s刷新60次)
-(CADisplayLink *)link{
if (_link == nil) {
//添加定时器.
CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];
[link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
_link = link;
}
return _link;
}
. 实现定时器监听方法,将转盘旋转(UIView旋转)
- (void)update{
//让转盘开始旋转
self.contentView.transform = CGAffineTransformRotate(self.contentView.transform, M_PI / 250.0);
}
6> 选择一个星座,点击中间的开始选号,当转盘停止旋转时,星座指针指向正上方
. 点击开始选号,创建基本动画,将转盘旋转N圈
//开始选号
- (IBAction)chooseNum:(id)sender {
//创建动画对象
CABasicAnimation *anim = [CABasicAnimation animation];
//设置动画的属性值
anim.keyPath = @"transform.rotation";
anim.toValue = @(M_PI * 4);
anim.delegate = self;
anim.duration = 0.5;
[self.contentView.layer addAnimation:anim forKey:nil];
}
. 实现动画停止后的方法,来将指针回转到正上方
//当动画结束时调用.
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
//动画结束时,选择按钮指向最上方.
//动画结束时,让选中的按钮倒着旋转回去.
CGAffineTransform transform = self.preBtn.transform;
CGFloat angle = atan2(transform.b, transform.a);
self.contentView.transform = CGAffineTransformMakeRotation(-angle);
}