原文地址----> MyBlog
SKChoosePopView是一个HUD风格的可定制化选项弹窗的快速解决方案,集成了上、下、左、右、中5个进场方向的6种动画效果,如果不能满足你对酷炫效果的需要,SKChoosePopView同样支持自定义动画,以及选择记录、动画的开闭、点击特效、行列数量控制等。如果你觉得还不错,star支持一下吧!
1.从GitHub上Clone-->SKChoosePopView, 然后查看Demo
2.请仔细阅读下方特别指出的部分和需要注意问题
3.在项目中使用SKChoosePopView,直接将SKPopView文件夹拷贝到工程中
4.SKChoosePopView基于Masonry布局,请确保你的工程里已存在Masonry,下载地址
#import "SKPopView.h"
/** 初始化方法
* @param title 标题数组
* @param iconNormal 默认图标数组
* @param iconSelected 选中图标数组, 若不需要传入nil即可
* @param titleColor 选中的选项标题字体颜色, 若不需要传入nil即可
* @param delegate 代理协议
* @param completion 弹窗出现后的回调操作,若不需要传入nil即可
*/
SKPopView * popView = [[SKPopView alloc] initWithOptionsTitle:kDate.title
OptionsIconNormal:kDate.normalIcons
OptionsIconSelected:kDate.selectedIcons
selectedTitleColor:[UIColor orangeColor]
delegate:self completion:^{
// TODO: 如果这里不需要就nil
}];
[popView show];
[popView dismiss];
popView.animationType = SK_TYPE_SPRING;
popView.animationDirection = SK_SUBTYPE_FROMBOTTOM;
popView.animationDuration = 0.5;
popView.enableRecord = YES;
popView.enableAnimation = YES;
popView.optionsLine = 2;
popView.optionsRow = 3;
popView.minLineSpacing = 10;
popView.minRowSpacing = 10;
SK_TYPE_SPRING,// 弹簧效果
SK_TYPE_ROTATION,// 旋转效果
SK_TYPE_FADE,// 渐变效果
SK_TYPE_LARGEN,// 变大效果
SK_TYPE_ROTATION_LARGEN,// 旋转变大效果
SK_TYPE_TRANSFORMATION// 变形效果
SK_SUBTYPE_FROMRIGHT,// 从右侧进入
SK_SUBTYPE_FROMLEFT,// 从左侧进入
SK_SUBTYPE_FROMTOP,// 从顶部进入
SK_SUBTYPE_FROMBOTTOM,// 从底部进入
SK_SUBTYPE_FROMCENTER// 从屏幕中间进入
- (void)selectedWithRow:(NSUInteger)row;
1.optionsLine
和optionsRow
属性是必须设置的, 且遵循垂直布局原则,请确保optionsLine * optionsRow于选项数量相等
2.最小行、列间距如不需要可以不设置,默认为0
3.如果开启动画,请确保animationType
、animationDirection
和animationDuration
属性已经设置
4.使用代理协议方法前,请确保已遵循
4.如果遇到其它问题,欢迎提交issues,我会及时回复
首先,在总体设计上我们采取模块化的思路,主要分为两部分:SKPopView
(视图)和SKPopAnimationManger
(动画管理),各司其职。
SKPopView
负责处理界面控件的创建和布局约束、外部对弹窗配置信息设置的响应(如是否开启动画效果、点击效果、选择记录、动画类型/方向、需要显示的行/列等)、已选择的选项回调等。
SKPopAnimationManger
则负责动画的相关管理,如对动画进场方向的轨迹控制、动画效果的具体实现等。
在整体上,SKPopView
充当了一个父UIView
的角色,在其内部添加grayBackground1
(灰色的背景)和popView
(HUD弹窗部分),总体结构简单明了,所以在调用的时候我们是直接将SKPopView
整个添加到我们需要显示的界面当中来使用的。
在SKPopView
中,grayBackground
作为灰色背景率先addSubview到SKPopView
中[self addSubview:grayBackground]
,而弹窗则作为插入部分,插入到grayBackground
之上[self insertSubview:self.popView aboveSubview:grayBackground]
, 而在popView
内部,我们内嵌了一个UICollectionView
,作为选项按钮的展示。
SKPopView
整体便会添加到当前视图的图层之上,而弹窗popView
此时会根据调用时在初始化方法里配置的SK_TYPE(动画类型)和SK_SUBTYPE(动画方向),在屏幕外选择一个入场前的坐标和入场时的动画效果,当初始化完成开始调用SKPopView
的show
方法时,popView
便开始入场、完成动画。#pragma mark - 外部调用
- (void)show
{
if (self.enableAnimation == YES) {// 如果开启动画效果
[self displayAnimation];
}
}
#pragma mark - 动画设置
- (void)displayAnimation
{
self.animationManger = [[SKPopAnimationManger alloc] init];
switch (self.animationType) {// 动画类型
case SK_TYPE_SPRING:
self.animationManger.type = SK_ANIMATION_TYPE_SPRING;
break;
case SK_TYPE_ROTATION:
self.animationManger.type = SK_ANIMATION_TYPE_ROTATION;
break;
case SK_TYPE_FADE:
self.animationManger.type = SK_ANIMATION_TYPE_FADE;
break;
case SK_TYPE_LARGEN:
self.animationManger.type = SK_ANIMATION_TYPE_LARGEN;
break;
case SK_TYPE_ROTATION_LARGEN:
self.animationManger.type = SK_ANIMATION_TYPE_ROTATION_LARGEN;
break;
case SK_TYPE_TRANSFORMATION:
self.animationManger.type = SK_ANIMATION_TYPE_TRANSFORMATION;
break;
}
switch (self.animationDirection) {// 动画进场方向
case SK_SUBTYPE_FROMRIGHT:
self.animationManger.animationDirection = SK_ANIMATION_SUBTYPE_FROMRIGHT;
break;
case SK_SUBTYPE_FROMLEFT:
self.animationManger.animationDirection = SK_ANIMATION_SUBTYPE_FROMLEFT;
break;
case SK_SUBTYPE_FROMTOP:
self.animationManger.animationDirection = SK_ANIMATION_SUBTYPE_FROMTOP;
break;
case SK_SUBTYPE_FROMBOTTOM:
self.animationManger.animationDirection = SK_ANIMATION_SUBTYPE_FROMBOTTOM;
break;
case SK_SUBTYPE_FROMCENTER:
self.animationManger.animationDirection = SK_ANIMATION_SUBTYPE_FROMCENTER;
break;
default:
break;
}
// 对进场动画进行设置
[self.animationManger animateWithView:self.popView Duration:self.animationDuration animationType:self.animationManger.type animationDirection:self.animationManger.animationDirection];
}
grayBackground
添加点击手势来达到目的, 注意:grayBackground
在这里是一个UIImageView
,在添加手势时,一定要将userInteractionEnabled = YES
,开启用户交互,否则手势将失效。// 灰色背景
UIImageView * grayBackground = [UIImageView new];
[self addSubview:grayBackground];
grayBackground.backgroundColor = [UIColor colorWithWhite:0.3 alpha:0.5];;
grayBackground.userInteractionEnabled = YES;
[grayBackground mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self).with.insets(UIEdgeInsetsMake(0, 0, 0, 0));
}];
// 添加手势
UITapGestureRecognizer * dismissGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(cancel)];
[grayBackground addGestureRecognizer:dismissGesture];
#pragma mark - 手势响应
- (void)cancel
{
[self dismissAnimation];
}
- (void)dismissAnimation
{
if (self.enableAnimation == YES) {// 如果开启动画效果
[self.animationManger dismissAnimationForRootView:self];// 使用离场动画
} else {
[self removeFromSuperview];// 直接移除整个SKPopView
}
}
SKPopView
的dismiss
方法,十分方便。如
- (void)clickCancel // 取消按钮方法
{
[popView dismiss];
}
popView
中镶嵌了一个UICollectionView
,而UICollectionView
提供了几个很好的方法可以让我们相对轻松的对选项的数量及在弹窗内的显示方式进行把控,我们主要应用了下面几个方法// 设置item的size
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
// 最小列(横向)间距
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
// 最小行(纵向)间距
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
然后根据我们在SKPopView
的头文件中暴露的外部配置信息optionsLine
(显示的行数)、optionsRow
(显示的列数)、minLineSpacing
(最小行间距)、minRowSpacing
(最小列间距),来对具体的样式进行修改,具体转换算法请参考SKPopView.m中外部配置
标签下的代码段
1.进场动画:
首先我们要弄明白一点,进场动画,顾名思义:就是由进场+动画两个效果部分组成的
由于默认集成了来自5个方向的6种不同效果的动画,导致动画在进行相应配置时,对于每种动画效果都需要考虑5种不同的进场起始坐标,所以,我们用一个统一的方法对动画的进场方向进行统一管理,同样的,考虑到一些动画效果需要使用动画组, 对动画在进场过程中的位移路径也需要做一个统一管理
#pragma mark - 动画方向初始化
- (void)animationDirectionInitialize
{
if (_animationDirection == SK_ANIMATION_SUBTYPE_FROMRIGHT) {// 从右侧进场
_animationView.center = CGPointMake(MyWidth + 1000, WindowCenter.y);
} else if (_animationDirection == SK_ANIMATION_SUBTYPE_FROMLEFT) {// 从左侧进场
_animationView.center = CGPointMake(MyWidth - 1000, WindowCenter.y);
} else if (_animationDirection == SK_ANIMATION_SUBTYPE_FROMTOP) {// 从顶部进场
_animationView.center = CGPointMake(WindowCenter.x, MyHeight - 1000);
} else if (_animationDirection == SK_ANIMATION_SUBTYPE_FROMBOTTOM) {// 从底部进场
_animationView.center = CGPointMake(WindowCenter.x, MyHeight + 1000);
} else {// 中间进场
_animationView.center = WindowCenter;
}
}
#pragma mark - 动画通用位移
- (CAKeyframeAnimation *)partOfTheAnimationGroupPosition:(CGPoint)startPosition
{
CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];// 创建关键帧位移动画
NSValue * startValue, * endValue;
startValue = [NSValue valueWithCGPoint:startPosition];// 起始坐标
endValue = [NSValue valueWithCGPoint:WindowCenter];// 结束坐标
animation.values = @[startValue, endValue];// 设置动画路径
animation.duration = _animationDuration;// 持续时间
return animation;
}
弹簧效果
的动画由于不是并发动画效果,所以是没有用到通用位移
的,所以它的位移方法是独立出来的 #pragma mark - 弹簧效果
- (void)springAnimation
{
[self animationDirectionInitialize];// 初始化方向
[self displacementWithStartPosition:_animationView.center];
}
/** 弹簧效果位移部分
* @param startPosition 位移起始坐标
*/
- (void)displacementWithStartPosition:(CGPoint)startPosition{
CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];// 创建关键帧动画
NSValue * startValue, * endValue;
startValue = [NSValue valueWithCGPoint:startPosition];
endValue = [NSValue valueWithCGPoint:WindowCenter];
animation.values = @[startValue, endValue];
animation.duration = _animationDuration;
animation.delegate = self;// 设置CAAnimationDelegate
[_animationView.layer addAnimation:animation forKey:@"pathAnimation"];
}
/** 弹簧效果晃动部分
*/
- (void)partOfTheSpringGroupShaking
{
NSString * keyPath = @"";
if (_animationDirection == SK_ANIMATION_SUBTYPE_FROMLEFT || _animationDirection == SK_ANIMATION_SUBTYPE_FROMRIGHT) {// 判断弹窗的来向
keyPath = @"transform.translation.x";
} else {
keyPath = @"transform.translation.y";
}
CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:keyPath];
animation.fromValue = [NSNumber numberWithFloat:-20.0];
animation.toValue = [NSNumber numberWithFloat:20.0];
animation.duration = 0.1;
animation.autoreverses = YES;// 是否重复
animation.repeatCount = 2;// 重复次数
[_animationView.layer addAnimation:animation forKey:@"shakeAnimation"];
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag// 监听动画停止
{
[self partOfTheSpringGroupShaking];
}
2.撤场动画
撤场动画就相对简单很多, 由于撤场方式是统一的,只需要对撤出坐标做处理,然后removeFromSuperview
- (void)dismissAnimationForRootView:(UIView *)view
{
[UIView animateWithDuration:0.5 animations:^{
_animationView.center = CGPointMake(WindowCenter.x, MyHeight + 1000);
} completion:^(BOOL finished) {
[view removeFromSuperview];
}];
}
3.选项点击动画
当点击动画效果被开启时,才会调用这个方法,即enableClickEffect = YES
时, SKPopViewCollectionViewCell
中会调用点击动画
- (void)setEnableClickEffect:(BOOL)enableClickEffect
{
_enableClickEffect = enableClickEffect;
if (enableClickEffect == YES) {
SKPopAnimationManger * animationManger = [[SKPopAnimationManger alloc] init];
[animationManger clickEffectAnimationForView:self.basementView];
}
}
而在SKPopAnimationManger
中,我们只是巧妙的对其做了一个缩放的效果,即可达到果冻式的触感
- (void)clickEffectAnimationForView:(UIView *)view
{
CABasicAnimation * scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
scaleAnimation.fromValue = [NSNumber numberWithFloat:1];
scaleAnimation.toValue = [NSNumber numberWithFloat:0.8];
scaleAnimation.duration = 0.1;
scaleAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[view.layer addAnimation:scaleAnimation forKey:nil];
}
Email: [email protected]
或者直接在文章下留言哦~