17-核心動畫實戰(zhàn)

轉(zhuǎn)盤(旋轉(zhuǎn))

  • 自定義轉(zhuǎn)盤上的button - WheelButton
    • 事件處理,重寫hitTest方法來尋找最合適的view
// 尋找最合適的view
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    // 獲取真實button的寬度和高度
    CGFloat btnW = self.bounds.size.width;
    CGFloat btnH = self.bounds.size.height;

    // 設(shè)置要處理事件的區(qū)域    
    CGFloat x = 0;
    CGFloat y = btnH / 2;
    CGFloat w = btnW;
    CGFloat h = y;
    CGRect rect = CGRectMake(x, y, w, h);
    if (CGRectContainsPoint(rect, point)) {
        return nil;
    }else{
        return [super hitTest:point withEvent:event];
    }
    
}
  • 自定義按鈕可以控制按鈕上圖片顯示的位置和尺寸
// 設(shè)置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);
}
  • 自定義按鈕取消點擊時的高亮狀態(tài),需重寫setHighlighted方法
// 取消高亮狀態(tài)
- (void)setHighlighted:(BOOL)highlighted
{
    
}
  • 自定義轉(zhuǎn)盤的view - WheelView
    • 在.h文件中聲明初始化方法和start以及pause方法
+ (instancetype)wheelView;
- (void)start
- (void)pause
  • 在wheelView初始化方法中加載xib文件
+ (instancetype)wheelView
{
   return  [[NSBundle mainBundle] loadNibNamed:@"WheelView" owner:nil options:nil][0];
}
  • initWithCoder方法只是在加載xib的時候會調(diào)用,但是并不會將xib中的控件和代碼聲明進(jìn)行連線
  • 可以在awakeFromNib方法中進(jìn)行添加按鈕的操作
