前言
之前的一个项目,没有使用TabBar,而是选择用圆环作为用户点击的菜单,加上深蓝的冷色调,APP看着还蛮高大上,先看下效果图:
demo.gif
效果图分析
1.类似地球的是一张Image,其余的都是Button,每个button都是可以点击,点击回调至VC你可以处理一些事.
2.项目中的需求是旋转圆盘后自动会停止,停止时布局还是和最开始的一样(只不过可能不是原来的按钮),点击某个按钮,这个按钮就在最上方的中间处.
3.点击中间的按钮,圆盘能缩放,再次点击时显示.
实现
.h文件
这个文件定义一个回调block以及初始化view和传入数据的方法
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
typedef void(^clickSomeOne)(NSString *str);
@interface LWCustomCircleView : UIView
//在想要回传的界面中定义,block必须用copy来修饰
@property (nonatomic, copy) clickSomeOne clickButton;
- (id)initWithFrame:(CGRect)frame andImage:(UIImage *)image;
- (void)addSubviewWithSubView:(NSArray *)imageArray andTitle:(NSArray *)titleArray andSize:(CGSize)size andCenterImage:(UIImage *)centerImage;
@end
.m文件
定义的属性,注释的很清楚
@interface LWCustomCircleView()
/** 播放音效 */
@property (nonatomic, assign) SystemSoundID soundID;
/** 减速定时器 */
@property (nonatomic, strong) NSTimer *timer;
/** 子试图数量 */
@property (nonatomic, assign) CGFloat numOfSubView;
/** 圆形图 */
@property (nonatomic, strong) UIImageView *circleImage;
/** 减速定时器 */
@property (nonatomic, strong) UIImageView *arrowImage;
/** 子试图数组 */
@property (nonatomic, strong) NSMutableArray *subViewArray;
/** 按钮数组 */
@property (nonatomic, strong) NSMutableArray *buttonArray;
/** 第一触碰点 */
@property (nonatomic, assign) CGPoint beginPoint;
/** 第二触碰点 */
@property (nonatomic, assign) CGPoint movePoint;
/** 正在跑 */
@property (nonatomic, assign) BOOL isPlaying;
/** 滑动时间 */
@property (nonatomic, strong) NSDate *date;
/** 开始转动时间 */
@property (nonatomic, strong) NSDate *startTouchDate;
/** 减速计数 */
@property (nonatomic, assign) NSInteger decelerTime;
/** 子试图大小 */
@property (nonatomic, assign) CGSize subViewSize;
/** 滑动手势 */
@property (nonatomic, strong) UIPanGestureRecognizer *panGes;
/** 转动的角度 */
@property (nonatomic, assign) double mStartAngle;
/** 转动临界速度,超过此速度便是快速滑动,手指离开仍会转动 */
@property (nonatomic, assign) int mFlingableValue;
/** 半径 */
@property (nonatomic, assign) int mRadius;
/** 检测按下到抬起时旋转的角度 */
@property (nonatomic, assign) float mTmpAngle;
/** 顺时针转动的定时器 */
@property (nonatomic, strong) NSTimer *flowtime;
/**逆时针转动的定时器 */
@property (nonatomic, strong) NSTimer *reverseTime;
@property (nonatomic, assign) float anglePerSecond;
/** 转动速度 */
@property (nonatomic, assign) float speed;
@end
初始化视图和一些属性
- (id)initWithFrame:(CGRect)frame andImage:(UIImage *)image
{
if (self = [super initWithFrame:frame]) {
self.decelerTime = 0;
self.subViewArray = [[NSMutableArray alloc] init];
self.circleImage = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
if (image == nil) {
self.circleImage.layer.cornerRadius = frame.size.width / 2;
}else {
self.circleImage.image = image;
self.circleImage.backgroundColor = [UIColor clearColor];
}
self.mRadius = frame.size.width / 2;
self.mStartAngle = M_PI_2 * 3;
self.mFlingableValue = 300;
self.isPlaying = false;
self.circleImage.userInteractionEnabled = YES;
[self addSubview:self.circleImage];
}
return self;
}
按钮的布局
给按钮添加图片和文字,中间的按钮只添加图片,添加滑动的手势和点击按钮时的重新布局
- (void)addSubviewWithSubView:(NSArray *)imageArray andTitle:(NSArray *)titleArray andSize:(CGSize)size andCenterImage:(UIImage *)centerImage
{
self.subViewSize = size;
self.numOfSubView = (CGFloat)titleArray.count;
self.buttonArray = [[NSMutableArray alloc] init];
for (NSInteger i = 0; i < self.numOfSubView; i++) {
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(20*Width, 20*Width, size.width*Width, size.height*Height)];
[button setImage:imageArray[i] forState:UIControlStateNormal];
//设置image在button上的位置(上top,左left,下bottom,右right)这里可以写负值,对上写-5,那么image就象上移动5个像素
[button setTitle:titleArray[i] forState:UIControlStateNormal];
[button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
button.titleLabel.font = [UIFont systemFontOfSize:16*Width];
button.titleLabel.textAlignment = NSTextAlignmentCenter;
button.imageEdgeInsets = UIEdgeInsetsMake(8,8,8,button.titleLabel.bounds.size.width);
button.titleEdgeInsets = UIEdgeInsetsMake(70, -button.imageView.bounds.size.width-40, 0, 0);
[button addTarget:self action:@selector(clickBtn:) forControlEvents:UIControlEventTouchUpInside];
button.tag = 100 + i;
[self.buttonArray addObject:button];
[self.subViewArray addObject:button];
[self.circleImage addSubview:button];
}
//按钮布局
[self layoutButton];
//中间按钮
UIButton *centerBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width/3.0, self.frame.size.height/3.0)];
centerBtn.tag = 100 + self.numOfSubView + 1;
centerBtn.layer.cornerRadius = self.frame.size.width / 6.0;
[centerBtn setImage:centerImage forState:UIControlStateNormal];
centerBtn.center = CGPointMake(self.frame.size.width/2.0, self.frame.size.height / 2.0);
[centerBtn addTarget:self action:@selector(clickBtnCenter:) forControlEvents:UIControlEventTouchUpInside];
[self.subViewArray addObject:centerBtn];
[self.circleImage addSubview:centerBtn];
//转动手势
self.panGes = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(zhuanPgr:)];
[self.circleImage addGestureRecognizer:self.panGes];
//加点击效果
for (NSInteger i=0; i<self.subViewArray.count; i++) {
UIButton *button= self.subViewArray[i];
[button addTarget:self action:@selector(subViewOut:) forControlEvents:UIControlEventTouchUpInside];
}
}
按钮的布局
按钮分布在圆环上,这里用到正弦和余弦函数
//按钮布局
- (void)layoutButton
{
/**
M_PI pi 3.14159265358979323846
M_PI_2 pi/2 1.57079632679489661923
M_PI_4 pi/4 0.785398163397448309616
*/
/**
sin((i/self.numOfSubView) * M_PI * 2 + self.mStartAngle):布局滑动时按钮均匀分布在圆的各个位置
(self.frame.size.width/2 - self.subViewSize.width/2 - 20):让按钮布局在圆环间
*/
for (NSInteger i = 0; i < self.numOfSubView; i++) {
CGFloat yy = 195 + sin((i/self.numOfSubView) * M_PI * 2 + self.mStartAngle) * (self.frame.size.width/2 - self.subViewSize.width/2 - 30);
CGFloat xx = 195 + cos((i/self.numOfSubView) * M_PI * 2 + self.mStartAngle) * (self.frame.size.width/2 - self.subViewSize.width/2 - 30);
UIButton *button = [self.buttonArray objectAtIndex:i];
button.center = CGPointMake(xx, yy);
}
}
滑动手势的处理
代码中的注释很详细
-(void)zhuanPgr:(UIPanGestureRecognizer *)pgr
{
if (pgr.state == UIGestureRecognizerStateBegan) {
self.mTmpAngle = 0;
self.beginPoint = [pgr locationInView:self];
self.startTouchDate = [NSDate date];
}else if (pgr.state == UIGestureRecognizerStateChanged) {
float StartAngleLast = self.mStartAngle;
self.movePoint = [pgr locationInView:self];
float start = [self getAngle:self.beginPoint];
float end = [self getAngle:self.movePoint];
if ([self getQuadrant:self.movePoint] == 1 || [self getQuadrant:self.movePoint] == 4) {
//一、四象限
self.mStartAngle += end - start;
self.mTmpAngle += end - start;
}else {
//二三象限
self.mStartAngle += start - end;
self.mTmpAngle += start - end;
}
[self layoutButton];
self.beginPoint = self.movePoint;
self.speed = self.mStartAngle - StartAngleLast;
NSTimeInterval time = [[NSDate date] timeIntervalSinceDate:self.startTouchDate];
self.anglePerSecond = self.mTmpAngle * 50 / time;
}else if (pgr.state == UIGestureRecognizerStateEnded) {
//计算每秒转动的角度
NSTimeInterval time = [[NSDate date] timeIntervalSinceDate:self.startTouchDate];
self.anglePerSecond = self.mTmpAngle * 50 / time;
if (self.anglePerSecond > 0) {
//求绝对值的函数,顺时针转动
if (fabs(self.anglePerSecond) > self.mFlingableValue && !self.isPlaying) {
self.isPlaying = true;
self.flowtime = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(flowAction) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.flowtime forMode:NSRunLoopCommonModes];
}
}else {
//逆时针转动
if (-fabsf(self.anglePerSecond) < -self.mFlingableValue && !self.isPlaying) {
self.isPlaying = true;
self.reverseTime = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(reverseAction) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.reverseTime forMode:NSRunLoopCommonModes];
}
}
if (self.isPlaying == false){
if (self.mStartAngle >= 0){
//fmod是取两个数相除的余数
if (fmod(self.mStartAngle, M_PI*2) > M_PI_2*2.32+M_PI_2*0.32 && fmod(self.mStartAngle, M_PI*2) <= M_PI_2*3+M_PI_2*0.32) {
self.mStartAngle = M_PI_2*3;
[self layoutButton];
self.clickButton(@"100");
}
if (fmod(self.mStartAngle, M_PI*2) > M_PI_2*1.68+M_PI_2*0.32 && fmod(self.mStartAngle, M_PI*2) <= M_PI_2*2.32+M_PI_2*0.32) {
self.mStartAngle = M_PI_2*2.32;
[self layoutButton];
self.clickButton(@"101");
}
if (fmod(self.mStartAngle, M_PI*2) > M_PI_2*1+M_PI_2*0.32 && fmod(self.mStartAngle, M_PI*2)<=M_PI_2*1.68+M_PI_2*0.32) {
self.mStartAngle = M_PI_2*1.68;
[self layoutButton];
self.clickButton(@"102");
}
if (fmod(self.mStartAngle, M_PI*2) > M_PI_2*0.32+M_PI_2*0.32 && fmod(self.mStartAngle, M_PI*2)<=M_PI_2*1+M_PI_2*0.32) {
self.mStartAngle = M_PI_2*1;
[self layoutButton];
self.clickButton(@"103");
}
if (fmod(self.mStartAngle, M_PI*2) > 0 && fmod(self.mStartAngle, M_PI*2) <= M_PI_2*0.32+M_PI_2*0.32) {
self.mStartAngle = M_PI_2*0.32;
[self layoutButton];
self.clickButton(@"104");
}
if (fmod(self.mStartAngle, M_PI*2)-M_PI*2 <= 0 && fmod(self.mStartAngle, M_PI*2)-M_PI*2 >= -M_PI_2*0.32*2) {
// mStartAngle = M_PI_2*3.66;
self.mStartAngle = -M_PI_2*0.32;
[self layoutButton];
self.clickButton(@"105");
}
}else {
if (fmod(self.mStartAngle, M_PI*2)+M_PI*2 > M_PI_2*2.32+M_PI_2*0.32 && fmod(self.mStartAngle, M_PI*2)+M_PI*2<=M_PI_2*3+M_PI_2*0.32) {
self.mStartAngle = M_PI_2*3;
[self layoutButton];
self.clickButton(@"100");
}
if (fmod(self.mStartAngle, M_PI*2)+M_PI*2 > M_PI_2*1.68+M_PI_2*0.32 && fmod(self.mStartAngle, M_PI*2)+M_PI*2<=M_PI_2*2.32+M_PI_2*0.32) {
self.mStartAngle = M_PI_2*2.32;
[self layoutButton];
self.clickButton(@"101");
}
if (fmod(self.mStartAngle, M_PI*2)+M_PI*2 > M_PI_2*1+M_PI_2*0.32 && fmod(self.mStartAngle, M_PI*2)+M_PI*2<=M_PI_2*1.68+M_PI_2*0.32) {
self.mStartAngle = M_PI_2*1.68;
[self layoutButton];
self.clickButton(@"102");
}
if (fmod(self.mStartAngle, M_PI*2)+M_PI*2 > M_PI_2*0.32+M_PI_2*0.32 && fmod(self.mStartAngle, M_PI*2)+M_PI*2<=M_PI_2*1+M_PI_2*0.32) {
self.mStartAngle = M_PI_2*1;
[self layoutButton];
self.clickButton(@"103");
}
if (fmod(self.mStartAngle, M_PI*2)+M_PI*2 > 0+M_PI_2*0.32 && fmod(self.mStartAngle, M_PI*2)+M_PI*2<=M_PI_2*0.32+M_PI_2*0.32) {
self.mStartAngle = M_PI_2*0.32;
[self layoutButton];
self.clickButton(@"104");
}
if (fmod(self.mStartAngle, M_PI*2) <= 0 && fmod(self.mStartAngle, M_PI*2) >= -M_PI_2*0.32*2) {
// mStartAngle = M_PI_2*3.66;
self.mStartAngle = -M_PI_2*0.32;
[self layoutButton];
self.clickButton(@"105");
}
}
}
}
}
//获取当前点弧度
/**
hypot:计算直角三角形的斜边长
*/
-(float)getAngle:(CGPoint)point {
double x = point.x - self.mRadius;
double y = point.y - self.mRadius;
return (float) (asin(y / hypot(x, y)));
}
/** 根据当前位置计算象限 */
-(int) getQuadrant:(CGPoint) point {
int tmpX = (int) (point.x - self.mRadius);
int tmpY = (int) (point.y - self.mRadius);
if (tmpX >= 0) {
return tmpY >= 0 ? 1 : 4;
} else {
return tmpY >= 0 ? 2 : 3;
}
}
顺时针和逆时针转动的定时器处理
速度减为零时,按钮重新布局,并销毁定时器
- (void)flowAction
{
if (self.speed < 0.1) {
[UIView animateWithDuration:2 animations:^{
} completion:^(BOOL finished) {
}];
self.isPlaying = false;
[self.flowtime invalidate];
self.flowtime = nil;
//停止转动时,布局好按钮位置,不需要这样的效果可注释
if (fmod(self.mStartAngle, M_PI*2) > M_PI_2*2.32+M_PI_2*0.32 && fmod(self.mStartAngle, M_PI*2) <= M_PI_2*3+M_PI_2*0.32) {
self.mStartAngle = M_PI_2*3;
[self layoutButton];
self.clickButton(@"100");
}
if (fmod(self.mStartAngle, M_PI*2) > M_PI_2*1.68+M_PI_2*0.32 && fmod(self.mStartAngle, M_PI*2) <= M_PI_2*2.32+M_PI_2*0.32) {
self.mStartAngle = M_PI_2*2.32;
[self layoutButton];
self.clickButton(@"101");
}
if (fmod(self.mStartAngle, M_PI*2) > M_PI_2*1+M_PI_2*0.32 && fmod(self.mStartAngle, M_PI*2) <= M_PI_2*1.68+M_PI_2*0.32) {
self.mStartAngle = M_PI_2*1.68;
[self layoutButton];
self.clickButton(@"102");
}
if (fmod(self.mStartAngle, M_PI*2) > M_PI_2*0.32+M_PI_2*0.32 && fmod(self.mStartAngle, M_PI*2) <= M_PI_2*1+M_PI_2*0.32) {
self.mStartAngle = M_PI_2*1;
[self layoutButton];
self.clickButton(@"103");
}
if (fmod(self.mStartAngle, M_PI*2) > 0 && fmod(self.mStartAngle, M_PI*2) <= M_PI_2*0.32+M_PI_2*0.32) {
self.mStartAngle = M_PI_2*0.32;
[self layoutButton];
self.clickButton(@"104");
}
if (fmod(self.mStartAngle, M_PI*2)-M_PI*2 <= 0 && fmod(self.mStartAngle, M_PI*2)-M_PI*2 >= -M_PI_2*0.32*2) {
// mStartAngle = M_PI_2*3.66;
self.mStartAngle = -M_PI_2*0.32;
[self layoutButton];
self.clickButton(@"105");
}
return;
}
// 不断改变mStartAngle,让其滚动,/30为了避免滚动太快
self.mStartAngle += self.speed ;
self.speed = self.speed/1.1;
// 逐渐减小这个值
// anglePerSecond /= 1.1;
[self layoutButton];
}
- (void)reverseAction
{
if (self.speed > -0.1) {
[UIView animateWithDuration:2 animations:^{
} completion:^(BOOL finished) {
}];
self.isPlaying = false;
[self.reverseTime invalidate];
self.reverseTime = nil;
//停止转动时,布局好按钮位置,不需要这样的效果可注释
if ((fmod(self.mStartAngle, M_PI*2)+M_PI*2 > M_PI_2*2.32+M_PI_2*0.32 && fmod(self.mStartAngle, M_PI*2)+M_PI*2 <= M_PI_2*3+M_PI_2*0.32) || (fmod(self.mStartAngle, M_PI*2)>M_PI_2*2.32+M_PI_2*0.32 && fmod(self.mStartAngle, M_PI*2)<=M_PI_2*3+M_PI_2*0.32)) {
self.mStartAngle = M_PI_2*3;
[self layoutButton];
self.clickButton(@"100");
}
if ((fmod(self.mStartAngle, M_PI*2)+M_PI*2>M_PI_2*1.68+M_PI_2*0.32 && fmod(self.mStartAngle, M_PI*2)+M_PI*2<=M_PI_2*2.32+M_PI_2*0.32)||(fmod(self.mStartAngle, M_PI*2)>M_PI_2*1.68+M_PI_2*0.32 && fmod(self.mStartAngle, M_PI*2)<=M_PI_2*2.32+M_PI_2*0.32)) {
self.mStartAngle = M_PI_2*2.32;
[self layoutButton];
self.clickButton(@"101");
}
if ((fmod(self.mStartAngle, M_PI*2)+M_PI*2>M_PI_2*1+M_PI_2*0.32 && fmod(self.mStartAngle, M_PI*2)+M_PI*2<=M_PI_2*1.68+M_PI_2*0.32)||(fmod(self.mStartAngle, M_PI*2)>M_PI_2*1+M_PI_2*0.32&&fmod(self.mStartAngle, M_PI*2)<=M_PI_2*1.68+M_PI_2*0.32)) {
self.mStartAngle = M_PI_2*1.68;
[self layoutButton];
self.clickButton(@"102");
}
if ((fmod(self.mStartAngle, M_PI*2)+M_PI*2 > M_PI_2*0.32+M_PI_2*0.32 && fmod(self.mStartAngle, M_PI*2)+M_PI*2 <= M_PI_2*1+M_PI_2*0.32) || (fmod(self.mStartAngle, M_PI*2) > M_PI_2*0.32+M_PI_2*0.32&&fmod(self.mStartAngle, M_PI*2) <= M_PI_2*1+M_PI_2*0.32)) {
self.mStartAngle = M_PI_2*1;
[self layoutButton];
self.clickButton(@"103");
}
if ((fmod(self.mStartAngle, M_PI*2)+M_PI*2>0 && fmod(self.mStartAngle, M_PI*2)+M_PI*2 <= M_PI_2*0.32+M_PI_2*0.32)||(fmod(self.mStartAngle, M_PI*2)>0&&fmod(self.mStartAngle, M_PI*2)<=M_PI_2*0.32+M_PI_2*0.32)) {
self.mStartAngle = M_PI_2*0.32;
[self layoutButton];
self.clickButton(@"104");
}
if (fmod(self.mStartAngle, M_PI*2)<=0 && fmod(self.mStartAngle, M_PI*2) >= -M_PI_2*0.32*2) {
// mStartAngle = M_PI_2*3.66;
self.mStartAngle = -M_PI_2*0.32;
[self layoutButton];
self.clickButton(@"105");
}
return;
}
self.mStartAngle += self.speed;
self.speed = self.speed/1.1;
[self layoutButton];
}
demo已上传至GitHub:https://github.com/CodeFeel/circleMenu
网友评论