16-核心动画

画板(图片处理)

  • 为了能对照片提供更多的手势操作,需要把从相册中选取的照片放置到UIImageView中,然后对控件添加手势操作即可完成对照片的对应操作
  • 创建一个专门用来处理图片的View(ImageHandleView)

隐式动画

  • 只有非根层view的layer才有隐式动画,根层view的layer没有隐式动画

时钟动画

  • 首先定义宏,每秒钟秒针、每分钟分针、每小时时针和每分钟时针旋转的度数
// 一秒钟秒针转6°
#define perSecondA 6

// 一分钟分针转6°
#define perMinuteA 6

// 一小时时针转30°
#define perHourA 30

// 每分钟时针转多少度
#define perMinuteHourA 0.5
  • 时钟界面的搭建
@interface ViewController ()

@property (weak, nonatomic) IBOutlet UIImageView *clockView;

@property (nonatomic, weak) CALayer *secondLayer;

@property (nonatomic, weak) CALayer *minuteLayer;

@property (nonatomic, weak) CALayer *hourLayer;

@end
  • 在viewDidLoad中对界面进行初始化
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    // 添加时针,因为先添加的在下面
    [self setUpHourLayer];
    
    // 添加分针
    [self setUpMinuteLayer];
    
    // 添加秒针
    [self setUpSecondLayer];
    
    // 添加定时器
    [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeChange) userInfo:nil repeats:YES];
    
    [self timeChange];
    
}
  • 以此初始化各个指针
#pragma mark - 添加秒针
- (void)setUpSecondLayer
{
   CALayer *secondL = [CALayer layer];
    
    secondL.backgroundColor = [UIColor redColor].CGColor;
    
    // 设置锚点
    secondL.anchorPoint = CGPointMake(0.5, 1);
    
    secondL.position = CGPointMake(kClockW * 0.5, kClockW * 0.5);
    
    secondL.bounds = CGRectMake(0, 0, 1, kClockW * 0.5 - 20);
    
    [_clockView.layer addSublayer:secondL];
    
    _secondLayer = secondL;
}

#pragma mark - 添加分针
- (void)setUpMinuteLayer
{
    CALayer *layer = [CALayer layer];
    
    layer.backgroundColor = [UIColor blackColor].CGColor;
    
    // 设置锚点
    layer.anchorPoint = CGPointMake(0.5, 1);
    
    layer.position = CGPointMake(kClockW * 0.5, kClockW * 0.5);
    
    layer.bounds = CGRectMake(0, 0, 4, kClockW * 0.5 - 20);
    
    layer.cornerRadius = 4;
    
    [_clockView.layer addSublayer:layer];
    
    _minuteLayer = layer;
}

#pragma mark - 添加时针
- (void)setUpHourLayer
{
    CALayer *layer = [CALayer layer];
    
    layer.backgroundColor = [UIColor blackColor].CGColor;
    
    // 设置锚点
    layer.anchorPoint = CGPointMake(0.5, 1);
    
    layer.position = CGPointMake(kClockW * 0.5, kClockW * 0.5);
    
    layer.bounds = CGRectMake(0, 0, 4, kClockW * 0.5 - 40);
    
    layer.cornerRadius = 4;
    
    [_clockView.layer addSublayer:layer];
    
    _hourLayer = layer;
}
  • 定时器调用的方法
- (void)timeChange
{
    // 获取当前的系统的时间
    
    // 获取当前日历对象
    NSCalendar *calendar = [NSCalendar currentCalendar];
    
    // 获取日期的组件:年月日小时分秒
    // components:需要获取的日期组件
    // fromDate:获取哪个日期的组件
    // 经验:以后枚举中有移位运算符,通常一般可以使用并运算(|)
    NSDateComponents  *cmp = [calendar components:NSCalendarUnitSecond | NSCalendarUnitMinute | NSCalendarUnitHour fromDate:[NSDate date]];
    
    // 获取秒
    NSInteger second = cmp.second;
    
    // 获取分
    NSInteger minute = cmp.minute;
    
    // 获取小时
    NSInteger hour = cmp.hour;
    
    // 计算秒针转多少度
    CGFloat secondA = second * perSecondA;
    
    // 计算分针转多少度
    CGFloat minuteA = minute * perMinuteA;
    
    // 计算时针转多少度
    CGFloat hourA = hour * perHourA + minute * perMinuteHourA;
    
    // 旋转秒针
    _secondLayer.transform = CATransform3DMakeRotation(angle2radion(secondA), 0, 0, 1);
    
    // 旋转分针
    _minuteLayer.transform = CATransform3DMakeRotation(angle2radion(minuteA), 0, 0, 1);
    
    // 旋转小时
    _hourLayer.transform = CATransform3DMakeRotation(angle2radion(hourA), 0, 0, 1);
}

