项目介绍:
自定义Button ( github @ coderZsq/coderZsq.project.ios)
这里我们使用CAShapeLayer + UIBezierPath将Button给画了出来, 其实完全可以让设计做图, 我这里只是因为抛砖引玉后面的内容!!
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
[Objective-C] 查看源文件 复制代码
- (CAShapeLayer *)roundShapeLayer {
if (!_roundShapeLayer) {
_roundShapeLayer = [CAShapeLayer layer];
_roundShapeLayer.backgroundColor = KC01_57c2de.CGColor;
_roundShapeLayer.borderColor = KC05_dddddd.CGColor;
_roundShapeLayer.borderWidth = 0.5f;
_roundShapeLayer.masksToBounds = YES;
}
return _roundShapeLayer;
}
- (CAShapeLayer *)horizontalShapeLayer {
if (!_horizontalShapeLayer) {
_horizontalShapeLayer = [CAShapeLayer layer];
_horizontalShapeLayer.fillColor = [UIColor whiteColor].CGColor;
}
return _horizontalShapeLayer;
}
- (CAShapeLayer *)verticalShapeLayer {
if (!_verticalShapeLayer) {
_verticalShapeLayer = [CAShapeLayer layer];
_verticalShapeLayer.fillColor = _horizontalShapeLayer.fillColor;
}
return _verticalShapeLayer;
}
- (void)setupSubviews {
[self setAlpha:0.7f];
[self.layer addSublayer:self.roundShapeLayer];
[self.layer addSublayer:self.horizontalShapeLayer];
[self.layer addSublayer:self.verticalShapeLayer];
}
- (void)layoutSubviews {
[super layoutSubviews];
CGFloat roundShapeLayerX = 0;
CGFloat roundShapeLayerY = 0;
CGFloat roundShapeLayerW = self.width;
CGFloat roundShapeLayerH = roundShapeLayerW;
self.roundShapeLayer.cornerRadius = roundShapeLayerW * 0.5f;
self.roundShapeLayer.frame = CGRectMake(roundShapeLayerX, roundShapeLayerY, roundShapeLayerW, roundShapeLayerH);
CGFloat horizontalPathW = self.width - 12;
CGFloat horizontalPathH = self.height / 6;
CGFloat horizontalPathX = (self.width - horizontalPathW) * 0.5f;
CGFloat horizontalPathY = (self.height - horizontalPathH) * 0.5f;
self.horizontalShapeLayer.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(horizontalPathX, horizontalPathY, horizontalPathW, horizontalPathH) cornerRadius:10].CGPath;
CGFloat verticalPathW = horizontalPathH;
CGFloat verticalPathH = horizontalPathW;
CGFloat verticalPathX = (self.width - verticalPathW) * 0.5f;;
CGFloat verticalPathY = (self.height - horizontalPathW) * 0.5f;;
self.verticalShapeLayer.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(verticalPathX, verticalPathY, verticalPathW, verticalPathH) cornerRadius:10].CGPath;
}
|
2. 可移动Button
将上面创建好的Button 继承与SQExtension中的SQRemovableButton 即可实现移动属性!! 这里我来看下可移动属性的实现原理!!(其实完全可以使用Pan手势实现!!)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
[Objective-C] 查看源文件 复制代码
@interface SQRemovableButton ()
@property (nonatomic,assign,getter = isMoved) BOOL moved;
@end
@implementation SQRemovableButton
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesMoved:touches withEvent:event];
self.moved = YES;
UITouch * touch = [touches anyObject];
CGPoint current = [touch locationInView:self];
CGPoint previous = [touch previousLocationInView:self];
CGPoint center = self.center;
center.x += current.x - previous.x; center.y += current.y - previous.y;
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;
CGFloat xMin = self.frame.size.width * 0.5f; CGFloat xMax = screenWidth - xMin;
CGFloat yMin = self.frame.size.height * 0.5f; CGFloat yMax = screenHeight - yMin - 49;
if (center.x > xMax) center.x = xMax; if (center.y > yMax) center.y = yMax;
if (center.x < xMin) center.x = xMin; if (center.y < yMin) center.y = yMin;
self.center = center;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if (!self.moved) {
[super touchesEnded:touches withEvent:event];
}
self.moved = NO; if (!self.dockable) return;
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
CGFloat x = self.frame.size.width * 0.5f;
[UIView animateWithDuration:0.25f animations:^{
CGPoint center = self.center;
center.x = self.center.x > screenWidth * 0.5f ? screenWidth - x : x;
self.center = center;
}];
}
|
这里有个难点在于当滑动的时候会和Touch Events/Tap Gesture 发生冲突, 解决方法在于当其移动的时候不调用其父类的方法即可!!
3. Button的Pop动画
使用SQExtension中的CAAnimation+Extension即可实现pop动画, 这个我不多说, 关键帧动画的基本实现!
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[Objective-C] 查看源文件 复制代码
+ (CAAnimation *)animationPopWithLayer:(CALayer *)layer {
CAKeyframeAnimation * pop = [CAKeyframeAnimation animation];
pop.keyPath = @"transform.scale";
pop.values = @[@0.1, @0.2, @0.3, @0.2, @0.1];
pop.additive = YES;
CAAnimationGroup * group = [CAAnimationGroup new];
group.animations = @[pop];
group.duration = kTimeInterval;
group.removedOnCompletion = NO;
[layer addAnimation:group forKey:nil];
return group;
}
|
我们同样将其加在navigationController.view 上并设定初始布局
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
– (SQLifestylePostButton *)postButton { CGFloat postButtonW = 40; CGFloat postButtonH = postButtonW; CGFloat postButtonX = self.view.width – kSpace – postButtonW; CGFloat postButtonY = self.view.height – 49 – kSpace – postButtonH; if (!_postButton) { _postButton = [SQLifestylePostButton new]; _postButton.frame = CGRectMake(postButtonX, postButtonY, postButtonW, postButtonH); } [CAAnimation animationPopWithLayer:_postButton.layer]; return _postButton; } |
并将点击事件在Button内部实现, 那我们怎么在view的内部拿到当前控制器呢?
之前我的老大在项目中是用单例设计模式实现的可以参考SQExtension中的SQViewControllerManager.h, 今天我分享一个更加投机的方式, 全局变量!!
我们先将所有的控制器都继承与SQViewController, 并实现如下方法;
.h
| 1 | extern UIViewController * kCurrentViewController; |
.m
| 1 | UIViewController * kCurrentViewController = nil; |
SQViewController.m
| 1 2 3 4 |
[Objective-C] 查看源文件 复制代码
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
kCurrentViewController = self;
}
|
这样我们就能够拿到当前的控制器了 whenTap方法我不多讲,底层是用runtime实现的, 就是为了让代码高内聚,低耦合,(关键是runtime真的要讲很久…)
| 1 2 3 4 5 |
[Objective-C] 查看源文件 复制代码
__weak typeof(self) _self = self;
[self whenTapped:^{
SQNavigationController * navigationController = [[SQNavigationController alloc]initWithRootViewController:[SQPostViewController new]];
[kCurrentViewController presentViewController:navigationController animated:YES completion:nil];
}];
|
到此为止, 我们的Button已经具有不错的动画效果了~~
4. Button的自定义转场动画
自定义转场, 我之前就有提及到我就快速的讲, 今天我们来在Modal的自定义转场, 自定义转场两步走~
1 设置代理
| 1 2 3 4 5 6 7 8 9 |
[Objective-C] 查看源文件 复制代码
@interface SQLifestylePostButton () UIViewControllerTransitioningDelegate>[/align]
[navigationController setTransitioningDelegate:_self];
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
SQHoleAnimatedTransitioning * animatedTransitioning = [SQHoleAnimatedTransitioning new];
animatedTransitioning.frame = self.frame;
return animatedTransitioning;
}
|
2 实现自定义转场
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
[Objective-C] 查看源文件 复制代码
@interface SQHoleAnimatedTransitioning ()
@property (nonatomic, strong) id<UIViewControllerContextTransitioning> transitionContext;
@end
static const CGFloat kRatio = 1.5f;
@implementation SQHoleAnimatedTransitioning
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
return 0.25f;
}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
self.transitionContext = transitionContext;
UIViewController * toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController * fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIView * containView = transitionContext.containerView;
[containView addSubview:toViewController.view];
[containView addSubview:fromViewController.view];
UIView * endView = [UIView new];
endView.frame = self.frame;
UIBezierPath * endPath = [UIBezierPath bezierPathWithOvalInRect:endView.frame];
UIView * startView = [UIView new];
startView.center = endView.center;
startView.bounds = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.height * kRatio, [UIScreen mainScreen].bounds.size.height * kRatio);
UIBezierPath * startpath = [UIBezierPath bezierPathWithOvalInRect:startView.frame];
CAShapeLayer * maskLayer = [CAShapeLayer layer];
maskLayer.path = endPath.CGPath;
fromViewController.view.layer.mask = maskLayer;
CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"path"];
animation.fromValue = (__bridge id )(startpath.CGPath);
animation.toValue = (__bridge id )(endPath.CGPath);
animation.duration = [self transitionDuration:self.transitionContext];
animation.delegate = self;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[maskLayer addAnimation:animation forKey:nil];
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
[self.transitionContext completeTransition:![self.transitionContext transitionWasCancelled]];
[self.transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey].view.layer.mask = nil;
}
|
之前我有看过用象限算法实现的, 擦, 好复杂… 本宝宝最不喜欢的就是计算这种费脑子的事情了, 能够投机的, 就不要那么麻烦嘛, 这里我借用了两个view来实现相同功能, 我感觉这里需要细讲一下,不然有些同学不是很好理解…
self.frame 就是可移动Button最终的frame, 然后借助一个View来确定第二个Rect的Center, 其实就是同心圆, 有没有很好理解?? 然后使用mask属性进行路径动画就可以了, mask其实就Ps中的蒙版, 遮罩, 对mask属性不是很熟悉的同学可以Google一下alpha通道, 简单来说就是, mask不透明, 就能够穿透, 好像就是这个意思~
DEMO 直接下载:
