iOS核心动画 一一 QQ粘性效果

源代码地址: https://github.com/coderZYGui/ZYQViscousEffect

效果图如下:


整体思路:

手指移动,按钮跟着移动,按钮跟着手指移动,移动时底部有一个圆(称为小圆),根据上面的大圆按钮拖动的距离,小圆的半径不断变小,移动时中间有一块不规则的填充区域,手指移动超过一定的范围,填充效果消失,当手指松开时,判断当前大圆距离与小圆之间的距离.

如果小于一个最大距离,就让大圆回到原来的位置,下次拖动时,同样具有填充效果.

如果大于一个最大距离,手指松开时,播放一个动画,动画完成时,删除动画按钮


计算不规则路径的图解:


代码如下:

ViewController.m文件

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 表示视图的autoresizing面具是否转化为约束的基于布局体系。
    self.view.translatesAutoresizingMaskIntoConstraints = NO;
    
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    
}


@end

ZYBageValueBtn文件

//
//  ZYBageValueBtn.h
//  ZYQqViscousEffect
//
//  Created by 朝阳 on 2017/10/21.
//  Copyright © 2017年 sunny. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface ZYBageValueBtn : UIButton

@end

#import "ZYBageValueBtn.h"
#define maxDistance 80

@interface ZYBageValueBtn()

/** 小圆 */
@property (nonatomic, weak) UIView *smallCircle;

/** 形状图层 */
@property (nonatomic, weak) CAShapeLayer *shapeL;

@end

@implementation ZYBageValueBtn

/**
 求出路径后,要把路径填充起来.但是不能够直接给填充到当前的按钮之上.按钮是可以拖动的.
 绘制东西,当超出它的范围以外就不会再绘制.
 所以要把路径添加到按钮的父控件当中, 但是当前是一个路径,是不能够直接添加到父控件当中的.
 可能过形状图层添加.
 形状图层会根据一个路径生成一个形状.把这个形状添加到当前控件的图片父层就可以了.
 添加时需要注意:
 形状图层之有一个,所以不能够在手指拖动方法当中添加.由于当手指拖动的距离超过某个范围后,形状图片会被移除.
 下一次再去移动时, 还会有填充的路径.所以把创建形状图层搞成一个懒加载的形式,
 如果发现下一次被删除时,再重新创建.
 */

#pragma -mark 懒加载
- (CAShapeLayer *)shapeL
{
    if (_shapeL == nil) {
        CAShapeLayer *shapeL = [CAShapeLayer layer];
        // 把形状图层添加到self的父视图的最下面
        [self.superview.layer insertSublayer:shapeL atIndex:0];
        shapeL.fillColor = [UIColor redColor].CGColor;
        _shapeL = shapeL;
    }
    return _shapeL;
}

- (void)awakeFromNib
{
    [super awakeFromNib];
    [self setUp];
    
}

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self setUp];
    }
    return self;
}

#pragma -mark 初始化方法
- (void)setUp
{
    self.backgroundColor = [UIColor redColor];
    self.layer.cornerRadius = self.bounds.size.width * 0.5;
    [self setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    
    // 添加拖动手势
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    [self addGestureRecognizer:pan];
    
    // 添加一个小圆在大圆的底部
    UIView *smallCircle = [[UIView alloc] init];
    smallCircle.frame = self.frame;
    smallCircle.backgroundColor = self.backgroundColor;
    smallCircle.layer.cornerRadius = self.layer.cornerRadius;
    self.smallCircle = smallCircle;
    // 把小圆添加到大圆的父控件上
    [self.superview addSubview:smallCircle];
    // 放在大圆的下面
    [self.superview insertSubview:smallCircle belowSubview:self];
}

#pragma -mark 手势方法
- (void)pan:(UIPanGestureRecognizer *)pan
{
    // 获取当前偏移量
    CGPoint transP = [pan translationInView:self];
    // 注意: 当使用transform的时候,修改的是self的frame值,不会修改center
    //    self.transform = CGAffineTransformTranslate(self.transform, transP.x, transP.y);
    
    CGPoint center = self.center;
    center.x += transP.x;
    center.y += transP.y;
    self.center = center;
    // NSLog(@"%@",NSStringFromCGPoint(self.center));
    // 复位(相对于上一次)
    [pan setTranslation:CGPointZero inView:self];
    
    // 当拖动的时候计算距离
    CGFloat distance = [self distanceWithSmallCircle:self.smallCircle bigCircle:self];
    // 取出小圆的半径
    CGFloat smallRadius = self.bounds.size.width * 0.5;
    // 当距离增大,小圆半径每次都减少一个比例
    smallRadius -= distance / 10.0;
    // 每次移动重新设置小圆的尺寸
    self.smallCircle.bounds = CGRectMake(0, 0, smallRadius * 2, smallRadius * 2);
    self.smallCircle.layer.cornerRadius = smallRadius;
    
    // 业务逻辑处理
    UIBezierPath *path = [self pathWithSmallCircle:self.smallCircle bigCircle:self];
    
    // 只有当小圆不隐藏的时候,才填充路径
    if (self.smallCircle.hidden == NO) {
        self.shapeL.path = path.CGPath;
    }
    
    // 当两圆之间的距离超过一个最大值的时候.把小圆隐藏,清除路径
    if (distance > maxDistance) {
        self.smallCircle.hidden = YES;
        // 移除填充路径
        [self.shapeL removeFromSuperlayer];
    }
    
    // 当手指停止拖动时
    /*
     移动后手指松开时判断: 如果两个圆之间的距离不超过最大距离,则让大圆复位,小圆显示
     若超过最大距离,让大圆播放一个动画消失
     */
    if (pan.state == UIGestureRecognizerStateEnded) {
        // 距离小于最大距离
        if (distance < maxDistance) {
            // 添加一个弹簧动画
            [UIView animateWithDuration:0.2 delay:0 usingSpringWithDamping:0.4 initialSpringVelocity:0 options:UIViewAnimationOptionCurveLinear animations:^{
                // 将绘制的图形清空
                [self.shapeL removeFromSuperlayer];
                // 和小圆重合
                self.center = self.smallCircle.center;
                self.smallCircle.hidden = NO;
                
            } completion:nil];
            
        }else{
            
            //播放一个动画消失
            UIImageView *imageV = [[UIImageView alloc] initWithFrame:self.bounds];
            
            NSMutableArray *imageArray = [NSMutableArray array];
            for (int i = 0 ; i < 8; i++) {
                UIImage *image =  [UIImage imageNamed:[NSString stringWithFormat:@"%d",i +1]];
                [imageArray addObject:image];
            }
            
            imageV.animationImages = imageArray;
            imageV.animationDuration = 1;
            [imageV startAnimating];
            
            [self addSubview:imageV];
            
            // 延迟1s后执行
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                
                // 将自己从父控件中移除
                [self removeFromSuperview];
                
            });
        }
    }
    
}

