轉圈菜單欄的實現《一》

再過幾天就要放假咯,前兩天也是夠鬧心的了,上線被蘋果拒了好幾次,心都拔涼拔涼的了,好吧,廢話說完了,下面直接入主題:

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,大神們不喜勿噴,有什么好的實現思路 歡迎探討 嘎嘎.... 下篇文章就單獨來講按鈕的點擊事件了,今天就寫到這了,下篇再見哦..

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,559評論 25 708
  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,257評論 4 61
  • 在iOS中隨處都可以看到絢麗的動畫效果,實現這些動畫的過程并不復雜,今天將帶大家一窺ios動畫全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,573評論 6 30
  • 如果是“工作總結”那么上周可以不用總結了,我基本上沒有干什么跟工作相關的事情,這可能是我來北京后最“頹廢”的一周了...
    小苤閱讀 258評論 0 0