- 做动画的一般性原则
- 善于拆解
- 涉及到的两个技术点
- CADisplayLink
- 简单来说,它就是一个定时器,每隔1/60秒刷新一次屏幕
- 使用的时候要将它添加到一个
runloop
中,并给它绑定一个target
和selector
- 由于它绑定的方法在每次屏幕刷新时被调用,精确度相当之高,非常适合UI的重绘
- 贝塞尔曲线
- CADisplayLink
- GooeySlideMenu动画的实现思路
- 首先把问题拆解成一个淡蓝色的View从屏幕左侧一如
- 利用贝塞尔曲线实现边界的弯曲(两个端点,两个控制点)
- GooeySlideMenu动画的具体实现步骤
- 界面布局如图所示
* 创建继承自
UIView
的ZQSlideMenuButton
类,具体代码如下```objc
// ZQSlideMenuButton.h
#import <UIKit/UIKit.h>
@interface ZQSlideMenuButton : UIView
/** 初始化菜单按钮标题 */
-(instancetype)initWithTitle:(NSString *)title;
/** 菜单按钮颜色 */
@property(nonatomic, strong)UIColor *buttonColor;
/** 菜单按钮点击block */
@property(nonatomic, copy)void(^buttonClickBlock)(void);
@end
// ZQSlideMenuButton.m
#import "ZQSlideMenuButton.h"
@interface ZQSlideMenuButton()
/** 菜单按钮标题 */
@property(nonatomic, strong)NSString *buttonTitle;
@end
@implementation ZQSlideMenuButton
/** 初始化菜单按钮标题的方法实现 */
-(instancetype)initWithTitle:(NSString *)title {
self = [super init];
if (self) {
self.buttonTitle = title;
}
return self;
}
// Only override drawRect: if you perform custom rawing.
// An empty implementation adversely affects performance during animation.
-(void)drawRect:(CGRect)rect {
// 开启上下文设置菜单按钮的的颜色
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddRect(context, rect);
[self.buttonColor set];
CGContextFillPath(context);
// 绘制菜单按钮的颜色
UIBezierPath *roundedRectanglePath = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, 1, 1) cornerRadius:rect.size.height / 2];
[self.buttonColor setFill];
[roundedRectanglePath fill];
[[UIColor whiteColor] setStroke];
roundedRectanglePath.lineWidth = 1;
[roundedRectanglePath stroke];
// 按钮标题的段落样式
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle defaultParagraphStyle]mutableCopy];
paragraphStyle.alignment = NSTextAlignmentCenter;
NSDictionary *attr = @{NSParagraphStyleAttributeName:paragraphStyle, NSFontAttributeName:[UIFont systemFontOfSize:24.0f], NSForegroundColorAttributeName:[UIColor whiteColor]};
CGSize size = [self.buttonTitle sizeWithAttributes:attr];
// 菜单按钮的尺寸
CGRect r = CGRectMake(rect.origin.x, rect.origin.y + (rect.size.height - size.height) / 2.0, rect.size.width, size.height);
// 绘制菜单按钮
[self.buttonTitle drawInRect:r withAttributes:attr];
}
/** 触摸结束调用的方法 */
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
NSUInteger tapCount = touch.tapCount;
switch (tapCount) {
case 1:
self.buttonClickBlock();
break;
default:
break;
}
}
```
* 创建继承自`UIView`的`ZQGooeySlideMenu`类,具体代码如下
```objc
// ZQGooeySlideMenu.h
#import <UIKit/UIKit.h>
typedef void (^MenuButtonClickedBlock)(NSInteger index, NSString *title, NSInteger titleCounts);
@interface ZQGooeySlideMenu : UIView
/** 菜单按钮的所有标题的便利构造函数 */
-(instancetype)initWithTitles:(NSArray *)titles;
/** 自定义菜单按钮标题 */
-(instancetype)initWithTitles:(NSArray *)titles withButtonHeight:(CGFloat)height withMenuColor:(UIColor *)menuColor withBackBlurStyle:(UIBlurEffectStyle)style;
/** 触发动画的方法 */
-(void)trigger;
/** 菜单按钮点击的block */
@property(nonatomic, copy)MenuButtonClickedBlock menuClickBlock;
@end
// GooeySlideMenu.m
#import "ZQGooeySlideMenu.h"
#import "ZQSlideMenuButton.h"
#define buttonSpace 30
#define menuBlankWidth 50
@interface ZQGooeySlideMenu()
@property (nonatomic,strong) CADisplayLink *displayLink;
@property NSInteger animationCount; // 动画的数量
@end
@implementation ZQGooeySlideMenu{
UIVisualEffectView *blurView;
UIView *helperSideView;
UIView *helperCenterView;
UIWindow *keyWindow;
BOOL triggered;
CGFloat diff;
UIColor *_menuColor;
CGFloat menuButtonHeight;
}
-(id)initWithTitles:(NSArray *)titles{
return [self initWithTitles:titles withButtonHeight:40.0f withMenuColor:[UIColor colorWithRed:0 green:0.722 blue:1 alpha:1] withBackBlurStyle:UIBlurEffectStyleDark];
}
-(id)initWithTitles:(NSArray *)titles withButtonHeight:(CGFloat)height withMenuColor:(UIColor *)menuColor withBackBlurStyle:(UIBlurEffectStyle)style{
self = [super init];
if (self) {
keyWindow = [[UIApplication sharedApplication]keyWindow];
blurView = [[UIVisualEffectView alloc]initWithEffect:[UIBlurEffect effectWithStyle:style]];
blurView.frame = keyWindow.frame;
blurView.alpha = 0.0f;
helperSideView = [[UIView alloc]initWithFrame:CGRectMake(-40, 0, 40, 40)];
helperSideView.backgroundColor = [UIColor redColor];
helperSideView.hidden = YES;
[keyWindow addSubview:helperSideView];
helperCenterView = [[UIView alloc]initWithFrame:CGRectMake(-40, CGRectGetHeight(keyWindow.frame)/2 - 20, 40, 40)];
helperCenterView.backgroundColor = [UIColor yellowColor];
helperCenterView.hidden = YES;
[keyWindow addSubview:helperCenterView];
self.frame = CGRectMake(- keyWindow.frame.size.width/2 - menuBlankWidth, 0, keyWindow.frame.size.width/2+menuBlankWidth, keyWindow.frame.size.height);
self.backgroundColor = [UIColor clearColor];
[keyWindow insertSubview:self belowSubview:helperSideView];
_menuColor = menuColor;
menuButtonHeight = height;
[self addButtons:titles];
}
return self;
}
-(void)addButtons:(NSArray *)titles{
if (titles.count % 2 == 0) {
NSInteger index_down = titles.count/2;
NSInteger index_up = -1;
for (NSInteger i = 0; i < titles.count; i++) {
NSString *title = titles[i];
ZQSlideMenuButton *home_button = [[ZQSlideMenuButton alloc]initWithTitle:title];
if (i >= titles.count / 2) {
index_up ++;
home_button.center = CGPointMake(keyWindow.frame.size.width/4, keyWindow.frame.size.height/2 + menuButtonHeight*index_up + buttonSpace*index_up + buttonSpace/2 + menuButtonHeight/2);
}else{
index_down --;
home_button.center = CGPointMake(keyWindow.frame.size.width/4, keyWindow.frame.size.height/2 - menuButtonHeight*index_down - buttonSpace*index_down - buttonSpace/2 - menuButtonHeight/2);
}
home_button.bounds = CGRectMake(0, 0, keyWindow.frame.size.width/2 - 20*2, menuButtonHeight);
home_button.buttonColor = _menuColor;
[self addSubview:home_button];
__weak typeof(self) WeakSelf = self;
home_button.buttonClickBlock = ^(){
[WeakSelf tapToUntrigger];
WeakSelf.menuClickBlock(i,title,titles.count);
};
}
}else{
NSInteger index = (titles.count - 1) / 2 +1;
for (NSInteger i = 0; i < titles.count; i++) {
index --;
NSString *title = titles[i];
ZQSlideMenuButton *home_button = [[ZQSlideMenuButton alloc]initWithTitle:title];
home_button.center = CGPointMake(keyWindow.frame.size.width/4, keyWindow.frame.size.height/2 - menuButtonHeight*index - 20*index);
home_button.bounds = CGRectMake(0, 0, keyWindow.frame.size.width/2 - 20*2, menuButtonHeight);
home_button.buttonColor = _menuColor;
[self addSubview:home_button];
__weak typeof(self) WeakSelf = self;
home_button.buttonClickBlock = ^(){
[WeakSelf tapToUntrigger];
WeakSelf.menuClickBlock(i,title,titles.count);
};
}
}
}
-(void)drawRect:(CGRect)rect {
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, 0)];
[path addLineToPoint:CGPointMake(self.frame.size.width-menuBlankWidth, 0)];
[path addQuadCurveToPoint:CGPointMake(self.frame.size.width-menuBlankWidth, self.frame.size.height) controlPoint:CGPointMake(keyWindow.frame.size.width/2+diff, keyWindow.frame.size.height/2)];
[path addLineToPoint:CGPointMake(0, self.frame.size.height)];
[path closePath];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddPath(context, path.CGPath);
[_menuColor set];
CGContextFillPath(context);
}
-(void)trigger{
if (!triggered) {
[keyWindow insertSubview:blurView belowSubview:self];
[UIView animateWithDuration:0.3 animations:^{
self.frame = self.bounds;
}];
[self beforeAnimation];
[UIView animateWithDuration:0.7 delay:0.0f usingSpringWithDamping:0.5f initialSpringVelocity:0.9f options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction animations:^{
helperSideView.center = CGPointMake(keyWindow.center.x, helperSideView.frame.size.height/2);
} completion:^(BOOL finished) {
[self finishAnimation];
}];
[UIView animateWithDuration:0.3 animations:^{
blurView.alpha = 1.0f;
}];
[self beforeAnimation];
[UIView animateWithDuration:0.7 delay:0.0f usingSpringWithDamping:0.8f initialSpringVelocity:2.0f options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction animations:^{
helperCenterView.center = keyWindow.center;
} completion:^(BOOL finished) {
if (finished) {
UITapGestureRecognizer *tapGes = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapToUntrigger)];
[blurView addGestureRecognizer:tapGes];
[self finishAnimation];
}
}];
[self animateButtons];
triggered = YES;
}else{
[self tapToUntrigger];
}
}
-(void)animateButtons{
for (NSInteger i = 0; i < self.subviews.count; i++) {
UIView *menuButton = self.subviews[i];
menuButton.transform = CGAffineTransformMakeTranslation(-90, 0);
[UIView animateWithDuration:0.7 delay:i*(0.3/self.subviews.count) usingSpringWithDamping:0.6f initialSpringVelocity:0.0f options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction animations:^{
menuButton.transform = CGAffineTransformIdentity;
} completion:NULL];
}
}
-(void)tapToUntrigger{
[UIView animateWithDuration:0.3 animations:^{
self.frame = CGRectMake(-keyWindow.frame.size.width/2-menuBlankWidth, 0, keyWindow.frame.size.width/2+menuBlankWidth, keyWindow.frame.size.height);
}];
[self beforeAnimation];
[UIView animateWithDuration:0.7 delay:0.0f usingSpringWithDamping:0.6f initialSpringVelocity:0.9f options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction animations:^{
helperSideView.center = CGPointMake(-helperSideView.frame.size.height/2, helperSideView.frame.size.height/2);
} completion:^(BOOL finished) {
[self finishAnimation];
}];
[UIView animateWithDuration:0.3 animations:^{
blurView.alpha = 0.0f;
}];
[self beforeAnimation];
[UIView animateWithDuration:0.7 delay:0.0f usingSpringWithDamping:0.7f initialSpringVelocity:2.0f options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction animations:^{
helperCenterView.center = CGPointMake(-helperSideView.frame.size.height/2, CGRectGetHeight(keyWindow.frame)/2);
} completion:^(BOOL finished) {
[self finishAnimation];
}];
triggered = NO;
}
//动画之前调用
-(void)beforeAnimation{
if (self.displayLink == nil) {
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkAction:)];
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
self.animationCount ++;
}
//动画完成之后调用
-(void)finishAnimation{
self.animationCount --;
if (self.animationCount == 0) {
[self.displayLink invalidate];
self.displayLink = nil;
}
}
-(void)displayLinkAction:(CADisplayLink *)dis{
CALayer *sideHelperPresentationLayer = (CALayer *)[helperSideView.layer presentationLayer];
CALayer *centerHelperPresentationLayer = (CALayer *)[helperCenterView.layer presentationLayer];
CGRect centerRect = [[centerHelperPresentationLayer valueForKeyPath:@"frame"]CGRectValue];
CGRect sideRect = [[sideHelperPresentationLayer valueForKeyPath:@"frame"]CGRectValue];
diff = sideRect.origin.x - centerRect.origin.x;
[self setNeedsDisplay];
}
@end
```
* 控制器代码如下所示
```objc
// ViewController.m
#import "ViewController.h"
#import "ZQGooeySlideMenu.h"
@interface ViewController ()<UITableViewDelegate,UITableViewDataSource>
@end
@implementation ViewController{
ZQGooeySlideMenu *menu;
}
-(void)viewDidLoad {
[super viewDidLoad];
self.title = @"首页";
menu = [[ZQGooeySlideMenu alloc]initWithTitles:@[@"首页",@"消息",@"发布",@"发现",@"个人",@"设置"]];
menu.menuClickBlock = ^(NSInteger index,NSString *title,NSInteger titleCounts){
NSLog(@"index:%ld title:%@ titleCounts:%ld",index,title,titleCounts);
};
}
-(IBAction)buttonTrigger:(id)sender {
[menu trigger];
}
#pragma mark -- UITabel View Datasource
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return 20;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"demoCell"];
cell.textLabel.text = [NSString stringWithFormat:@"NO.%ld",(long)indexPath.row];
return cell;
}
@end
```
* 运行结果如图所示