再过几天就要放假咯,前两天也是够闹心的了,上线被苹果拒了好几次,心都拔凉拔凉的了,好吧,废话说完了,下面直接入主题:
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,大神们不喜勿喷,有什么好的实现思路 欢迎探讨 嘎嘎.... 下篇文章就单独来讲按钮的点击事件了,今天就写到这了,下篇再见哦..