- (void)awakeFromNib
{
    // 由于imageView的特殊性,默認(rèn)是不能與用戶進(jìn)行交互的
    _centerView.userInteractionEnabled = YES;
    CGFloat btnW = 68;
    CGFloat btnH = 143;
    
    CGFloat wh = self.bounds.size.width;
    
    // 加載大圖片(默認(rèn))
    UIImage *bigImage = [UIImage imageNamed:@"LuckyAstrology"];
    
    // 加載大圖片(選中)
    UIImage *selBigImage = [UIImage imageNamed:@"LuckyAstrologyPressed"];
    
    // 獲取當(dāng)前使用的圖片像素和點的比例
    CGFloat scale = [UIScreen mainScreen].scale;
    CGFloat imageW = bigImage.size.width / 12 * scale;
    CGFloat imageH = bigImage.size.height * scale;
    // CGImageRef image:需要裁減的圖片
    // rect:裁減區(qū)域
    // 裁減區(qū)域是以像素為基準(zhǔn)
    // CGImageCreateWithImageInRect(CGImageRef image, CGRect rect)
    
    // 添加按鈕
    for (int i = 0; i < 12; i++) {
        WheelButton *btn = [WheelButton buttonWithType:UIButtonTypeCustom];
        
        // 設(shè)置按鈕的位置
        btn.layer.anchorPoint = CGPointMake(0.5, 1);
        
        btn.bounds = CGRectMake(0, 0, btnW, btnH);
        
        btn.layer.position = CGPointMake(wh * 0.5, wh * 0.5);
        
        // 按鈕的旋轉(zhuǎn)角度
        CGFloat radion = (30 * i) / 180.0 * M_PI;
        
        btn.transform = CGAffineTransformMakeRotation(radion);
        
        [_centerView addSubview:btn];
        
        // 加載按鈕的圖片
        // 計算裁減區(qū)域
        CGRect clipR = CGRectMake(i * imageW, 0, imageW, imageH);
        
        // 裁減圖片
        CGImageRef imgR =  CGImageCreateWithImageInRect(bigImage.CGImage, clipR);
        
        UIImage *image = [UIImage imageWithCGImage:imgR];
        
        // 設(shè)置按鈕的圖片
        [btn setImage:image forState:UIControlStateNormal];
        
        // 設(shè)置選中狀態(tài)下圖片
        imgR = CGImageCreateWithImageInRect(selBigImage.CGImage, clipR);

        image = [UIImage imageWithCGImage:imgR];
        
        // 設(shè)置按鈕的圖片
        [btn setImage:image forState:UIControlStateSelected];
        
        // 設(shè)置選中背景圖片
        [btn setBackgroundImage:[UIImage imageNamed:@"LuckyRototeSelected"] forState:UIControlStateSelected];
        
        // 監(jiān)聽按鈕的點擊
        [btn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
        
        // 默認(rèn)選中第一個
        if (i == 0) {
            [self btnClick:btn];
        }
        
        // btn.backgroundColor = [UIColor redColor];
    }
}
  • 默認(rèn)記錄上一次被點擊的按鈕,當(dāng)點擊下一個按鈕時清空上一個按鈕,并將當(dāng)前按鈕記錄
- (void)btnClick:(UIButton *)btn
{
    _selBtn.selected = NO;
    btn.selected = YES;
    _selBtn = btn;
}
  • 當(dāng)點擊開始選號按鈕時,通過transform獲取選中按鈕的旋轉(zhuǎn)角度,并進(jìn)行逆向旋轉(zhuǎn),并在動畫結(jié)束以后重新開啟定時器動畫
#pragma mark - 點擊開始選號的時候
- (IBAction)startPicker:(id)sender {
    
    // 不需要定時器旋轉(zhuǎn)
    self.link.paused = YES;
    
    // 中間的轉(zhuǎn)盤快速的旋轉(zhuǎn),并且不需要與用戶交互
    
    CABasicAnimation *anim = [CABasicAnimation animation];
    
    anim.keyPath = @"transform.rotation";
    
    anim.toValue = @(M_PI * 2 * 3);
    
    anim.duration = 0.5;
    anim.delegate = self;
    
    [_centerView.layer addAnimation:anim forKey:nil];
    
    // 點擊哪個星座,就把當(dāng)前星座指向中心點上面
    
    // M_PI 3.14
    // 根據(jù)選中的按鈕獲取旋轉(zhuǎn)的度數(shù),
    // 通過transform獲取角度
    CGFloat angle = atan2(_selBtn.transform.b, _selBtn.transform.a);
    
    // 旋轉(zhuǎn)轉(zhuǎn)盤
    _centerView.transform = CGAffineTransformMakeRotation(-angle);
    
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        self.link.paused = NO;
    });
}

折疊圖片的動畫

  • 原理分析:通過兩個UIImageView分別顯示圖片的上半部分(topView)和下半部分(bottonView),然后將其放入同一個UIView(dragView),旋轉(zhuǎn)的時候只旋轉(zhuǎn)上部分的控件,為了讓一張完整的圖片通過兩個控件顯示,可以通過layer圖層控制圖片顯示的內(nèi)容

  • 如何快速的把兩個控件拼接成一個完整的圖片

    • 可以通過contentsRect設(shè)置圖片顯示的尺寸,取值0~1
    _topView.layer.contentsRect = CGRectMake(0, 0, 1, 0.5);
    _topView.layer.anchorPoint = CGPointMake(0.5, 1);
    
    _bottomView.layer.contentsRect = CGRectMake(0, 0.5, 1, 0.5);
    _bottomView.layer.anchorPoint = CGPointMake(0.5, 0);
  • 分別給dragView添加拖拽手勢(UIPanGestureRecognizer)和bottonView添加漸變圖層(CAGradientLayer)
    // 添加手勢
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    
    [_dragView addGestureRecognizer:pan];
    
    // 漸變圖層
    CAGradientLayer *gradientL = [CAGradientLayer layer];
    
    // 注意圖層需要設(shè)置尺寸
    gradientL.frame = _bottomView.bounds;

    gradientL.opacity = 0;
    gradientL.colors = @[(id)[UIColor clearColor].CGColor,(id)[UIColor blackColor].CGColor];
    _gradientL = gradientL;
    // 設(shè)置漸變顏色
    // gradientL.colors = @[(id)[UIColor redColor].CGColor,(id)[UIColor greenColor].CGColor,(id)[UIColor yellowColor].CGColor];
    
    // 設(shè)置漸變定位點
    // gradientL.locations = @[@0.1,@0.4,@0.5];
    
    // 設(shè)置漸變開始點,取值0~1
    // gradientL.startPoint = CGPointMake(0, 1);
    
    [_bottomView.layer addSublayer:gradientL];
  • 在拖拽手勢的pan方法中給topView的layer圖層添加旋轉(zhuǎn)動畫以及設(shè)置漸變圖層的陰影效果,并在手指抬起的時候添加彈簧效果的動畫
