美文网首页
iOS 圆环菜单

iOS 圆环菜单

作者: 迷恋代码 | 来源:发表于2018-01-17 16:45 被阅读484次

前言

之前的一个项目,没有使用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

相关文章

  • iOS 圆环菜单

    前言 之前的一个项目,没有使用TabBar,而是选择用圆环作为用户点击的菜单,加上深蓝的冷色调,APP看着还蛮高大...

  • 第 16 章:圆环菜单

    原文链接作者:C4 开源项目译者:Crystal Sun全部章节请关注此文集C4教程翻译校对后的内容请看这里 我们...

  • iOS 绘制颜色渐变圆环 --- 值得一看

    iOS 绘制颜色渐变圆环 本文主要介绍一种绘制颜色渐变的进度圆环. 先上效果图: 实现思路: CAShapeLay...

  • React-Native ART绘制圆形进度条

    思路 绘制两个圆环叠加,改变上部圆环的填充色~ 技术点:需要ART,iOS和Android自行导入 Wedge.j...

  • iOS开发之UIMenu

    iOS 13 引入 UIMenu 在 iOS 13 中引入,可以很方便的创建程序菜单和上下文菜单。 参考:iOS开...

  • ios 圆环渐变

    1需求 需要绘制一个下图的圆环,可能圆环看上有点丑,但是加了渐变,对于新手朋友来说,可能也没那么简单。 2分析需求...

  • iOS 简单圆环

  • IOS头像圆环效果

    效果图

  • 侧边栏

    iOS 三种侧滑菜单效果对比详解

  • iOS 三种侧滑菜单效果对比详解

    侧滑菜单 ios分类:iOS界面布局 DDMenu(传统侧滑效果)下载地址:https://github.com/...

网友评论

      本文标题:iOS 圆环菜单

      本文链接:https://www.haomeiwen.com/subject/idrroxtx.html