HUD風(fēng)格的選項(xiàng)彈窗是我們在日常開發(fā)中經(jīng)常會碰到的一類需求蚕泽,通常因?yàn)轫?xiàng)目周期等因素翁脆,很少會專門抽出時(shí)間來對此類彈窗進(jìn)行專門的定制開發(fā)和維護(hù)育苟。常見的情況就是google類似的效果控件黎棠,如果恰好匹配需求,效果上說得過去鲤看,那么便可以節(jié)省不少的時(shí)間和精力缘揪,但更多的情況是,我們花費(fèi)了更多的時(shí)間去修改义桂、去填坑找筝,效果缺不見得如意,導(dǎo)致開發(fā)者最后不得不吐槽:還不如自己寫慷吊。對于注重追求效率的開發(fā)者袖裕,似乎什么輪子都可以上路跑,只要它ok溉瓶,但對于更注重用戶體驗(yàn)和效果的開發(fā)者而言急鳄,就很難將就了,但人的精力總歸有限堰酿,抽不開身怎么辦呢疾宏?這個(gè)時(shí)候就很需要一款效果很贊,使用方便触创、簡潔坎藐、可快速集成的組件了:SKChoosePopView便應(yīng)運(yùn)而生了。
簡述
SKChoosePopView是一個(gè)HUD風(fēng)格的可定制化選項(xiàng)彈窗的快速解決方案嗅榕,集成了上顺饮、下、左凌那、右、中5個(gè)進(jìn)場方向的6種動(dòng)畫效果吟逝,如果不能滿足你對酷炫效果的需要帽蝶,SKChoosePopView同樣支持自定義動(dòng)畫,以及選擇記錄块攒、動(dòng)畫的開閉励稳、點(diǎn)擊特效、行列數(shù)量控制等囱井。如果你覺得還不錯(cuò)驹尼,star支持一下吧!
效果圖
一.如何使用
1.如何開始
1.從GitHub上Clone-->SKChoosePopView, 然后查看Demo (由于使用cocoaPods管理庞呕,請打開xcworkspace工程進(jìn)行查看)
2.請仔細(xì)閱讀下方特別指出的部分和需要注意問題
3.在項(xiàng)目中使用SKChoosePopView新翎,直接將目錄下的SKChoosePopView文件夾拷貝到工程中程帕,或在podfile文件中添加pod 'SKChoosePopView'
4.SKChoosePopView基于Masonry布局,請確保你的工程里已存在Masonry地啰,下載地址
2.使用方法
頭文件導(dǎo)入
#import "SKPopView.h"
初始化
/** 初始化方法
* @param title 標(biāo)題數(shù)組
* @param iconNormal 默認(rèn)圖標(biāo)數(shù)組
* @param iconSelected 選中圖標(biāo)數(shù)組, 若不需要傳入nil即可
* @param titleColor 選中的選項(xiàng)標(biāo)題字體顏色, 若不需要傳入nil即可
* @param delegate 代理協(xié)議
* @param completion 彈窗出現(xiàn)后的回調(diào)操作愁拭,若不需要傳入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];
設(shè)置動(dòng)畫類型
popView.animationType = SK_TYPE_SPRING;
設(shè)置動(dòng)畫方向
popView.animationDirection = SK_SUBTYPE_FROMBOTTOM;
動(dòng)畫時(shí)間
popView.animationDuration = 0.5;
開啟/關(guān)閉選擇記錄
popView.enableRecord = YES;
開啟/關(guān)閉動(dòng)畫效果
popView.enableAnimation = YES;
行數(shù)設(shè)置
popView.optionsLine = 2;
列數(shù)設(shè)置
popView.optionsRow = 3;
最小行間距
popView.minLineSpacing = 10;
最小列間距
popView.minRowSpacing = 10;
動(dòng)畫設(shè)置
SK_TYPE_SPRING,// 彈簧效果
SK_TYPE_ROTATION,// 旋轉(zhuǎn)效果
SK_TYPE_FADE,// 漸變效果
SK_TYPE_LARGEN,// 變大效果
SK_TYPE_ROTATION_LARGEN,// 旋轉(zhuǎn)變大效果
SK_TYPE_TRANSFORMATION// 變形效果
動(dòng)畫進(jìn)場方向
SK_SUBTYPE_FROMRIGHT,// 從右側(cè)進(jìn)入
SK_SUBTYPE_FROMLEFT,// 從左側(cè)進(jìn)入
SK_SUBTYPE_FROMTOP,// 從頂部進(jìn)入
SK_SUBTYPE_FROMBOTTOM,// 從底部進(jìn)入
SK_SUBTYPE_FROMCENTER// 從屏幕中間進(jìn)入
獲取已選擇的選項(xiàng)row(代理協(xié)議方法)
- (void)selectedWithRow:(NSUInteger)row;
注意事項(xiàng)
1.optionsLine
和optionsRow
屬性是必須設(shè)置的, 且遵循垂直布局原則,請確保optionsLine * optionsRow于選項(xiàng)數(shù)量相等
2.最小行亏吝、列間距如不需要可以不設(shè)置岭埠,默認(rèn)為0
3.如果開啟動(dòng)畫,請確保animationType
蔚鸥、animationDirection
和animationDuration
屬性已經(jīng)設(shè)置
4.使用代理協(xié)議方法前惜论,請確保已遵循<SKPopViewDelegate>
5.對于沒有使用cocoaPods
的同學(xué),在將SKChoosePopView拷貝到工程目錄后止喷,請到SKMacro.h
內(nèi)將#import "Masonry.h"
的注釋打開来涨,并注釋掉下面一行的#import <Masonry/Masonry.h>
,以免Xcode編譯報(bào)錯(cuò)
6.如果遇到其它問題启盛,歡迎提交issues蹦掐,我會及時(shí)回復(fù)
二.實(shí)現(xiàn)思路
1.設(shè)計(jì)思路
首先,在總體設(shè)計(jì)上我們采取模塊化的思路僵闯,主要分為兩部分:
SKPopView
(視圖)和SKPopAnimationManage
(動(dòng)畫管理)卧抗,各司其職。SKPopView
負(fù)責(zé)處理界面控件的創(chuàng)建和布局約束鳖粟、外部對彈窗配置信息設(shè)置的響應(yīng)(如是否開啟動(dòng)畫效果社裆、點(diǎn)擊效果、選擇記錄向图、動(dòng)畫類型/方向泳秀、需要顯示的行/列等)、已選擇的選項(xiàng)回調(diào)等榄攀。SKPopAnimationManage
則負(fù)責(zé)動(dòng)畫的相關(guān)管理嗜傅,如對動(dòng)畫進(jìn)場方向的軌跡控制、動(dòng)畫效果的具體實(shí)現(xiàn)等檩赢。
2.功能實(shí)現(xiàn)
布局
在整體上吕嘀,
SKPopView
充當(dāng)了一個(gè)父UIView
的角色,在其內(nèi)部添加grayBackground1
(灰色的背景)和popView
(HUD彈窗部分)贞瞒,總體結(jié)構(gòu)簡單明了偶房,所以在調(diào)用的時(shí)候我們是直接將SKPopView
整個(gè)添加到我們需要顯示的界面當(dāng)中來使用的。在
SKPopView
中军浆,grayBackground
作為灰色背景率先addSubview到SKPopView
中[self addSubview:grayBackground]
棕洋,而彈窗則作為插入部分,插入到grayBackground
之上[self insertSubview:self.popView aboveSubview:grayBackground]
, 而在popView
內(nèi)部乒融,我們內(nèi)嵌了一個(gè)UICollectionView
掰盘,作為選項(xiàng)按鈕的展示摄悯。
彈窗的出現(xiàn)與消失
- 彈窗的出現(xiàn)原理:當(dāng)彈窗被調(diào)用時(shí),
SKPopView
整體便會添加到當(dāng)前視圖的圖層之上庆杜,而彈窗popView
此時(shí)會根據(jù)調(diào)用時(shí)在初始化方法里配置的SK_TYPE(動(dòng)畫類型)和SK_SUBTYPE(動(dòng)畫方向)射众,在屏幕外選擇一個(gè)入場前的坐標(biāo)和入場時(shí)的動(dòng)畫效果,當(dāng)初始化完成開始調(diào)用SKPopView
的show
方法時(shí)叨橱,popView
便開始入場、完成動(dòng)畫罗洗。
#pragma mark - 外部調(diào)用
- (void)show
{
if (self.enableAnimation == YES) {// 如果開啟動(dòng)畫效果
[self displayAnimation];
}
}
#pragma mark - 動(dòng)畫設(shè)置
- (void)displayAnimation
{
self.animationManage = [[SKPopAnimationManage alloc] init];
switch (self.animationType) {// 動(dòng)畫類型
case SK_TYPE_SPRING:
self.animationManage.type = SK_ANIMATION_TYPE_SPRING;
break;
case SK_TYPE_ROTATION:
self.animationManage.type = SK_ANIMATION_TYPE_ROTATION;
break;
case SK_TYPE_FADE:
self.animationManage.type = SK_ANIMATION_TYPE_FADE;
break;
case SK_TYPE_LARGEN:
self.animationManage.type = SK_ANIMATION_TYPE_LARGEN;
break;
case SK_TYPE_ROTATION_LARGEN:
self.animationManage.type = SK_ANIMATION_TYPE_ROTATION_LARGEN;
break;
case SK_TYPE_TRANSFORMATION:
self.animationManage.type = SK_ANIMATION_TYPE_TRANSFORMATION;
break;
}
switch (self.animationDirection) {// 動(dòng)畫進(jìn)場方向
case SK_SUBTYPE_FROMRIGHT:
self.animationManage.animationDirection = SK_ANIMATION_SUBTYPE_FROMRIGHT;
break;
case SK_SUBTYPE_FROMLEFT:
self.animationManage.animationDirection = SK_ANIMATION_SUBTYPE_FROMLEFT;
break;
case SK_SUBTYPE_FROMTOP:
self.animationManage.animationDirection = SK_ANIMATION_SUBTYPE_FROMTOP;
break;
case SK_SUBTYPE_FROMBOTTOM:
self.animationManage.animationDirection = SK_ANIMATION_SUBTYPE_FROMBOTTOM;
break;
case SK_SUBTYPE_FROMCENTER:
self.animationManage.animationDirection = SK_ANIMATION_SUBTYPE_FROMCENTER;
break;
default:
break;
}
// 對進(jìn)場動(dòng)畫進(jìn)行設(shè)置
[self.animationManage animateWithView:self.popView Duration:self.animationDuration animationType:self.animationManage.type animationDirection:self.animationManage.animationDirection];
}
- 由于支持對已選擇的按鈕進(jìn)行記錄保存伙菜,所以在使用上,用戶僅能通過對灰色背景的點(diǎn)擊來達(dá)到讓彈窗消失的目的贩绕,我們通過對
grayBackground
添加點(diǎn)擊手勢來達(dá)到目的, 注意:grayBackground
在這里是一個(gè)UIImageView
,在添加手勢時(shí)淑倾,一定要將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 - 手勢響應(yīng)
- (void)cancel
{
[self dismissAnimation];
}
- (void)dismissAnimation
{
if (self.enableAnimation == YES) {// 如果開啟動(dòng)畫效果
[self.animationManage dismissAnimationForRootView:self];// 使用離場動(dòng)畫
} else {
[self removeFromSuperview];// 直接移除整個(gè)SKPopView
}
}
- 如果需求上有取消按鈕勃救,可以在取消按鈕的點(diǎn)擊方法內(nèi)手動(dòng)調(diào)用
SKPopView
的dismiss
方法碍讨,十分方便。
如
- (void)clickCancel // 取消按鈕方法
{
[popView dismiss];
}```
### 選項(xiàng)的數(shù)量與顯示把控
- 因?yàn)槲覀冊赻popView`中鑲嵌了一個(gè)`UICollectionView`蒙秒,而`UICollectionView`提供了幾個(gè)很好的方法可以讓我們相對輕松的對選項(xiàng)的數(shù)量及在彈窗內(nèi)的顯示方式進(jìn)行把控勃黍,我們主要應(yīng)用了下面幾個(gè)方法
```objectivec
// 設(shè)置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
然后根據(jù)我們在SKPopView
的頭文件中暴露的外部配置信息optionsLine
(顯示的行數(shù))、optionsRow
(顯示的列數(shù))税肪、minLineSpacing
(最小行間距)溉躲、minRowSpacing
(最小列間距),來對具體的樣式進(jìn)行修改益兄,具體轉(zhuǎn)換算法請參考SKPopView.m中外部配置
標(biāo)簽下的代碼段
動(dòng)畫
- 動(dòng)畫部分我們分為:進(jìn)場動(dòng)畫、撤場動(dòng)畫和選項(xiàng)點(diǎn)擊動(dòng)畫三個(gè)部分
1.進(jìn)場動(dòng)畫:
首先我們要弄明白一點(diǎn)箭券,進(jìn)場動(dòng)畫净捅,顧名思義:就是由進(jìn)場+動(dòng)畫兩個(gè)效果部分組成的
由于默認(rèn)集成了來自5個(gè)方向的6種不同效果的動(dòng)畫,導(dǎo)致動(dòng)畫在進(jìn)行相應(yīng)配置時(shí)辩块,對于每種動(dòng)畫效果都需要考慮5種不同的進(jìn)場起始坐標(biāo)蛔六,所以荆永,我們用一個(gè)統(tǒng)一的方法對動(dòng)畫的進(jìn)場方向進(jìn)行統(tǒng)一管理蹦狂,同樣的鸽捻,考慮到一些動(dòng)畫效果需要使用動(dòng)畫組, 對動(dòng)畫在進(jìn)場過程中的位移路徑也需要做一個(gè)統(tǒng)一管理
#pragma mark - 動(dòng)畫方向初始化
- (void)animationDirectionInitialize
{
if (_animationDirection == SK_ANIMATION_SUBTYPE_FROMRIGHT) {// 從右側(cè)進(jìn)場
_animationView.center = CGPointMake(MyWidth + 1000, WindowCenter.y);
} else if (_animationDirection == SK_ANIMATION_SUBTYPE_FROMLEFT) {// 從左側(cè)進(jìn)場
_animationView.center = CGPointMake(MyWidth - 1000, WindowCenter.y);
} else if (_animationDirection == SK_ANIMATION_SUBTYPE_FROMTOP) {// 從頂部進(jìn)場
_animationView.center = CGPointMake(WindowCenter.x, MyHeight - 1000);
} else if (_animationDirection == SK_ANIMATION_SUBTYPE_FROMBOTTOM) {// 從底部進(jìn)場
_animationView.center = CGPointMake(WindowCenter.x, MyHeight + 1000);
} else {// 中間進(jìn)場
_animationView.center = WindowCenter;
}
}
#pragma mark - 動(dòng)畫通用位移
- (CAKeyframeAnimation *)partOfTheAnimationGroupPosition:(CGPoint)startPosition
{
CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];// 創(chuàng)建關(guān)鍵幀位移動(dòng)畫
NSValue * startValue, * endValue;
startValue = [NSValue valueWithCGPoint:startPosition];// 起始坐標(biāo)
endValue = [NSValue valueWithCGPoint:WindowCenter];// 結(jié)束坐標(biāo)
animation.values = @[startValue, endValue];// 設(shè)置動(dòng)畫路徑
animation.duration = _animationDuration;// 持續(xù)時(shí)間
return animation;
}
```
- 其中`彈簧效果`的動(dòng)畫由于不是并發(fā)動(dòng)畫效果简卧,所以是沒有用到`通用位移`的断箫,所以它的位移方法是獨(dú)立出來的
```objectivec
#pragma mark - 彈簧效果
- (void)springAnimation
{
[self animationDirectionInitialize];// 初始化方向
[self displacementWithStartPosition:_animationView.center];
}
/** 彈簧效果位移部分
* @param startPosition 位移起始坐標(biāo)
*/
- (void)displacementWithStartPosition:(CGPoint)startPosition{
CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];// 創(chuàng)建關(guān)鍵幀動(dòng)畫
NSValue * startValue, * endValue;
startValue = [NSValue valueWithCGPoint:startPosition];
endValue = [NSValue valueWithCGPoint:WindowCenter];
animation.values = @[startValue, endValue];
animation.duration = _animationDuration;
animation.delegate = self;// 設(shè)置CAAnimationDelegate
[_animationView.layer addAnimation:animation forKey:@"pathAnimation"];
}
/** 彈簧效果晃動(dòng)部分
*/
- (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;// 是否重復(fù)
animation.repeatCount = 2;// 重復(fù)次數(shù)
[_animationView.layer addAnimation:animation forKey:@"shakeAnimation"];
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag// 監(jiān)聽動(dòng)畫停止
{
[self partOfTheSpringGroupShaking];
}
```
2.撤場動(dòng)畫
- 撤場動(dòng)畫就相對簡單很多, 由于撤場方式是統(tǒng)一的准验,只需要對撤出坐標(biāo)做處理缨历,然后removeFromSuperview
```objectivec
- (void)dismissAnimationForRootView:(UIView *)view
{
[UIView animateWithDuration:0.5 animations:^{
_animationView.center = CGPointMake(WindowCenter.x, MyHeight + 1000);
} completion:^(BOOL finished) {
[view removeFromSuperview];
}];
}
```
3.選項(xiàng)點(diǎn)擊動(dòng)畫
- 當(dāng)點(diǎn)擊動(dòng)畫效果被開啟時(shí)衫贬,才會調(diào)用這個(gè)方法蝴乔,即`enableClickEffect = YES`時(shí), `SKPopViewCollectionViewCell`中會調(diào)用點(diǎn)擊動(dòng)畫
```objectivec
- (void)setEnableClickEffect:(BOOL)enableClickEffect
{
_enableClickEffect = enableClickEffect;
if (enableClickEffect == YES) {
SKPopAnimationManage * animationManage = [[SKPopAnimationManage alloc] init];
[animationManage clickEffectAnimationForView:self.basementView];
}
}
```
而在`SKPopAnimationManger`中四啰,我們只是巧妙的對其做了一個(gè)縮放的效果宁玫,即可達(dá)到果凍式的觸感
```objectivec
- (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];
}
```
# 感謝你花時(shí)間閱讀以上內(nèi)容, 如果這個(gè)項(xiàng)目能夠幫助到你,記得告訴我
Email: shevakuilin@gmail.com
或者直接在文章下留言哦~