// 拖動的時候旋轉(zhuǎn)上部分內(nèi)容,200 M_PI
- (void)pan:(UIPanGestureRecognizer *)pan
{
    // 獲取偏移量
   CGPoint transP = [pan translationInView:_dragView];
    
    // 旋轉(zhuǎn)角度,往下逆時針旋轉(zhuǎn)
    CGFloat angle = -transP.y / 200.0 * M_PI;
    
    CATransform3D transfrom = CATransform3DIdentity;
    
    // 增加旋轉(zhuǎn)的立體感,近大遠(yuǎn)小,d:距離圖層的距離
    transfrom.m34 = -1 / 500.0;
    
    transfrom = CATransform3DRotate(transfrom, angle, 1, 0, 0);
    
    _topView.layer.transform = transfrom;
    
    // 設(shè)置陰影效果
    _gradientL.opacity = transP.y * 1 / 200.0;
    
    if (pan.state == UIGestureRecognizerStateEnded) { // 反彈
        
        // 彈簧效果的動畫
        // SpringWithDamping:彈性系數(shù),越小,彈簧效果越明顯
        [UIView animateWithDuration:0.6 delay:0 usingSpringWithDamping:0.2 initialSpringVelocity:10 options:UIViewAnimationOptionCurveEaseInOut animations:^{
            
            _topView.layer.transform = CATransform3DIdentity;
            
        } completion:^(BOOL finished) {
            
        }];
    }
    
}

音量震動條的動畫

  • 核心:復(fù)制圖層CAReplicatorLayer的使用

  • 復(fù)制圖層:是指可以把圖層里面的所有子層復(fù)制

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    // CAReplicatorLayer復(fù)制圖層,可以把圖層里面所有子層復(fù)制
    // 創(chuàng)建復(fù)制圖層
    CAReplicatorLayer *repL = [CAReplicatorLayer layer];
    
    repL.frame = _lightView.bounds;
    
    [_lightView.layer addSublayer:repL];
    
    CALayer *layer = [CALayer layer];
    
    layer.anchorPoint = CGPointMake(0.5, 1);
    layer.position = CGPointMake(15, _lightView.bounds.size.height);
    layer.bounds = CGRectMake(0, 0, 30, 150);
    
    layer.backgroundColor = [UIColor whiteColor].CGColor;
    
    [repL addSublayer:layer];
    
    CABasicAnimation *anim = [CABasicAnimation animation];
    
    anim.keyPath = @"transform.scale.y";
    
    anim.toValue = @0.1;
    
    anim.duration = 0.5;
    
    anim.repeatCount = MAXFLOAT;
    
    // 設(shè)置動畫反轉(zhuǎn)
    anim.autoreverses = YES;
    
    [layer addAnimation:anim forKey:nil];
    
    // 復(fù)制層中子層總數(shù)
    // instanceCount:表示復(fù)制層里面有多少個子層,包括原始層
    repL.instanceCount = 3;
    
    // 設(shè)置復(fù)制子層偏移量,不包括原始層,相對于原始層x偏移
    repL.instanceTransform = CATransform3DMakeTranslation(45, 0, 0);
    
    // 設(shè)置復(fù)制層動畫延遲時間
    repL.instanceDelay = 0.1;
    
    // 如果設(shè)置了原始層背景色,就不需要設(shè)置這個屬性
    repL.instanceColor = [UIColor greenColor].CGColor;
    
    repL.instanceGreenOffset = -0.3;
    
}