#pragma -mark 两圆描述一个路径
/**
 给定两个圆描述一个不规则路径,然后填充这条路径,就是粘性效果
 
 @param smallCircle 小圆
 @param bigCircle 大圆
 @return 路径
 */
- (UIBezierPath *)pathWithSmallCircle:(UIView *)smallCircle bigCircle:(UIView *)bigCircle
{
    // 获得小圆中心点
    CGFloat x1 = smallCircle.center.x;
    CGFloat y1 = smallCircle.center.y;
    // 获得大圆中心点
    CGFloat x2 = bigCircle.center.x;
    CGFloat y2 = bigCircle.center.y;
    
    // 两圆之间的距离
    CGFloat d = [self distanceWithSmallCircle:smallCircle bigCircle:bigCircle];
    
    // 如果没有移动,则返回nil
    if (d <= 0) {
        return nil;
    }
    
    // X轴偏移量 / d
    CGFloat sinθ = (x2 - x1) / d;
    // Y轴偏移量 / d
    CGFloat cosθ = (y2 - y1) / d;
    
    /**
     已知一个角,一个斜边
     角的邻边 = 斜边 * cosθ
     角的对边 = 斜边 * sinθ
     */
    
    CGFloat r1 = smallCircle.bounds.size.width * 0.5;
    CGFloat r2 = bigCircle.bounds.size.width * 0.5;
    
    CGPoint pointA = CGPointMake(x1 - r1 * cosθ, y1 + r1 * sinθ);
    CGPoint pointB = CGPointMake(x1 + r1 * cosθ, y1 - r1 * sinθ);
    CGPoint pointC = CGPointMake(x2 + r2 * cosθ, y2 - r2 * sinθ);
    CGPoint pointD = CGPointMake(x2 - r2 * cosθ, y2 + r2 * sinθ);
    CGPoint pointO = CGPointMake(pointA.x + d * 0.5 * sinθ, pointA.y + d * 0.5 * cosθ);
    CGPoint pointP = CGPointMake(pointB.x + d * 0.5 * sinθ, pointB.y + d * 0.5 * cosθ);
    
    UIBezierPath *path = [UIBezierPath bezierPath];
    //AB
    [path moveToPoint:pointA];
    [path addLineToPoint:pointB];
    //BC(曲线)
    [path addQuadCurveToPoint:pointC controlPoint:pointP];
    //CD
    [path addLineToPoint:pointD];
    //DA(曲线)
    [path addQuadCurveToPoint:pointA controlPoint:pointO];
    
    return path;
    
}

#pragma -mark 两圆之间的距离
//当手指拖动大圆时,小圆的半径会根据拖动的距离进行减小.所以要计算出两个圆之间的距离.
//计算完毕后.让小圆的原始半径每次都减去一个距离比例.重新设置尺寸大小.和小圆的半径.
/**
 计算小圆中心到大圆中心的距离
 
 @param smallCircle 小圆
 @param bigCircle 大圆
 */
- (CGFloat)distanceWithSmallCircle:(UIView *)smallCircle bigCircle:(UIView *)bigCircle
{
    // 根据勾股定理求
    // X轴上的偏移量
    CGFloat offsetX = bigCircle.center.x - smallCircle.center.x;
    // Y轴上的偏移量
    CGFloat offsetY = bigCircle.center.y - smallCircle.center.y;
    
    return sqrt(offsetX * offsetX + offsetY * offsetY);
}

#pragma -mark 关闭按钮的高亮状态
/**
 取消按钮的高亮状态
 
 */
- (void)setHighlighted:(BOOL)highlighted
{
    
}

@end


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

white camel

感谢支持~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值