轉(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)的位置