活動指示器動畫

  • 同樣是利用復(fù)制層制作類似進(jìn)度條的動畫
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    CAReplicatorLayer *repL = [CAReplicatorLayer layer];
    
    repL.frame = _redView.bounds;
    
    [_redView.layer addSublayer:repL];
    
    CALayer *layer = [CALayer layer];
    
    layer.transform = CATransform3DMakeScale(0, 0, 0);
    
    layer.position = CGPointMake(_redView.bounds.size.width / 2, 20);
    
    layer.bounds = CGRectMake(0, 0, 10, 10);
    
    layer.backgroundColor = [UIColor greenColor].CGColor;
    
    [repL addSublayer:layer];
    
    // 設(shè)置縮放動畫
    CABasicAnimation *anim = [CABasicAnimation animation];
    
    anim.keyPath = @"transform.scale";
    
    anim.fromValue = @1;
    
    anim.toValue = @0;
    
    anim.repeatCount = MAXFLOAT;
    
    CGFloat duration = 1;
    
    anim.duration = duration;
    
    [layer addAnimation:anim forKey:nil];

    int count = 20;
    
    CGFloat angle = M_PI * 2 / count;
    
    // 設(shè)置子層總數(shù)
    repL.instanceCount = count;
    
    repL.instanceTransform = CATransform3DMakeRotation(angle, 0, 0, 1);
    
    repL.instanceDelay = duration / count;
    
}

單條路徑粒子效果動畫

  • 原理分析:在touchesBegan方法中創(chuàng)建UIBezierPath并設(shè)置起點,在touchesMoved方法添加線到某點,在awakeFromNib方法中初始化復(fù)制層和單個粒子的圖層,當(dāng)用戶點擊開始動畫按鈕,給單個粒子設(shè)置幀動畫,并設(shè)置粒子的數(shù)量以及延遲動畫的時間

  • 當(dāng)手指觸摸開始的時候

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    
    // 重繪
    [self reDraw];
    
    // 獲取touch對象
    UITouch *touch = [touches anyObject];
    
    // 獲取當(dāng)前觸摸點
    CGPoint curP = [touch locationInView:self];
    
    // 創(chuàng)建一個路徑
    UIBezierPath *path = [UIBezierPath bezierPath];

    // 設(shè)置起點
    [path moveToPoint:curP];
    
    _path = path;
    
}
  • 在手指移動過程中時刻添加連線并進(jìn)行重繪
static int _instansCount = 0;

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    // 獲取touch對象
    UITouch *touch = [touches anyObject];
    
    // 獲取當(dāng)前觸摸點
    CGPoint curP = [touch locationInView:self];

    // 添加線到某個點
    [_path addLineToPoint:curP];
    
    // 重繪
    [self setNeedsDisplay];
    
    _instansCount ++;
    
}

- (void)drawRect:(CGRect)rect {
    [_path stroke];
}
  • 在awakeFromNib方法中初始化圖層
- (void)awakeFromNib
{
    // 創(chuàng)建復(fù)制層
    CAReplicatorLayer *repL = [CAReplicatorLayer layer];
    
    repL.frame = self.bounds;
    
    [self.layer addSublayer:repL];
    
    // 創(chuàng)建圖層
    CALayer *layer = [CALayer layer];
    
    CGFloat wh = 10;
    layer.frame = CGRectMake(0, -1000, wh, wh);
    
    layer.cornerRadius = wh / 2;
    
    layer.backgroundColor = [UIColor blueColor].CGColor;
    
    [repL addSublayer:layer];
    
    _dotLayer = layer;
    
    _repL = repL;
}

#pragma mark - 開始動畫
- (void)startAnim
{

    _dotLayer.hidden = NO;
    
    // 創(chuàng)建幀動畫
    CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
    
    anim.keyPath = @"position";
    
    anim.path = _path.CGPath;
    
    anim.duration = 4;
    
    anim.repeatCount = MAXFLOAT;
    
    [_dotLayer addAnimation:anim forKey:nil];
    
    // 復(fù)制子層
    _repL.instanceCount = _instansCount;
    
    _repL.instanceDelay = 0.1;
    
}

