本文并非最终版本,如果想要关注更新或更正的内容请关注文集,联系方式详见文末,如有疏忽和遗漏,欢迎指正。
本文相关目录:
===================== 所属文集:6.0 图形和多媒体 =====================
6.2 核心动画->1.0 CALayer的简介
6.2 核心动画->1.1 CALayer的基本属性
6.2 核心动画->1.2 CALayer的创建 & 非根layer的隐式动画
6.2 核心动画->2.0 Core Animation(核心动画)
6.2 核心动画->3.0 核心动画 & UIView动画
6.2 核心动画->4.0 常用动画效果
===================== 所属文集:6.0 图形和多媒体 =====================
4.1 旋转立体效果 - 折叠图片案例
1、如何制作图片折叠效果?
把一张图片分成两部分显示,上面一部分,下面一部分,折叠上面部分的内容。
2、如何把一张图片分成两部分显示。
- 创建两个View,一个显示上半部分,一个显示下半部分
- 需要用到Layer(图层)的一个属性:contentsRect ,这个属性是可以控制图片显示的尺寸,可以
让图片只显示上部分或者下部分,注意: 取值范围是0~1
CGRectMake(0, 0, 1, 0.5) : 表示显示上半部分
CGRectMake(0, 0.5, 1, 0.5) : 表示显示下半部分
3、如何快速的把两部分拼接成一张完整的图片
(1)首先了解折叠,折叠其实就是旋转,既然需要旋转就需要明确锚点,因为默认都是绕着锚点旋转的。
(2)上部分内容绕着底部中心旋转,所以设置上部分的锚点为(0.5,1)
(3)锚点设置好了,就可以确定位置了.
(4)可以把上下部分重合在一起,然后分别设置上下部分的锚点,上部分的锚点为(0.5,1),下部分
的锚点为(0.5,0),就能快速重叠了。
4、如何折叠上部分内容。
- 在拖动视图的时候,旋转上部分控件。修改 transform 属性。
- 可以在上部分和下部分底部添加一个拖动控件(拖动控件尺寸就是完整的图片尺寸),给这个控件添
加一个pan手势,就能制造一个假象,拖动控件的时候,折叠图片。
- 计算Y轴每偏移一点,需要旋转多少角度,假设完整图片尺寸高度为200,当y = 200时,上部分图片
应该刚好旋转180°,因此 angle = offsetY * M_PI / 200 ;
- 上部分内容应该是绕着X轴旋转,逆时针旋转,因此角度需要为负数。
代码示例:
#import "ViewController.h"
@interface ViewController ()
@property(weak, nonatomic) IBOutlet UIImageView *topView;
@property(weak, nonatomic) IBOutlet UIImageView *bottomView;
@property(weak, nonatomic) IBOutlet UIView *dragView; // 手势拖动的 View
@property(weak, nonatomic) CAGradientLayer *gradientL; // 渐变图层
@end
@implementation ViewController
// 快速把两个控件拼接成一个完整图片
- (void)viewDidLoad {
[super viewDidLoad];
// 通过设置contentsRect可以设置图片显示的尺寸,取值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);
// 添加手势
UIPanGestureRecognizer *pan =
[[UIPanGestureRecognizer alloc] initWithTarget:self
action:@selector(pan:)];
[_dragView addGestureRecognizer:pan];
#pragma mark - 阴影效果(当折叠图片的时候,底部应该有个阴影渐变过程)(利用CAGradientLayer渐变图层,制作阴影效果,添加到底部视图上,并且一开始需要隐藏,在拖动的时候慢慢显示出来。颜色应是由 透明到黑色 渐变,表示阴影从无到有)
// 创建渐变图层
CAGradientLayer *gradientL = [CAGradientLayer layer];
// 设置渐变颜色
gradientL.colors =
@[ (id)[UIColor clearColor].CGColor, (id)[UIColor blackColor].CGColor ];
// 设置尺寸
gradientL.frame = _bottomView.bounds;
_gradientL = gradientL;
// 设置图层透明色为不透明
gradientL.opacity = 0;
// 设置渐变定位点(决定渐变的范围)
// gradientL.locations = @[@0.1,@0.4,@0.5];
// 设置渐变开始点,取值0~1(决定渐变的方向)
// gradientL.startPoint = CGPointMake(0, 1);
[_bottomView.layer addSublayer:gradientL];
}
#pragma mark - 拖动的时候旋转上部分内容
- (void)pan:(UIPanGestureRecognizer *)pan {
// 获取手指偏移量
CGPoint transP = [pan translationInView:_dragView];
// 初始化形变
CATransform3D transfrom = CATransform3DIdentity;
// 设置立体效果,增加旋转的立体感,给形变设置m34属性,近大远小
// -1 / z,z表示观察者在z轴上的值,表示距图层的距离:z越小,离我们越近,东西越大
transfrom.m34 = -1 / 500.0; // 第二个数
// 计算折叠角度,因为往下逆时针旋转,所以取反
CGFloat angle = -transP.y / 200.0 * M_PI;
// 开始旋转
transfrom = CATransform3DRotate(transfrom, angle, 1, 0, 0);
_topView.layer.transform = transfrom;
// 设置阴影效果
// 在拖动的时候计算不透明度值,假设拖动200,阴影完全显示,不透明度应该为1,因此opacity=y轴偏移量*1/200.0;
_gradientL.opacity = transP.y * 1 / 200.0;
#pragma mark - 反弹效果(当手指抬起的时候,应该把折叠图片还原,其实就是把形变清空)
// 当手指抬起的时候,设置弹簧效果的动画
if (pan.state == UIGestureRecognizerStateEnded) {
// 弹簧效果的动画
[UIView animateWithDuration:0.6
delay:0
usingSpringWithDamping:0.2 // 弹性系数:越小,弹簧效果越明显
initialSpringVelocity:10 // 弹簧的初始速度
options:UIViewAnimationOptionCurveEaseInOut // 动画效果:快入快出
animations:^{
_topView.layer.transform = CATransform3DIdentity; // 初始化形变量
_gradientL.opacity = 0; // 还原阴影
}
completion:^(BOOL finished){
}];
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
运行效果:
旋转立体效果.gif4.2 音量震动条效果
方法1 :创建多个layer,按顺序播放y轴缩放动画
方法2:利用 CAReplicatorLayer(复制图层)实现
(这里只介绍方法2)
1、什么是CAReplicatorLayer?
一种可以复制自己子层的layer,并且复制出来的layer和原生子层有同样的属性,位置,形变,动画。
2、CAReplicatorLayer属性
// 复制层中子层总数:表示复制层里面有多少个子层,包括原始层
@property NSInteger instanceCount;
// 复制子层动画延迟时长
@property CFTimeInterval instanceDelay;
// 复制子层形变偏移量(不包括原生子层):相对于原生子层x偏移,每个复制子层都是相对上一个
@property CATransform3D instanceTransform;
// 子层颜色:会和原生子层背景色冲突,如果设置了原生子层背景色,就不需要设置这个属性
@property (nullable) CGColorRef instanceColor;
// 颜色通道的偏移量:每个复制子层都是相对上一个的偏移量
@property float instanceRedOffset;
@property float instanceGreenOffset;
@property float instanceBlueOffset;
@property float instanceAlphaOffset;
代码示例:
#import "ViewController.h"
@interface ViewController ()
@property(weak, nonatomic) IBOutlet UIView *lightView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
#pragma mark - 首先创建复制layer,音乐振动条layer添加到复制layer上,然后复制子层
// 创建复制图层(CAReplicatorLayer),可以把图层里面所有子层复制
CAReplicatorLayer *repL = [CAReplicatorLayer layer];
// 设置复制图层的属性
repL.frame = _lightView.bounds;
// 添加复制图层到lightView上
[_lightView.layer addSublayer:repL];
#pragma mark - 先创建一个音量振动条,并且设置好动画,动画是绕着底部缩放,设置锚点
// 创建图层
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"; // 缩放y值
anim.toValue = @0.1;
anim.duration = 0.2;
anim.repeatCount = MAXFLOAT;
anim.autoreverses = YES; // 设置动画反转
// 添加动画
[layer addAnimation:anim forKey:nil];
// 复制层中子层总数:表示复制层里面有多少个子层,包括原生子层
repL.instanceCount = 5; // 设置5个子层,其中4个是复制层
// 复制子层形变偏移量(不包括原生子层):相对于原生子层x偏移,每个复制子层都是相对上一个
// 设置复制子层的相对位置,每个x轴相差45
repL.instanceTransform = CATransform3DMakeTranslation(45, 0, 0);
// 设置复制子层动画延迟时长
repL.instanceDelay = 0.1;
// 子层颜色:会和原生子层背景色冲突,如果设置了原生子层背景色,就不需要设置这个属性
repL.instanceColor = [UIColor blueColor].CGColor;
// 颜色通道的偏移量:每个复制子层都是相对上一个的偏移量
repL.instanceBlueOffset = -0.3;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
运行效果:
音量震动条效果.gif4.3 活动指示器效果
设计思路:
1、创建复制图层
2、创建一个矩形图层,设置缩放动画
3、复制矩形图层,并且设置每个复制层的角度形变
4、设置复制动画延长时间
代码示例:
#import "ViewController.h"
@interface ViewController ()
@property(weak, nonatomic) IBOutlet UIView *gray;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
#pragma mark - 创建复制图层
CAReplicatorLayer *repL = [CAReplicatorLayer layer];
repL.frame = _gray.bounds;
[_gray.layer addSublayer:repL];
#pragma mark - 创建一个矩形图层,设置缩放动画。
CALayer *layer = [CALayer layer];
// 设置属性
layer.transform = CATransform3DMakeScale(0, 0, 0);
layer.position = CGPointMake(_gray.bounds.size.width / 2, 20);
layer.bounds = CGRectMake(0, 0, 10, 10);
layer.backgroundColor = [UIColor grayColor].CGColor;
// 添家图层
[repL addSublayer:layer];
// 设置缩放动画
CABasicAnimation *anim = [CABasicAnimation animation];
anim.keyPath = @"transform.scale";
anim.fromValue = @1; // 动画Value值从1到0变化
anim.toValue = @0;
anim.repeatCount = MAXFLOAT;
CGFloat duration = 1;
anim.duration = duration;
[layer addAnimation:anim forKey:nil];
#pragma mark - 复制矩形图层,并且设置每个复制层的角度形变
int count = 20;
CGFloat angle = M_PI * 2 / count; // 设置子层形变角度
repL.instanceCount = count; // 设置子层总数
repL.instanceTransform = CATransform3DMakeRotation(angle, 0, 0, 1);
#pragma mark - 设置复制动画延长时间(需要保证第一个执行完毕之后,绕一圈刚好又是从第一个执行,因此需要把动画时长平均分给每个子层)
// 公式:子层动画延长时间 = 动画时长 / 子层总数
// 假设有两个图层,动画时间为1秒,延长时间就为0.5秒。当第一个动画执行到一半的时候(0.5),第二个开始执行。第二个执行完
repL.instanceDelay = duration / count;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
运行效果:
活动指示器效果.gif
4.4 粒子效果
思路:搞个画板绘制路径,自定义view
效果:随机绘制一条路径,点击开始按钮,粒子动画
代码示例:
ViewController.m
#import "DrawView.h"
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
#pragma mark - 点击开始动画
- (IBAction)startAnim:(id)sender {
DrawView *view = (DrawView *)self.view; // 强转的方式获取
[view startAnim];
}
#pragma mark - 点击重绘
- (IBAction)reDraw:(id)sender {
DrawView *view = (DrawView *)self.view;
[view reDraw];
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
DrawView.h
#import <UIKit/UIKit.h>
@interface DrawView : UIView
- (void)startAnim; // 开始动画
- (void)reDraw; // 重绘
@end
DrawView.m
#import "DrawView.h"
@interface DrawView ()
@property(nonatomic, strong) UIBezierPath *path; // 路径
@property(nonatomic, weak) CALayer *dotLayer; // 点图层
@property(nonatomic, weak) CAReplicatorLayer *repL; // 复制层
@end
@implementation DrawView
#pragma mark - 1、加载完xib调用,创建复制层
- (void)awakeFromNib {
// 创建复制层
CAReplicatorLayer *repL = [CAReplicatorLayer layer];
// 设置复制层属性
repL.frame = self.bounds;
// 添加复制层到控件的layer上
[self.layer addSublayer:repL];
_repL = repL;
}
#pragma mark - 2、调用drawRect
- (void)drawRect:(CGRect)rect {
[_path stroke];
}
#pragma mark - 3、点击屏幕
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 获取touch对象
UITouch *touch = [touches anyObject];
// 获取当前触摸点
CGPoint curP = [touch locationInView:self];
// 设置起点
[self.path moveToPoint:curP];
}
#pragma mark - 4、懒加载路径(目的是为了在不点击重绘的情况下,将所有的线添加到一条路径上去)
// 因为核心动画只能设置一个路径,因此只能创建一个路径,懒加载路径。
- (UIBezierPath *)path {
if (_path == nil) {
_path = [UIBezierPath bezierPath];
}
return _path;
}
static int _instansCount = 0;
#pragma mark - 5、手指在屏幕上移动的时候
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
// 获取touch对象
UITouch *touch = [touches anyObject];
// 获取当前触摸点
CGPoint curP = [touch locationInView:self];
// 添加线到某个点
[_path addLineToPoint:curP];
// 重绘
[self setNeedsDisplay];
_instansCount++;
}
#pragma mark - 6、点击开始动画(重写开始动画的方法)
- (void)startAnim {
_dotLayer.hidden = NO; // 开始动画的时候显示图层
// 创建帧动画
CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
// 动画属性
anim.keyPath = @"position";
anim.path = _path.CGPath;
anim.duration = 3;
anim.repeatCount = MAXFLOAT;
// 添加动画到 dotLayer 图层上
[self.dotLayer addAnimation:anim forKey:nil];
// 复制子层的数(如果复制的子层有动画,先添加动画,在复制)
_repL.instanceCount = _instansCount;
// 设置图层延时动画
_repL.instanceDelay = 0.2;
}
#pragma mark - 7、懒加载点图层
- (CALayer *)dotLayer {
if (_dotLayer == nil) {
// 创建粒子图层
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;
}
#pragma mark - 8、点击重绘(重写重绘方法)
- (void)reDraw {
_path = nil; // 清空绘图信息
[self setNeedsDisplay]; // 重绘
// 把图层移除父控件,复制层也会移除。
[_dotLayer removeFromSuperlayer];
_dotLayer = nil; // 清空点图层
_instansCount = 0; // 清空子层总数
}
@end
运行效果:
粒子效果.gif4.5 倒影效果
实现思路:
1、通常做法:用复制图层实现,搞个UIImageView展示图片,然后复制UIImageView.
注意:复制图层只能复制子层,但是UIImageView只有一个主层,并没有子层,因此不能直接复制UIImageView
正确做法:应该把UIImageView添加到一个UIView上,然后复制UIView的层,就能复制UIImageView
注意:默认A控件是B控件的子控件,那么A控件的层就是B控件的层的子层。
2、但是有问题,默认UIView的层不是复制层,我们想把UIView的层变成复制层,重写+layerClass方法。
3、倒影效果:就是就是把复制图片旋转180度,然后往下平移,最好先偏移在,在旋转。
代码示例:
RepView.m
#import "RepView.h"
@implementation RepView
// 设置控件主层的类型
+ (Class)layerClass {
return [CAReplicatorLayer class]; // 设置为复制层
}
@end
ViewController.m
#import "RepView.h"
#import "ViewController.h"
@interface ViewController ()
@property(weak, nonatomic) IBOutlet RepView *repView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
CAReplicatorLayer *layer = (CAReplicatorLayer *)_repView.layer;
layer.instanceCount = 2;
// 设置平移(向下平移)
CATransform3D transform =
CATransform3DMakeTranslation(0, _repView.bounds.size.height, 0);
// 设置旋转:绕着X轴旋转(180度)
transform = CATransform3DRotate(transform, M_PI, 1, 0, 0);
// 设置复制层的形变(将旋转和平移的数据设置在layer上)
layer.instanceTransform = transform;
// 设置颜色通道的偏移量
layer.instanceAlphaOffset = -0.2;
layer.instanceBlueOffset = -0.5;
layer.instanceGreenOffset = -0.5;
layer.instanceRedOffset = -0.5;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
运行效果:
倒影效果.png4.6 QQ粘性效果
实现思路:
步骤1、自定义大圆控件(UIButton)可以显示背景图片,和文字
步骤2、让大圆控件随着手指移动而移动(不能根据形变修改大圆的位置,只能通过center,因为全程都需要用到中心点计算)
步骤3、在拖动的时候,添加一个小圆控件在原来大圆控件的位置
- 注意这个小圆控件并不会随着手指移动而移动,因此应该添加到父控件上
- 一开始设置中心点和尺寸和大圆控件一样
- 随着大圆拖动,小圆半径不断减少,可以根据两个圆心的距离,随便生成一段比例,随着圆心距离
增加,圆心半径不断减少
- 每次小圆改变,需要重新设置小圆的尺寸和圆角半径
步骤4、粘性效果:就是在两圆之间绘制一个形变矩形,描述形变矩形路径
- 这里需要用到CAShapeLayer,可以根据一个路径,生成一个图层,展示出来。把形变图层添加到父控件并且显示在小圆图层下就OK了。因为所有计算出来的点,都是基于父控件。
注意:这里不能用绘图,因为绘图内容只要超过当前控件尺寸就不会显示,但是当前形变矩形必须显示
在控件之外
先复习下对边、邻边、斜边
- 对边:这角的对面的线
- 邻边:这个角的邻居.组成这个角的两条线
- 斜边:直角三角形三条线中最长的这条线(直角的对面的这么条)
cos Θ = 邻边 / 斜边
sin Θ =对边 / 斜边
附赠:角度、弧度互转图表
角度、弧度互转图表.png
粘性计算图:
粘性计算图.png
关于不规则矩形的处理:(使用 CAShapeLayer)
- 普通CALayer在被初始化时是需要给一个frame值的,这个frame值一般都与给定view的bounds值一致,它本身是有形状的,而且是矩形.
- CAShapeLayer在初始化时也需要给一个frame值,但是,它本身没有形状,它的形状来源于你给定的一个path(因此必须给一个path,而且,即使path不完整也会自动首尾相接)然后它会去取CGPath值
步骤5、粘性业务逻辑处理
- 当圆心距离超过100,就不需要描述形变矩形(并且把之前的形变矩形移除父层),小圆也需要隐藏。
- 没有超过100,则相反。
步骤6、手指停止拖动业务逻辑
- 判断下圆心是否超过100,超过就播放爆炸效果,添加个UIImageView在当前控件上,并且需要取消控制器view的自动布局。
- 没有超过,就还原。
代码示例:
ViewController.m
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 取消自动布局
self.view.translatesAutoresizingMaskIntoConstraints = NO;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
GooView.m(注意GooView继承自UIButton,且 SB中的UIButton的class要关联GooView)
#import "GooView.h"
@interface GooView ()
@property(nonatomic, weak) UIView *smallCircleView; // 小圆
@property(nonatomic, assign) CGFloat oriSmallRadius; // 小圆半径
@property(nonatomic, weak) CAShapeLayer *shapeLayer; // 不规则矩形
@end
@implementation GooView
#pragma mark - 不规则矩形的懒加载(两圆产生距离才需要绘制)
- (CAShapeLayer *)shapeLayer {
if (_shapeLayer == nil) {
CAShapeLayer *layer = [CAShapeLayer layer]; // 创建不规则矩形
_shapeLayer = layer;
layer.fillColor = self.backgroundColor.CGColor; // 设置不规则矩形的填充颜色
// 不规则矩形添加按钮的父控件上的layer
[self.superview.layer insertSublayer:layer below:self.layer];
}
return _shapeLayer;
}
#pragma mark - 小圆懒加载
- (UIView *)smallCircleView {
if (_smallCircleView == nil) {
UIView *view = [[UIView alloc] init]; // 创建
view.backgroundColor = self.backgroundColor; // 背景色
_smallCircleView = view;
// 小圆添加按钮的父控件上
[self.superview insertSubview:view belowSubview:self];
}
return _smallCircleView;
}
- (void)awakeFromNib {
[self setUp];
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self setUp];
}
return self;
}
#pragma mark - 初始化
- (void)setUp {
CGFloat w = self.bounds.size.width;
// 记录小圆最初始半径
_oriSmallRadius = w / 2;
// 设置最初始圆角半径
self.layer.cornerRadius = w / 2;
// 设置小圆的文字颜色
[self setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
// 设置小圆的文字字体大小
self.titleLabel.font = [UIFont systemFontOfSize:12];
// 添加手势事件(不用touchesBegan,因为会跟按钮监听事件冲突)
UIPanGestureRecognizer *pan =
[[UIPanGestureRecognizer alloc] initWithTarget:self
action:@selector(pan:)];
// 添加手势到控件上
[self addGestureRecognizer:pan];
// 设置小圆位置、尺寸和圆角半径
self.smallCircleView.center = self.center;
self.smallCircleView.bounds = self.bounds;
self.smallCircleView.layer.cornerRadius = w / 2;
}
// 最大圆心距离
#define kMaxDistance 80
// 计算两个圆心之间的距离
- (CGFloat)circleCenterDistanceWithBigCircleCenter:(CGPoint)bigCircleCenter
smallCircleCenter:(CGPoint)smallCircleCenter {
CGFloat offsetX = bigCircleCenter.x - smallCircleCenter.x; // x2-x1
CGFloat offsetY = bigCircleCenter.y - smallCircleCenter.y; // y2-y1
// 开平方得到两个圆心之间的距离
return sqrt(offsetX * offsetX + offsetY * offsetY);
}
// 描述两圆之间一条矩形路径
- (UIBezierPath *)pathWithBigCirCleView:(UIView *)bigCirCleView
smallCirCleView:(UIView *)smallCirCleView {
// 大圆的圆心、x、y、半径
CGPoint bigCenter = bigCirCleView.center;
CGFloat x2 = bigCenter.x;
CGFloat y2 = bigCenter.y;
CGFloat r2 = bigCirCleView.bounds.size.width / 2;
// 小圆的圆心、x、y、半径
CGPoint smallCenter = smallCirCleView.center;
CGFloat x1 = smallCenter.x;
CGFloat y1 = smallCenter.y;
CGFloat r1 = smallCirCleView.bounds.size.width / 2;
// 获取圆心距离
CGFloat d = [self circleCenterDistanceWithBigCircleCenter:bigCenter
smallCircleCenter:smallCenter];
CGFloat sinθ = (x2 - x1) / d;
CGFloat cosθ = (y2 - y1) / d;
// 坐标系是基于父控件计算的
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 / 2 * sinθ, pointA.y + d / 2 * cosθ);
CGPoint pointP =
CGPointMake(pointB.x + d / 2 * sinθ, pointB.y + d / 2 * cosθ);
// 1、创建一个路径对象
UIBezierPath *path = [UIBezierPath bezierPath];
// 2、一个封闭路径(A-B-C-D-A)
[path moveToPoint:pointA]; // 起点:A
[path addLineToPoint:pointB]; // 添加线AB
[path addQuadCurveToPoint:pointC controlPoint:pointP]; // 绘制BC曲线
[path addLineToPoint:pointD]; // 添加线CD
[path addQuadCurveToPoint:pointA controlPoint:pointO]; // 绘制DA曲线
// 3、返回path
return path;
}
- (void)pan:(UIPanGestureRecognizer *)pan {
#warning 移动控件位置
// 获取手指偏移量
CGPoint transP = [pan translationInView:self];
// 修改按钮的形变,并不会修改中心点,因此要用下面的方法
// self.transform = CGAffineTransformTranslate(self.transform, transP.x,
// transP.y);
// 通过修改center的xy,用以修改按钮的形变
CGPoint center = self.center;
center.x += transP.x;
center.y += transP.y;
// 将修改后的参数赋值
self.center = center;
// 复位
[pan setTranslation:CGPointZero inView:self];
#warning 设置小圆半径(小圆半径随着大小两个圆的圆心的距离不断增加而减小)
// 计算圆心距离
CGFloat d = [self
circleCenterDistanceWithBigCircleCenter:self.center
smallCircleCenter:self.smallCircleView.center];
// 计算小圆的半径(随着圆心距的不断变化而变化)
CGFloat smallRadius = _oriSmallRadius - d / 10;
// 设置小圆的尺寸(随着小圆半径的不断变化而变化)
self.smallCircleView.bounds =
CGRectMake(0, 0, smallRadius * 2, smallRadius * 2);
// 设置小圆的圆角半径(随着小圆半径的不断变化而变化)
self.smallCircleView.layer.cornerRadius = smallRadius;
#warning 绘制不规则矩形
/*
遇到的问题:不能通过绘图,因为绘图只能在当前控件上画,超出部分不会显示
解决方法:展示不规则矩形,通过不规则矩形路径生成一个图层
*/
// 当圆心距离大于最大圆心距离的时候,可以拖出来
if (d > kMaxDistance) {
self.smallCircleView.hidden = YES; // 隐藏小圆
[self.shapeLayer removeFromSuperlayer]; // 移除不规则的矩形
self.shapeLayer = nil; // 清空不规则的矩形的内容
} else if (d > 0 && self.smallCircleView.hidden == NO) {
// 当有圆心距离,并且圆心距离不大的时候,展示不规则矩形
self.shapeLayer.path =
[self pathWithBigCirCleView:self smallCirCleView:self.smallCircleView]
.CGPath;
}
#warning 手指抬起的时候,还原
if (pan.state == UIGestureRecognizerStateEnded) {
// 当圆心距离大于最大圆心距离的时候,展示gif动画
if (d > kMaxDistance) {
// 创建imageView,设置Frame
UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.bounds];
// for循环展示图片
NSMutableArray *arrM = [NSMutableArray array];
for (int i = 1; i < 9; i++) {
UIImage *image =
[UIImage imageNamed:[NSString stringWithFormat:@"%d", i]];
[arrM addObject:image];
}
imageView.animationImages = arrM; // 将图片添加到数组
imageView.animationRepeatCount = 1; // 设置动画次数
imageView.animationDuration = 0.5; // 设置动画时间
[imageView startAnimating]; // 开始动画
[self addSubview:imageView]; // 添加图片
// 延迟0.4s,移除控件
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
[self removeFromSuperview];
});
} else { // 当圆心距离小于最大圆心距离的时候
[self.shapeLayer removeFromSuperlayer]; // 移除不规则矩形
self.shapeLayer = nil; // 清空不规则的矩形的内容
// 当抬起手指的时候,还原位置、显示小圆
[UIView animateWithDuration:0.5
delay:0
usingSpringWithDamping:0.2
initialSpringVelocity:0
options:UIViewAnimationOptionCurveLinear
animations:^{
self.center = self.smallCircleView.center; // 设置大圆中心点位置
}
completion:^(BOOL finished) {
self.smallCircleView.hidden = NO; // 显示小圆
}];
}
}
}
@end
运行效果:
QQ粘性效果.gif本文源码 Demo 详见 Github
https://github.com/shorfng/iOS_6.0_Graphics_and_multimedia.git
作者:蓝田(Loto)
出处: 简书
如果你觉得本篇文章对你有所帮助,请点击文章末尾下方“喜欢”
如有疑问,请通过以下方式交流:
① 评论区回复
② 微信(加好友请注明“简书+称呼”)
③发送邮件
至 shorfng@126.com
本文版权归作者和本网站共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
网友评论
更多 iOS 开发相关文章请关注: http://www.jianshu.com/collection/e84a7722d673