再過幾天就要放假咯,前兩天也是夠鬧心的了,上線被蘋果拒了好幾次,心都拔涼拔涼的了,好吧,廢話說完了,下面直接入主題:
Demo地址
https://github.com/wxh794708907/YJYYCircleMenu.git
效果圖
效果圖大概就是這樣子,這其實也是公司項目中的一個需求,這里我單獨拿出來講
需求分析:
1 .首先這是一個菜單,這個菜單包含了5個元素,暫且叫它5個item吧,其實真正實現的時候是用的按鈕,
2 .它是有動畫的,一直在繞著中心來旋轉,而且轉動一會后會短暫的停大概1s的時間
3 .它其實是有點擊事件的(但是這篇文章我先不講,下篇再拿來說)
具體實現
1.UI實現,5個按鈕圍繞中心點進行布局,這個時候你可能會想,按鈕的frame該怎么去設置,其實剛開始做的時候我也一直在疑惑,我到底該怎么去布局,怎么去設置frame,后來茅塞頓開,其實根本就不需要考慮frame,只需要考慮寬高就行,具體待會你看代碼就明白了。
2.動畫分析:可能每個人都有自己的思路去實現這個動畫,有些人可能想的是將所有的6個按鈕都布局好之后 通過給整個菜單來添加關鍵幀動畫, 而我的思路是通過給每一個按鈕去添加一個動畫來達到效果,這也就是為什么我不需要考慮x和y的原因,因為動畫是基于layer來做的。
3.點擊事件暫且不在本篇文章中來說 敬請期待下篇。
代碼實踐
控制器中代碼:
//
// ViewController.m
// YJYYCircleMenu
//
// Created by 遇見遠洋 on 17/1/2.
// Copyright ? 2017年 遇見遠洋. All rights reserved.
//
#import "ViewController.h"
#import "YJYYCycleMenu.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
YJYYCycleMenu * menu = [YJYYCycleMenu cycleMenuWithTitles:@[@"讀新聞",@"導航",@"訂咖啡",@"查資訊",@"萌萌噠"] menuWidth:60 center:self.view.center radius:100];
[self.view addSubview:menu];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
.h:
//
// YJYYCycleMenu.h
// YJYYCircleMenu
//
// Created by 遇見遠洋 on 17/1/2.
// Copyright ? 2017年 遇見遠洋. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface YJYYCycleMenu : UIView
/**
快速實例化菜單
@param titles 文本數組 數組個數多少就是多少個menu
@param menuWidth 菜單item的高度 最好寬高相等
@param center 中心點 圍繞那個點抓圈
@param radius 半徑 選裝半徑
*/
+ (instancetype)cycleMenuWithTitles:(NSArray<NSString *> *)titles menuWidth:(CGFloat)menuWidth center:(CGPoint)center radius:(CGFloat)radius;
@end
.m
//
// YJYYCycleMenu.m
// YJYYCircleMenu
//
// Created by 遇見遠洋 on 17/1/2.
// Copyright ? 2017年 遇見遠洋. All rights reserved.
//
#import "YJYYCycleMenu.h"
@interface YJYYCycleMenu ()
/**<按鈕數組*/
@property (strong,nonatomic)NSMutableArray *btnsArray;
/**<標題數組*/
@property (strong,nonatomic)NSArray *titiles;
/**<開始角度*/
@property (strong,nonatomic)NSMutableArray *startAngle;
/**<結束角度*/
@property (strong,nonatomic)NSMutableArray *endAngle;
/** 半徑 */
@property(nonatomic,assign) CGFloat radius;
/** 中心點 */
@property(nonatomic,assign) CGPoint centerPoint;
/** 按鈕寬高 */
@property(nonatomic,assign) CGFloat itemHW;
@end
@implementation YJYYCycleMenu
+ (instancetype)cycleMenuWithTitles:(NSArray<NSString *> *)titles menuWidth:(CGFloat)menuWidth center:(CGPoint)center radius:(CGFloat)radius {
YJYYCycleMenu * menu = [[YJYYCycleMenu alloc]init];
menu.titiles = titles;
menu.radius = radius;
menu.centerPoint = center;
menu.itemHW = menuWidth;
[menu startAnimation];
return menu;
}
- (NSMutableArray *)btnsArray {
if (!_btnsArray) {
_btnsArray = [NSMutableArray arrayWithCapacity:self.titiles.count];
for (int i = 0; i < self.titiles.count; i++) {
UIButton * circleBtn = [[UIButton alloc]initWithFrame:CGRectMake(0, 0, _itemHW, _itemHW)];
[circleBtn setTitle:self.titiles[i] forState:UIControlStateNormal];
circleBtn.backgroundColor = [UIColor colorWithWhite:0 alpha:0.3f];
circleBtn.layer.cornerRadius = _itemHW*0.5;
circleBtn.layer.masksToBounds = YES;
[circleBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
circleBtn.titleLabel.font = [UIFont systemFontOfSize:14];
[self addSubview:circleBtn];
[_btnsArray addObject:circleBtn];
}
}
return _btnsArray;
}
- (NSArray *)titiles {
if (!_titiles) {
_titiles = [NSArray array];
}
return _titiles;
}
- (NSMutableArray *)startAngle {
if (!_startAngle) {
_startAngle = [NSMutableArray array];
for (int i = 0; i<self.titiles.count;i++ ) {
[_startAngle addObject:@(2*M_PI/self.titiles.count*i)];
}
NSLog(@"%@",_startAngle);
}
return _startAngle;
}
- (NSMutableArray *)endAngle {
if (!_endAngle) {
_endAngle = [NSMutableArray array];
for (int i = 0; i<self.titiles.count;i++ ) {
CGFloat angle = 2*M_PI - (2*M_PI/self.titiles.count*i);
if (angle + [self.startAngle[i] floatValue] != 2*M_PI) {
angle = -angle;
NSLog(@"%f========%f",[self.startAngle[i] floatValue],angle);
}
[_endAngle addObject:@(angle)];
}
}
return _endAngle;
}
#pragma mark - 事件處理
#pragma mark -
/**
* 開始轉圈動畫
*/
- (void)startAnimation {
[self.btnsArray enumerateObjectsUsingBlock:^(UIButton * circleBtn, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"%@",circleBtn.currentTitle);
[self keyFrameWithStart:[self.startAngle[idx] floatValue] endAngle:[self.endAngle[idx] floatValue] animationView:circleBtn];
}];
}
/**
* 幀動畫封裝
*
* @param startAngle 開始角度
* @param endAngle 結束角度
* @param animationView 動畫view
*/
- (void)keyFrameWithStart:(CGFloat)startAngle endAngle:(CGFloat)endAngle animationView:(UIView *)animationView{
CAKeyframeAnimation * keyFrame = [CAKeyframeAnimation animationWithKeyPath:@"position"];
//創建一條路徑
UIBezierPath * bezierPath = [UIBezierPath bezierPathWithArcCenter:self.centerPoint radius:self.radius startAngle:startAngle endAngle:endAngle clockwise:YES];
keyFrame.path = bezierPath.CGPath;
//1.2設置動畫執行完畢后,不刪除動畫
keyFrame.removedOnCompletion=NO;
//1.3設置保存動畫的最新狀態
keyFrame.fillMode=kCAFillModeForwards;
//1.4設置動畫執行的時間
keyFrame.duration=15.0;
keyFrame.repeatCount = NSIntegerMax;
//1.5設置動畫的節奏
keyFrame.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
//2.添加核心動畫
[animationView.layer addAnimation:keyFrame forKey:nil];
}
@end
總結分析
應該會有不少人可能沒那么明白原理,為什么沒有設置按鈕的X和Y,出來就可以讓按鈕圍繞中心點布局好了,其實你只需要關注下面的這個方法就好了,這個方式是核心:
- (void)keyFrameWithStart:(CGFloat)startAngle endAngle:(CGFloat)endAngle animationView:(UIView *)animationView;
由于動畫是基于layer的,所以你只要有寬高,開始動畫的時候設置好開始角度和結束角度,這樣就可以達到我們的需求了,這里再說明一下的是,其實角度和結束角度我是怎么考慮的,這里我也是考慮了好久的地方,但是現在還是可能會有問題的,所以你如果需要運用到實際項目中的時候 如果出現問題的話,一般都是因為結束角度沒有設置對導致的
角度計算
1.起始角度是由按鈕個數來決定的,最主要是計算沒個按鈕之間的角度差 通過" 2*M_PI/self.titiles.count * i "這個來計算角度差值,
2.結束角度
就比較麻煩了,如果單純用2π - 起始角度會出現問題,還需要考慮角度負數的問題,具體你可以看下endAngle的懶加載。
下篇預告
基本到這也就差不多實現了,可能我的思路比較low,大神們不喜勿噴,有什么好的實現思路 歡迎探討 嘎嘎.... 下篇文章就單獨來講按鈕的點擊事件了,今天就寫到這了,下篇再見哦..