核心动画(CABasicAnimation)

  • 创建动画-->描述修改哪个属性产生动画-->设置值-->设置动画执行的次数-->取消动画反弹-->将动画添加到layer上
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    // 创建动画
    CABasicAnimation *anim = [CABasicAnimation animation];
    
    // 描述下修改哪个属性产生动画
    // anim.keyPath = @"position";
    // 只能是layer属性
    anim.keyPath = @"transform.scale";
    
    // 设置值
    // anim.toValue = [NSValue valueWithCGPoint:CGPointMake(250, 500)];
    
    anim.toValue = @0.5;
    
    // 设置动画执行次数
    anim.repeatCount = MAXFLOAT;
    
    // 取消动画反弹
    // 设置动画完成的时候不要移除动画
    anim.removedOnCompletion = NO;
    
    // 设置动画执行完成要保持最新的效果
    anim.fillMode = kCAFillModeForwards;
    
    [_imageV.layer addAnimation:anim forKey:nil];
    
}

核心动画(CAKeyFrameAnimation)

  • 开始触摸时,创建UIBezierPath并设置起点
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    // touch
    UITouch *touch = [touches anyObject];
    
    // 获取手指的触摸点
    CGPoint curP = [touch locationInView:self];
    
    // 创建路径
    UIBezierPath *path = [UIBezierPath bezierPath];
    _path = path;
    
    // 设置起点
    [path moveToPoint:curP];
    
}
  • 触摸移动时,获取触摸点并添加路径
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    // touch
    UITouch *touch = [touches anyObject];
    
    // 获取手指的触摸点
    CGPoint curP = [touch locationInView:self];
    
    [_path addLineToPoint:curP];
    
    [self setNeedsDisplay];
}
  • 结束触摸时,给imageView添加核心动画
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    // 给imageView添加核心动画
    // 添加核心动画
    
    CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
    
    anim.keyPath = @"position";
    
    // anim.values = @[@(angle2Radion(-10)),@(angle2Radion(10)),@(angle2Radion(-10))];
    
    anim.path = _path.CGPath;
    
    anim.duration = 1;
    
    anim.repeatCount = MAXFLOAT;
    
    [[[self.subviews firstObject] layer] addAnimation:anim forKey:nil];
}

核心动画(CATransition)

  • CATransition即转场动画
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    
    // 转场代码
    if (i == 4) {
        i = 1;
    }
    // 加载图片名称
    NSString *imageN = [NSString stringWithFormat:@"%d",i];
    
    _imageView.image = [UIImage imageNamed:imageN];
    
    i++;
    
    // 转场动画
    CATransition *anim = [CATransition animation];
    
    anim.type = @"pageCurl";
    
    anim.duration = 2;
    
    [_imageView.layer addAnimation:anim forKey:nil];
    
}

核心动画(CAAnimationGroup)

  • CAAnimationGroup即动画组
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    // 同时缩放,平移,旋转
    CAAnimationGroup *group = [CAAnimationGroup animation];
    
    CABasicAnimation *scale = [CABasicAnimation animation];
    scale.keyPath = @"transform.scale";
    scale.toValue = @0.5;
    
    CABasicAnimation *rotation = [CABasicAnimation animation];
    rotation.keyPath = @"transform.rotation";
    rotation.toValue = @(arc4random_uniform(M_PI));
    
    CABasicAnimation *position = [CABasicAnimation animation];
    position.keyPath = @"position";
    position.toValue = [NSValue valueWithCGPoint:CGPointMake(arc4random_uniform(200), arc4random_uniform(200))];
    
    group.animations = @[scale,rotation,position];
    
    [_redView.layer addAnimation:group forKey:nil];
    
}

UIView和核心动画(Core Animation)的区别

  • 通过分别使用UIView动画和核心动画观察,核心动画并不会真实的改变图层的属性值

  • 而UIView动画必须通过修改属性的真实值,才会有动画效果

  • 如果以后做动画的时候,不需要与用户交互,通常使用核心动画(比如转场效果)

  • 注意:核心动画中,取消反弹的代码必须放在图层添加动画之前

      anim.removedOnCompletion = NO;
      anim.fillMode = kCAFillModeForwards;
      [self.redView.layer addAnimation:anim forKey:nil];
    
  • 核心动画要想监听动画的完成,需要实现代理,但是不需要遵守任何协议

// 动画完成的时候调用
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
    NSLog(@"%@", NSStringFromCGPoint(_redView.layer.position));
}
  • UIView动画在完成的时候,可以使用block定义动画完成后要执行的代码
    [UIView animateWithDuration:0.25 animations:^{
        
        _redView.layer.position = CGPointMake(150, 400);

    } completion:^(BOOL finished) {

            NSLog(@"%@", NSStringFromCGPoint(_redView.layer.position));

        }];

转盘的设计

  • 首先是界面的搭建,直接从xib中加载即可,创建一个类方法返回xib所对应的View