- (void)reDraw
{
    
    _path = nil;
    [self setNeedsDisplay];
    
    _dotLayer.hidden = YES;
    
}

多條路徑粒子效果動畫

  • 原理分析:通過懶加載dotLayer和path,可以保證在程序運行過程中只有一個dotLayer和path對象

  • 懶加載dotLayer和path,需重寫其get方法

#pragma mark - 懶加載點層
- (CALayer *)dotLayer
{
    if (_dotLayer == nil) {
        // 創(chuàng)建圖層
        CALayer *layer = [CALayer layer];
        
        CGFloat wh = 10;
        layer.frame = CGRectMake(0, -1000, wh, wh);
        
        layer.cornerRadius = wh / 2;
        
        layer.backgroundColor = [UIColor blueColor].CGColor;
        [_repL addSublayer:layer];
        
        _dotLayer = layer;
    }
    return _dotLayer;
}

- (UIBezierPath *)path
{
    if (_path == nil) {
        _path = [UIBezierPath bezierPath];
    }
    
    return _path;
}
  • 注意:如果復(fù)制的子層有動畫,需要先添加動畫再復(fù)制,否則子層動畫可能添加不成功

  • 復(fù)制子層:self.repL.instanceCount = self.instanceCount;

  • 延遲圖層動畫:self.repL.instanceDelay = 0.2;

倒影效果

  • 利用復(fù)制層,將圖片繞X軸旋轉(zhuǎn)后修改圖層顏色通道的值
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    CAReplicatorLayer *layer =  (CAReplicatorLayer *)_repView.layer;
    
    layer.instanceCount = 2;
    
    CATransform3D transform = CATransform3DMakeTranslation(0, _repView.bounds.size.height, 0);
    // 繞著X軸旋轉(zhuǎn)
    transform = CATransform3DRotate(transform, M_PI, 1, 0, 0);
    
    // 往下面平移控件的高度
    layer.instanceTransform = transform;
    
    layer.instanceAlphaOffset = -0.1;
    layer.instanceBlueOffset = -0.1;
    layer.instanceGreenOffset = -0.1;
    layer.instanceRedOffset = -0.1;

}

QQ粘性效果

  • 注意:touchesBegan方法會和按鈕的監(jiān)聽事件沖突,所以在有按鈕的監(jiān)聽事件以后,只能使用手勢事件代替touchesBegan方法

  • 使用self.transform修改按鈕的形變,并不會修改按鈕的中心點,所以需要直接修改self.center

  • 每一次相對于上一次的形變,都需要進(jìn)行復(fù)位操作

  • 繪制不規(guī)則的矩形,不能通過繪圖,因為繪圖只能在當(dāng)前控件上畫,超出部分將不會顯示,而且只有當(dāng)兩個圓產(chǎn)生距離的時候才需要進(jìn)行繪制

  • 描述兩圓之間的矩形路徑需要特定的算法

  • 手指抬起的時候,將大圓進(jìn)行還原,根據(jù)圓心的距離判斷是否需要移除不規(guī)則矩形

    • 當(dāng)大小圓圓心的距離大于設(shè)定的最大圓心距離時,需要展示一張爆炸的gif圖片,并將大圓從父控件中移除
    • 當(dāng)圓心距離不大于規(guī)定的距離時,需要移除不規(guī)則矩形,并將大圓還原到默認(rèn)的位置
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Core Animation Core Animation,中文翻譯為核心動畫,它是一組非常強(qiáng)大的動畫處理API,...
    45b645c5912e閱讀 3,154評論 0 21
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,979評論 25 709
  • 官網(wǎng)中文版 1 簡易天空替換## 技能:線性擦除,追蹤運動,用顏色鍵K出人物,用色彩曲線調(diào)色。(1)天空圖片素材放...
    朱細(xì)細(xì)閱讀 11,641評論 4 82
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,208評論 4 61
  • 初戀的感覺你還記得嗎? 你的初戀現(xiàn)在在哪里? -1- 他出現(xiàn)在我的世界里是在讀小學(xué)的時候。 那個時候的我,心智還是...
    周小墨er閱讀 400評論 0 2

友情鏈接更多精彩內(nèi)容