转圈菜单栏的实现《一》

作者: 再见远洋 | 来源:发表于2017-01-17 18:40 被阅读107次

    再过几天就要放假咯,前两天也是够闹心的了,上线被苹果拒了好几次,心都拔凉拔凉的了,好吧,废话说完了,下面直接入主题:

    Demo地址

    https://github.com/wxh794708907/YJYYCircleMenu.git

    效果图
    YJYYCircleMenu.gif

    效果图大概就是这样子,这其实也是公司项目中的一个需求,这里我单独拿出来讲

    需求分析:

    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,大神们不喜勿喷,有什么好的实现思路 欢迎探讨 嘎嘎.... 下篇文章就单独来讲按钮的点击事件了,今天就写到这了,下篇再见哦..

    相关文章

      网友评论

        本文标题:转圈菜单栏的实现《一》

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