- 效果:拖动信息提示数目按钮,感觉像是在拉伸按钮,当拖动到一定范围,按钮(小圆被抽出),松开手小圆会爆炸。如果抽出小圆后,将小圆移回之前的位置,小圆会被恢复
- 核心计算公式:(见图中A、B、C、D、O、P点公式)
- 步骤:
- 生成大圆view并设置属性(示例是在storyboard中创建,所以下面代码没有这步)
- 根据手势拖动设置按钮移动
- 在按钮原来的位置生成一个与按钮一模一样的小圆view
- 封装两圆中心距离计算方法(勾股定理)
- 根据两圆中心距离及一定比例,设置小圆大小形变
- 利用核心计算公式,计算并绘制直线和曲线路径,通过形状图层生成不规则矩形(计算方法封装,需注意两圆中心距离为0,直接return)
- 当两圆中心距离达到一定距离后,实现抽走效果(小圆同时隐藏)
- 如果松手位置在爆炸范围圈外,松开手通过核心动画或者imageView播放爆炸帧动画,并将大圆从父控件中移除
- 如果松手位置在爆炸范围圈内,通过弹簧效果使大圆重新回到原点,并显示小圆
- VC.m中,要取消autoMask转化为自动布局,否则位移会有问题
[图片上传失败...(image-220148-1511405997537)]
##在BageView.m中
@interface BageView ()
//形状图层
@property (nonatomic, weak) CAShapeLayer *shapeLayer;
//小圆控件
@property (nonatomic, weak) UIView *smallCircleView;
@end
@implementation BageView
//懒加载形状图层
- (CAShapeLayer *)shapeLayer
{
if (_shapeLayer == nil) {
// 可以根据路径生成图层
CAShapeLayer *layer = [CAShapeLayer layer];
layer.fillColor = [UIColor redColor].CGColor;
[self.superview.layer insertSublayer:layer atIndex:0];//为什么要这样插入?
_shapeLayer = layer;
}
return _shapeLayer;
}
//对象初始化
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self setUp];
}
return self;
}
//对象初始化
- (void)awakeFromNib
{
// 初始化
[self setUp];
}
// 初始化操作
- (void)setUp
{
##生成大圆view并设置属性(示例是在storyboard中创建,所以下面代码没有这步)
// 默认背景颜色:红色
self.backgroundColor = [UIColor redColor];
// 默认圆角半径
self.layer.cornerRadius = self.bounds.size.width * 0.5;
// 设置字体
self.titleLabel.font = [UIFont systemFontOfSize:12];
// 设置文字的颜色
[self setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
##根据手势拖动设置按钮移动
// 添加pan手势
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
[self addGestureRecognizer:pan];
##在按钮原来的位置生成一个与按钮一模一样的小圆view
// 添加一个小圆
UIView *smallCircleView = [[UIView alloc] init];
// 尺寸
smallCircleView.frame = self.frame;
// 颜色
smallCircleView.backgroundColor = self.backgroundColor;
// 设置圆角半径
smallCircleView.layer.cornerRadius = self.layer.cornerRadius;
// 添加到父控件
[self.superview insertSubview:smallCircleView belowSubview:self];
_smallCircleView = smallCircleView;
}
##根据手势拖动设置按钮移动
- (void)pan:(UIPanGestureRecognizer *)pan
{
// 获取手指的偏移量
CGPoint transP = [pan translationInView:self];
// 修改形变,移动控件的位置
// 修改transform并不会修改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;
// 复位
[pan setTranslation:CGPointZero inView:self];
##封装两圆中心距离计算方法(勾股定理)
// 计算下两个圆心的距离
CGFloat distance = [self distanceWithSmallView:_smallCircleView bigView:self];
##根据两圆中心距离及一定比例,设置小圆大小形变
// 修改小圆的半径,根据圆心距离产生一个比例
CGFloat smallR = self.bounds.size.width * 0.5 - distance / 10.0;
_smallCircleView.bounds = CGRectMake(0, 0, smallR * 2 , smallR * 2);
// 设置圆角半径
_smallCircleView.layer.cornerRadius = smallR;
利用核心计算公式,计算并绘制直线和曲线路径,通过形状图层生成不规则矩形(计算方法封装,需注意两圆中心距离为0,直接return)
// 计算不规则的矩形路径
UIBezierPath *path = [self pathWithSmallView:_smallCircleView bigView:self];
if (self.smallCircleView.hidden == NO) { // 当小圆没有显示的时候,就不需要描述不规则的矩形
// 设置形状图层的路径
self.shapeLayer.path = path.CGPath;
}
##当两圆中心距离达到一定距离后,实现抽走效果(小圆同时隐藏)
// 当圆心距离大于60的时候,吸附效果
if (distance > 60) {
// 隐藏小圆
_smallCircleView.hidden = YES;
// 不规则的矩形移除父控件
// self.shapeLayer.hidden = YES;
[self.shapeLayer removeFromSuperlayer];
}
##如果松手位置在爆炸范围圈外,松开手通过核心动画或者imageView播放爆炸帧动画,并将大圆从父控件中移除
if (pan.state == UIGestureRecognizerStateEnded) { // 手指抬起的时候,需要做判断
if (distance > 60) {
NSMutableArray *images = [NSMutableArray array];
// 加载gif图片
for (int i = 1; i <= 8; i++) {
NSString *imageName = [NSString stringWithFormat:@"%d",i];
UIImage *image = [UIImage imageNamed:imageName];
[images addObject:image];
}
// 播放gif图片
UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.bounds];
// 设置动画数组
imageView.animationImages = images;
imageView.animationDuration = 1;
[self addSubview:imageView];
// 主动播放
[imageView startAnimating];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.9 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self removeFromSuperview];
});
##如果松手位置在爆炸范围圈内,通过弹簧效果使大圆重新回到原点,并显示小圆
}else{ // 还原
// 位置
[UIView animateWithDuration:0.25 delay:0 usingSpringWithDamping:0.1 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.center = self.smallCircleView.center;
} completion:^(BOOL finished) {
// 小圆显示
self.smallCircleView.hidden = NO;
}];
}
}
}
利用核心计算公式,计算并绘制直线和曲线路径,通过形状图层生成不规则矩形(计算方法封装,需注意两圆中心距离为0,直接return))
// 计算不规则的矩形路径
- (UIBezierPath *)pathWithSmallView:(UIView *)smallView bigView:(UIView *)bigView
{
// 计算圆心
CGFloat d = [self distanceWithSmallView:smallView bigView:bigView];
CGFloat y1 = smallView.center.y;
CGFloat x1 = smallView.center.x;
CGFloat r1 = smallView.layer.cornerRadius;
CGFloat y2 = bigView.center.y;
CGFloat x2 = bigView.center.x;
CGFloat r2 = bigView.layer.cornerRadius;
// 如果间距为0,不计算矩形路径
if (d == 0) return nil;
// cosθ
CGFloat cosθ = (y2 - y1) / d;
// sinθ
CGFloat sinθ = (x2 - x1) / d;
// A:
CGPoint pointA = CGPointMake(x1 - r1 * cosθ, y1 + r1 * sinθ);
// B:
CGPoint pointB = CGPointMake(x1 + r1 * cosθ, y1 - r1 * sinθ);
// C:
CGPoint pointC = CGPointMake(x2 + r2 * cosθ, y2 - r2 * sinθ);
// D:
CGPoint pointD = CGPointMake(x2 - r2 * cosθ, y2 + r2 * sinθ);
// O:
CGPoint pointO = CGPointMake(pointA.x + d * 0.5 * sinθ, pointA.y + d * 0.5 * cosθ);
// P:
CGPoint pointP = CGPointMake(pointB.x + d * 0.5 * sinθ, pointB.y + d * 0.5 * cosθ);
// 描述路径
UIBezierPath *path = [UIBezierPath bezierPath];
// nan == null
[path moveToPoint:pointA];
// AB
[path addLineToPoint:pointB];
// BC
// 绘制曲线
[path addQuadCurveToPoint:pointC controlPoint:pointP];
// CD
[path addLineToPoint:pointD];
// DA
[path addQuadCurveToPoint:pointA controlPoint:pointO];
return path;
}
##封装两圆中心距离计算方法(勾股定理)
// 计算两个控件的圆心距离
- (CGFloat)distanceWithSmallView:(UIView *)smallView bigView:(UIView *)bigView
{
CGFloat offsetX = bigView.center.x - smallView.center.x;
CGFloat offsetY = bigView.center.y - smallView.center.y;
return sqrt(offsetX * offsetX + offsetY * offsetY);
}
- (void)setHighlighted:(BOOL)highlighted{}
@end
##VC.m中,要取消autoMask转化为自动布局,否则位移会有问题
- (void)viewDidLoad {
[super viewDidLoad];
// 取消autoMask转化为自动布局
self.view.translatesAutoresizingMaskIntoConstraints = NO;
}