先上圖??Demo在最下方
這次主講下拉果凍動畫效果昂勒,自定義Refresh動畫請自行下載最下面的Demo井赌,若有算法上或者本文沒看懂的問題谤逼,可文章下面留言??
1.隱藏自身NavigationBar,自定義View作為NavigationBar完成上圖效果
2.自定義View與UITableView或者UIPanGestureRecognizer聯(lián)動
3.UIBezierPath實現(xiàn)跟隨下拉使View變形
4.松手后回彈效果(CADisplayLink實現(xiàn))
明白上面4點后仇穗,下面的內容你可能會好消化一點=流部。=
如上圖完成這個效果的重點在于用這5個點作為Path來完成一系列效果。而從Gif中不難看出仪缸,3號點是一個關鍵點贵涵,可以把它間接的理解為UIBezierPath中的ControlPoint(事實不是,后文解釋)恰画。
這里創(chuàng)建一個Layer作為容器來承載Path
@property (nonatomic, strong) CAShapeLayer *shapeLayer;
self.shapeLayer = [CAShapeLayer layer];
self.shapeLayer.fillColor = RGB(252, 157, 154).CGColor;
[self.layer addSublayer:self.shapeLayer];
然后創(chuàng)建一個ControlPoint宾茂,為了達到自然的效果,因為下拉變形距離如果跟隨手勢拖動位置直接變化會顯得很不自然拴还、突兀跨晴,所以用一個ControlPoint來比例縮小手勢拉動距離(這里再創(chuàng)建一個像素View來更直觀的看效果),并且給ControlPoint添加KVO觀察Value的變化來更新Path的變化
@property (nonatomic) CGPoint endAnimatePoint;
@property (nonatomic, strong) UIView *referencePointView;
self.referencePointView = [[UIView alloc] initWithFrame:CGRectMake(CGRectGetWidth(self.frame)/2.f-2, CGRectGetMaxY(self.frame)-2, 4, 4)];
self.referencePointView.backgroundColor = [UIColor redColor];
[self addSubview:self.referencePointView];
#define KVO_EndPoint @"endAnimatePoint"
- (void)setupKVO {
[self addObserver:self forKeyPath:KVO_EndPoint options:NSKeyValueObservingOptionNew context:nil];
self.endAnimatePoint = CGPointMake(CGRectGetWidth(self.frame)/2.f-2, CGRectGetMaxY(self.frame)-2);
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if ([keyPath isEqualToString:KVO_EndPoint]) {
[self updateShapeLayerPath];
}
}
從上圖可以看到ControlPoint的作用,方便理解
這里我用UITableView聯(lián)動作為例子(直接用UIPanGestureRecognizer就是這個版本的簡化)
[self.jellyTableView.panGestureRecognizer addTarget:self action:@selector(handlePanAction:)];
- (void)handlePanAction:(UIPanGestureRecognizer *)pan {
CGPoint point = [pan translationInView:self.view];
[self.customNavigationBar animateStateChangeAtX:point.x y:point.y];
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
[self.customNavigationBar animateStateEnd];
}
- (void)animateStateChangeAtX:(CGFloat)x y:(CGFloat)y {
if (!self.isAnimating) {
if ((y*0.7 + CGRectGetHeight(self.frame)) > CGRectGetHeight(self.frame)) {
//減緩手勢變化達到更自然的效果
CGFloat mHeight = y*0.7 + CGRectGetHeight(self.frame);
CGFloat mWidth = x + CGRectGetWidth(self.frame)/2.f;
//改變ControlPoint的Value來執(zhí)行KVO的方法
self.endAnimatePoint = CGPointMake(mWidth-2, mHeight-2);
self.referencePointView.frame = CGRectMake(mWidth-2, mHeight-2, 4, 4);
self.titleLabel.alpha = (Refresh_Height-y)/Refresh_Height;
self.loadingAnimationView.alpha = y/Refresh_Height;
}
if (y > Refresh_Height) {
self.refreshState = YES;
}
else {
self.refreshState = NO;
}
}
}
接下來是果凍效果的核心代碼 注釋都在代碼里
- (void)updateShapeLayerPath {
UIBezierPath *tPath = [UIBezierPath bezierPath];
//移動點到1
[tPath moveToPoint:CGPointMake(0, 0)];
//連接1-5的線
[tPath addLineToPoint:CGPointMake(CGRectGetWidth(self.frame), 0)];
//連接5-4的線
[tPath addLineToPoint:CGPointMake(CGRectGetWidth(self.frame), CGRectGetHeight(self.frame))];
//連接4-2的線 其中3點用ConrolPoint來作為控制點連接
[tPath addQuadCurveToPoint:CGPointMake(0, CGRectGetHeight(self.frame)) controlPoint:CGPointMake(self.endAnimatePoint.x, self.endAnimatePoint.y)];
[tPath closePath];
self.shapeLayer.path = tPath.CGPath;
}
到此為止下拉已經可以看到效果了
接下來要實現(xiàn)回彈效果
這里我要用到CADisplayLink片林,這是作為跟著屏幕的刷新率更新界面UI的一個類似Timer的東西端盆,詳細屬性和作用簡書其他文章里也有很詳細的介紹,這里就不多做解釋了
@property (nonatomic, strong) CADisplayLink *displayLink;
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updatePath:)];
self.displayLink.paused = YES;
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
- (void)updatePath:(CADisplayLink *)displayLink {
CALayer *layer = self.referencePointView.layer.presentationLayer;
self.endAnimatePoint = CGPointMake(layer.position.x, layer.position.y);
}
//這里我要提一嘴费封,Mode一定要選擇NSRunLoopCommonModes焕妙,原因可以自行搜索一下NSRunLoopCommonModes和NSDefaultRunLoopMode的區(qū)別,若選擇后者會導致動畫看不到或者只能看到最后一部分
這里CADisplayLink的作用就是弓摘,在我們松開手的那個刻焚鹊,拿到最后一個ControlPoint,在利用系統(tǒng)自帶的+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);
方法韧献,來做一個ControlPoint回到初始位置并帶有彈性效果的動畫末患,方法中usingSpringWithDamping
屬性可以理解為是用來控制彈性動畫的,這樣就可以做到隨著屏幕的刷新率锤窑,一直改變ControlPoint的位置直到回到原點動畫結束
- (void)animateStateEnd {
if (!self.isAnimating) {
self.isAnimating = YES;
self.displayLink.paused = NO;
[UIView animateWithDuration:1 delay:0 usingSpringWithDamping:0.5 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.referencePointView.frame = CGRectMake(CGRectGetWidth(self.frame)/2.f-2, CGRectGetMaxY(self.frame)-2, 4, 4);
if (!self.refreshState) {
self.titleLabel.alpha = 1;
self.loadingAnimationView.alpha = 0;
}
else {
self.titleLabel.alpha = 0;
self.loadingAnimationView.alpha = 1;
}
} completion:^(BOOL finished) {
if (finished) {
self.isAnimating = NO;
self.displayLink.paused = YES;
}
}];
if (self.refreshState) {
[self.loadingAnimationView startAnimate];
[self performSelector:@selector(stopAnimate) withObject:nil afterDelay:5];
}
}
}
到這里位置下拉刷新果凍/彈力效果就完成了