前言
为什么我们要做这个效果装逼炫技😁没有别的了,废话少说先看看效果
下方的箭头主要是来确定,整个控件的高度,是随着折叠而变化的。
分析
1、首先遇到问题还是先分析。整个控件是一个折叠容器,然后容器里有很多个小的可以折叠的控件,小的折叠控件,折叠的时候我们把锚点移到y=0,然后在旋转180°
2、在每一个可旋转的控件我们首先要做的是截图,把整个一个可能很复杂的控件变为一张
imageview
这样方便我们待会做旋转。3、要做出“翻过来”的效果我这里是使用一个模糊效果的
imageview
加载上面,旋转180°的过程中并不是一次性完成的,因为我们如果是往上折叠,旋转到90°的时候我们应该是看到控件的背部,所以我们在旋转90°动画完成的后,要把“背部”放到最前面,看上去就像翻过去了。大致的分析就到这儿,其中还有很多细节需要处理
准备
- 第一步我们要配置好折叠容器中的可折叠元素,(这个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];
//添加到可折叠数组中
[self.itemArrayM addObject:rotatedView];
//保存到展开数组中
[self.unfoldArrayM addObject:[NSValue valueWithCGRect:rect]];
}
self.contentView.alpha = 0.;
}
实现
在自定义一个控件的时候我们不要立马去实现某一个方法,而是先想一下我们需要什么属性,需要什么方法,每一个方法需要哪些对应的参数。我觉得这才是一个面向对象编程的思想。
我们需要让旋转控件做展开或者折叠的效果,所以我们要有展开和折叠的方法,展开是要时间的,所以参数有duration
,我们的折叠容器中可折叠的控件不止一个要做到一个控件折叠完成后再折叠第二个所以我们有参数delay
/**
旋转180度
@param duration 持续时长
@param delay 延时
*/
- (void)foldingAnimationMI_PWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay;
/**
展开旋转180度
@param duration 持续时长
@param delay 延时
*/
- (void)unfoldingAnimationMI_PWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay;
在将要折叠和折叠完成后,我们需要做一些事情,比如修改折叠容器的高度或者说修改状态,我这里是使用协议代理实现的
@protocol AIFoldRotatedViewDelegate<NSObject>
/**
折叠完成后回调
@param roatatedView 折叠控件
@param anim anim description
@param flag flag description
*/
- (void)foldRotatedView:(AIFoldRotatedView*)roatatedView animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;
/**
展开完成后回调
@param roatatedView 折叠控件
@param anim anim description
@param flag flag description
*/
- (void)unfoldRotatedView:(AIFoldRotatedView*)roatatedView animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;
/**
将要折叠回调
@param roatatedView 折叠控件
*/
- (void)willfoldRotatedView:(AIFoldRotatedView*)roatatedView;
/**
将要展开回调
@param roatatedView 折叠控件
*/
- (void)willUnfoldRotatedView:(AIFoldRotatedView*)roatatedView;
@end
在我们刚才大致的分析了之后,现在我们看如何实现这些方法
其中最最核心的就是我们要把这个控件旋转一个角度,不管是展开还是折叠都是旋转动画所以我旋转动画封装成一个方法
/**
旋转动画
@param timing 节奏
@param from 开始
@param to 结束
@param duration 持续时长
@param delay 延时
*/
- (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;
}
接下来我以折叠为例,展开的方法和折叠类似
/**
折叠旋转180度
@param duration 持续时长
@param delay 延时
*/
- (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(@"未设置代理");
}
});
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、大家可以看到我这里并没有旋转180°,而是旋转的90°这是因为我在前面讲的我们要实现折叠到一半,我们应该看到的是控件的"背面",这里我动画拿到了添加了key/value就是为了确定动画结束的地方(这个技巧我在下载按钮|动画中也有使用)
- (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(@"未设置代理");
}
}
}
当折叠到90°的时候我们把背景图,放到前面来,形成一个"看到控件背后的效果",整个180°旋转完成后调用折叠完成的回调函数,这里有点要注意,我们要清理layer
的transform
因为我们是有多个可旋转的控件,还原transform
在下一个控件旋转的时候锚点就可能出问题,这个特别是在展开的时候体现非常明显
2、一个控件折叠完成后,需要修改的他的frame和把已经折叠的这个控件放在,让已经折叠这个控件成为上一个控件的子控件
最后折叠完成后层级关系应该是这样
/**
一个叠完成后回调
@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是我最大的支持
参考文献
参考开源库:popping
folding-cell
参考博客:http://blog.sina.com.cn/s/blog_8f5097be0101b91z.html