前言
為什么我們要做這個(gè)效果裝逼炫技??沒有別的了,廢話少說先看看效果

下方的箭頭主要是來確定,整個(gè)控件的高度,是隨著折疊而變化的。
分析
1、首先遇到問題還是先分析。整個(gè)控件是一個(gè)折疊容器,然后容器里有很多個(gè)小的可以折疊的控件,小的折疊控件,折疊的時(shí)候我們把錨點(diǎn)移到y(tǒng)=0,然后在旋轉(zhuǎn)180°

2、在每一個(gè)可旋轉(zhuǎn)的控件我們首先要做的是截圖,把整個(gè)一個(gè)可能很復(fù)雜的控件變?yōu)橐粡?code>imageview這樣方便我們待會(huì)做旋轉(zhuǎn)。
3、要做出“翻過來”的效果我這里是使用一個(gè)模糊效果的
imageview加載上面,旋轉(zhuǎn)180°的過程中并不是一次性完成的,因?yàn)槲覀內(nèi)绻峭险郫B,旋轉(zhuǎn)到90°的時(shí)候我們應(yīng)該是看到控件的背部,所以我們在旋轉(zhuǎn)90°動(dòng)畫完成的后,要把“背部”放到最前面,看上去就像翻過去了。大致的分析就到這兒,其中還有很多細(xì)節(jié)需要處理
準(zhǔn)備
- 第一步我們要配置好折疊容器中的可折疊元素,(這個(gè)demo中的0、1、2、3、4)
/**
配置折疊元素
*/
- (void)configurationFoldItem {
self.unfoldArrayM = [NSMutableArray arrayWithCapacity:self.itemCount];
for (int i = 0; i < self.itemCount; i ++ ) {
CGRect rect = CGRectMake(0 , i * self.itemHeight, self.itemWidth, self.itemHeight);
UIImage *image = [self ai_takeSnapshotWithFrame:rect];
AIFoldRotatedView *rotatedView = [[AIFoldRotatedView alloc]initWithFrame:rect Image:image];
// rotatedView.layer.anchorPoint = CGPointMake(.5, 0);
// //測試
// rotatedView.tag = i+100;
rotatedView.delegate = self;
[self addSubview:rotatedView];
//添加到可折疊數(shù)組中
[self.itemArrayM addObject:rotatedView];
//保存到展開數(shù)組中
[self.unfoldArrayM addObject:[NSValue valueWithCGRect:rect]];
}
self.contentView.alpha = 0.;
}
實(shí)現(xiàn)
在自定義一個(gè)控件的時(shí)候我們不要立馬去實(shí)現(xiàn)某一個(gè)方法,而是先想一下我們需要什么屬性,需要什么方法,每一個(gè)方法需要哪些對應(yīng)的參數(shù)。我覺得這才是一個(gè)面向?qū)ο缶幊痰乃枷搿?br>
我們需要讓旋轉(zhuǎn)控件做展開或者折疊的效果,所以我們要有展開和折疊的方法,展開是要時(shí)間的,所以參數(shù)有duration,我們的折疊容器中可折疊的控件不止一個(gè)要做到一個(gè)控件折疊完成后再折疊第二個(gè)所以我們有參數(shù)delay
/**
旋轉(zhuǎn)180度
@param duration 持續(xù)時(shí)長
@param delay 延時(shí)
*/
- (void)foldingAnimationMI_PWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay;
/**
展開旋轉(zhuǎn)180度
@param duration 持續(xù)時(shí)長
@param delay 延時(shí)
*/
- (void)unfoldingAnimationMI_PWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay;
在將要折疊和折疊完成后,我們需要做一些事情,比如修改折疊容器的高度或者說修改狀態(tài),我這里是使用協(xié)議代理實(shí)現(xiàn)的
@protocol AIFoldRotatedViewDelegate<NSObject>
/**
折疊完成后回調(diào)
@param roatatedView 折疊控件
@param anim anim description
@param flag flag description
*/
- (void)foldRotatedView:(AIFoldRotatedView*)roatatedView animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;
/**
展開完成后回調(diào)
@param roatatedView 折疊控件
@param anim anim description
@param flag flag description
*/
- (void)unfoldRotatedView:(AIFoldRotatedView*)roatatedView animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;
/**
將要折疊回調(diào)
@param roatatedView 折疊控件
*/
- (void)willfoldRotatedView:(AIFoldRotatedView*)roatatedView;
/**
將要展開回調(diào)
@param roatatedView 折疊控件
*/
- (void)willUnfoldRotatedView:(AIFoldRotatedView*)roatatedView;
@end
在我們剛才大致的分析了之后,現(xiàn)在我們看如何實(shí)現(xiàn)這些方法
其中最最核心的就是我們要把這個(gè)控件旋轉(zhuǎn)一個(gè)角度,不管是展開還是折疊都是旋轉(zhuǎn)動(dòng)畫所以我旋轉(zhuǎn)動(dòng)畫封裝成一個(gè)方法
/**
旋轉(zhuǎn)動(dòng)畫
@param timing 節(jié)奏
@param from 開始
@param to 結(jié)束
@param duration 持續(xù)時(shí)長
@param delay 延時(shí)
*/
- (CABasicAnimation*)rotationAnimationTiming:(NSString *)timing from:(CGFloat)from to:(CGFloat)to duration:(NSTimeInterval)duration delay:(NSTimeInterval)delay {
CABasicAnimation *rotateAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.x"];
rotateAnimation.timingFunction = [CAMediaTimingFunction functionWithName:timing];
rotateAnimation.fromValue = @(from);
rotateAnimation.toValue = @(to);
rotateAnimation.duration = duration;
rotateAnimation.delegate = self;
rotateAnimation.fillMode = kCAFillModeForwards;
rotateAnimation.removedOnCompletion = NO;
rotateAnimation.beginTime = CACurrentMediaTime() + delay;
return rotateAnimation;
}
接下來我以折疊為例,展開的方法和折疊類似
/**
折疊旋轉(zhuǎn)180度
@param duration 持續(xù)時(shí)長
@param delay 延時(shí)
*/
- (void)foldingAnimationMI_PWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay {
[self clearTransform];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.layer.position = CGPointMake(CGRectGetMidX(self.frame), self.ai_y );
self.layer.anchorPoint = CGPointMake(.5, 0);
if (self.delegate && [self.delegate respondsToSelector:@selector(willfoldRotatedView:)]) {
[self.delegate willfoldRotatedView:self ];
}else {
AILog(@"未設(shè)置代理");
}
});
CABasicAnimation *animation1Layer = [self rotationAnimationTiming:kCAMediaTimingFunctionEaseIn from:0 to:M_PI_2 duration:duration * .5 delay:delay ];
[animation1Layer setValue:@"foldstarAnimation" forKey:@"name"];
[self.layer addAnimation:animation1Layer forKey:@"animation1"];
}
1、大家可以看到我這里并沒有旋轉(zhuǎn)180°,而是旋轉(zhuǎn)的90°這是因?yàn)槲以谇懊嬷v的我們要實(shí)現(xiàn)折疊到一半,我們應(yīng)該看到的是控件的"背面",這里我動(dòng)畫拿到了添加了key/value就是為了確定動(dòng)畫結(jié)束的地方(這個(gè)技巧我在下載按鈕|動(dòng)畫中也有使用)
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
NSString *name = [anim valueForKey:@"name"];
if ([name isEqualToString:@"foldstarAnimation"]) {//折疊到90°
// 讓backView到最前面來
[self bringSubviewToFront:self.backView];
CABasicAnimation *foldendAnimation = [self rotationAnimationTiming:kCAMediaTimingFunctionEaseOut from:M_PI_2 to:M_PI duration:anim.duration delay:0 ];
[foldendAnimation setValue:@"foldendAnimation" forKey:@"name"];
[self.layer addAnimation:foldendAnimation forKey:nil];
}else if([name isEqualToString:@"foldendAnimation"]){ //折疊完成
// [self.layer removeAllAnimations];
[self rotatedXWithAngle:0];
[self clearTransform];
if (self.delegate && [self.delegate respondsToSelector:@selector(foldRotatedView:animationDidStop:finished:)]) {
[self.delegate foldRotatedView:self animationDidStop:anim finished:flag];
}else {
AILog(@"未設(shè)置代理");
}
}
}
當(dāng)折疊到90°的時(shí)候我們把背景圖,放到前面來,形成一個(gè)"看到控件背后的效果",整個(gè)180°旋轉(zhuǎn)完成后調(diào)用折疊完成的回調(diào)函數(shù),這里有點(diǎn)要注意,我們要清理layer的transform因?yàn)槲覀兪怯卸鄠€(gè)可旋轉(zhuǎn)的控件,還原transform在下一個(gè)控件旋轉(zhuǎn)的時(shí)候錨點(diǎn)就可能出問題,這個(gè)特別是在展開的時(shí)候體現(xiàn)非常明顯
2、一個(gè)控件折疊完成后,需要修改的他的frame和把已經(jīng)折疊的這個(gè)控件放在,讓已經(jīng)折疊這個(gè)控件成為上一個(gè)控件的子控件
最后折疊完成后層級關(guān)系應(yīng)該是這樣

/**
一個(gè)疊完成后回調(diào)
@param roatatedView 折疊視圖
@param anim anim description
@param flag flag description
*/
-(void)foldRotatedView:(AIFoldRotatedView *)roatatedView animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
[roatatedView.layer removeAllAnimations];
NSInteger index = [self.itemArrayM indexOfObject:roatatedView];
self.descView = self.itemArrayM[index-1];
roatatedView.frame = self.descView.bounds;
[self.descView addSubview:roatatedView];
}
GitHub上查看源碼,你的star是我最大的支持

參考文獻(xiàn)
參考開源庫:popping
folding-cell
參考博客:http://blog.sina.com.cn/s/blog_8f5097be0101b91z.html