+ (instancetype)wheelView
{
   return  [[NSBundle mainBundle] loadNibNamed:@"WheelView" owner:nil options:nil][0];
}
  • 注意:initWithCoder:方法只是在加载xib的时候会调用,但是并不会将xib中的控件和代码进行连线
  • 所以需要在awakeFromNib方法中,进行添加和设置按钮的操作等
- (void)awakeFromNib
{
    // UIImageView是个比较特殊的View,默认不会与用户进行交互,需要设置userInteractionEnabled为YES
    _centerView.userInteractionEnabled = YES;
    CGFloat btnW = 68;
    CGFloat btnH = 143;
    
     CGFloat wh = self.bounds.size.width;
    
    // 加载大图片
    UIImage *bigImage = [UIImage imageNamed:@"LuckyAstrology"];
    
    // 加载大图片
    UIImage *selBigImage = [UIImage imageNamed:@"LuckyAstrologyPressed"];
    
    // 获取当前使用的图片像素和点的比例
    CGFloat scale = [UIScreen mainScreen].scale;
    CGFloat imageW = bigImage.size.width / 12 * scale;
    CGFloat imageH = bigImage.size.height * scale;
    // CGImageRef image:需要裁减的图片
    // rect:裁减区域
    // 裁减区域是以像素为基准
    // CGImageCreateWithImageInRect(CGImageRef image, CGRect rect)
    
    // 添加按钮
    for (int i = 0; i < 12; i++) {
        WheelButton *btn = [WheelButton buttonWithType:UIButtonTypeCustom];
        
        // 设置按钮的位置
        btn.layer.anchorPoint = CGPointMake(0.5, 1);
        
        btn.bounds = CGRectMake(0, 0, btnW, btnH);
        
        btn.layer.position = CGPointMake(wh * 0.5, wh * 0.5);
        
        // 按钮的旋转角度
        CGFloat radion = (30 * i) / 180.0 * M_PI;
        
        btn.transform = CGAffineTransformMakeRotation(radion);
        
        [_centerView addSubview:btn];
        
        // 加载按钮的图片
        // 计算裁减区域
        CGRect clipR = CGRectMake(i * imageW, 0, imageW, imageH);
        
        // 裁减图片
        CGImageRef imgR =  CGImageCreateWithImageInRect(bigImage.CGImage, clipR);
        
        UIImage *image = [UIImage imageWithCGImage:imgR];
        
        // 设置按钮的图片
        [btn setImage:image forState:UIControlStateNormal];
        
        // 设置选中状态下图片
        imgR = CGImageCreateWithImageInRect(selBigImage.CGImage, clipR);

        image = [UIImage imageWithCGImage:imgR];
        
        // 设置按钮的图片
        [btn setImage:image forState:UIControlStateSelected];
        
        // 设置选中背景图片
        [btn setBackgroundImage:[UIImage imageNamed:@"LuckyRototeSelected"] forState:UIControlStateSelected];
        
        // 监听按钮的点击
        [btn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
        
        // 默认选中第一个
        if (i == 0) {
            [self btnClick:btn];
        }
        
    }
}

- (void)btnClick:(UIButton *)btn
{
    _selBtn.selected = NO;
    btn.selected = YES;
    _selBtn = btn;
}
  • 开始旋转的时候,使用CABasicAnimation创建动画,并添加到self.centerView的layer上
#pragma mark - 开始旋转
- (void)start
{
    CABasicAnimation *anim = [CABasicAnimation animation];
    
    anim.keyPath = @"transform.rotation";
    
    anim.toValue = @(M_PI * 2);
    
    anim.duration = 2;
    
    anim.repeatCount = MAXFLOAT;
    
    [_centerView.layer addAnimation:anim forKey:nil];
}
  • 当需要修改系统控件的属性的时候,需要自定义控件并集成系统的控件,重写其中的方法即可
// 设置UIImageView的尺寸
// contentRect:按钮的尺寸
- (CGRect)imageRectForContentRect:(CGRect)contentRect
{
    // 计算UIImageView控件尺寸
    CGFloat imageW = 40;
    CGFloat imageH = 46;
    CGFloat imageX = (contentRect.size.width - imageW) * 0.5;
    CGFloat imageY = 20;
    return CGRectMake(imageX, imageY, imageW, imageH);
}

// 取消高亮状态
- (void)setHighlighted:(BOOL)highlighted
{
    
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌。在这里你可以看...
    F麦子阅读 5,001评论 5 13
  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥ios动画全貌。在这里你可以看...
    每天刷两次牙阅读 8,321评论 6 30
  • Core Animation Core Animation,中文翻译为核心动画,它是一组非常强大的动画处理API,...
    45b645c5912e阅读 2,968评论 0 21
  • 在iOS实际开发中常用的动画无非是以下四种:UIView动画,核心动画,帧动画,自定义转场动画。 1.UIView...
    请叫我周小帅阅读 3,000评论 1 23
  • 7、不使用IB是,下面这样做有什么问题? 6、请说说Layer和View的关系,以及你是如何使用它们的。 1.首先...
    AlanGe阅读 597评论 0 1