iOS - 用 UIBezierPath 實現果凍效果

最近在網上看到一個很酷的下拉刷新效果(http://iostuts.io/2015/10/17/elastic-bounce-using-uibezierpath-and-pan-gesture/)。本身試着實現了一下其中的果凍回彈效果。ios

效果


1.gif

DEMO


邏輯


  • 下圖p1,藍色部分圖形是一個CAShapeLayer,他的形狀由UIBezierPath的路徑組成的。github

  • 這個路徑是由r1,r2,r3,r4,r5這5個紅點肯定的。其中r1,r2,r3,r4都是不動點,惟一能夠動的是r5點api

  • 根據上面的動態圖能夠看出,CAShapeLayer的形狀是隨着r5紅點的移動而相應變化的,因此只要得到r5的座標變化就能夠用UIBezierPath作出相應的路徑,而後就能夠造成相應的形狀。oop

blob.png

實現

初始化CAShapeLayer動畫

- (void)configShapeLayer
{ 
    _shapeLayer = [CAShapeLayer layer];
    _shapeLayer.fillColor = [UIColor colorWithRed:57/255.0 green:67/255.0 blue:89/255.0 alpha:1.0].CGColor;
    [self.layer addSublayer:_shapeLayer];
}

初始化R5點ui

- (void)configCurveView
{
    // _curveView就是r5點
    _curveX = SYS_DEVICE_WIDTH/2.0;       // r5點x座標
    _curveY = MIN_HEIGHT;                 // r5點y座標
    _curveView = [[UIView alloc] initWithFrame:CGRectMake(_curveX, _curveY, 3, 3)];
    _curveView.backgroundColor = [UIColor redColor];
    [self addSubview:_curveView];
}
添加移動手勢&CADisplayLink
- (void)configAction
{
    _mHeight = 100;                       // 手勢移動時相對高度
    _isAnimating = NO;                    // 是否處於動效狀態
 
    // 手勢
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanAction:)];
    self.userInteractionEnabled = YES;
    [self addGestureRecognizer:pan];
 
    // calculatePath方法是算出在運行期間_curveView的座標,從而肯定_shapeLayer的形狀
    _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(calculatePath)];
    [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    // 在手勢結束的時候才調用calculatePath方法,因此一開始是暫停的
    _displayLink.paused = YES;    
}

 

1,手勢移動時,r5紅點跟着手勢移動,_shapeLayer則根據r5的座標來擴大本身的區域spa

2,手勢結束時,r5紅點經過UIView的動畫方法來改變r5的座標,同時_shapeLayer根據r5的座標縮小本身的區域並最終返回原形。code

- (void)handlePanAction:(UIPanGestureRecognizer *)pan
{
    if(!_isAnimating)
    {
        if(pan.state == UIGestureRecognizerStateChanged)
        {
            // 手勢移動時,_shapeLayer跟着手勢向下擴大區域
            CGPoint point = [pan translationInView:self];
 
            // 這部分代碼使r5紅點跟着手勢走
            _mHeight = point.y*0.7 + MIN_HEIGHT;
            _curveX = SYS_DEVICE_WIDTH/2.0 + point.x;
            _curveY = _mHeight > MIN_HEIGHT ? _mHeight : MIN_HEIGHT;
            _curveView.frame = CGRectMake(_curveX,
                                          _curveY,
                                          _curveView.frame.size.width,
                                          _curveView.frame.size.height);
 
            // 根據r5座標,更新_shapeLayer形狀
            [self updateShapeLayerPath];
 
        }
        else if (pan.state == UIGestureRecognizerStateCancelled ||
                 pan.state == UIGestureRecognizerStateEnded ||
                 pan.state == UIGestureRecognizerStateFailed)
        {
            // 手勢結束時,_shapeLayer返回原狀併產生彈簧動效
            _isAnimating = YES;
            _displayLink.paused = NO;           //開啓displaylink,會執行方法calculatePath.
 
            // 彈簧動效
            [UIView animateWithDuration:1.0
                                  delay:0.0
                 usingSpringWithDamping:0.5
                  initialSpringVelocity:0
                                options:UIViewAnimationOptionCurveEaseInOut
                             animations:^{
 
                // 曲線點(r5點)是一個view.因此在block中有彈簧效果.而後根據他的動效路徑,在calculatePath中計算彈性圖形的形狀
                _curveView.frame = CGRectMake(SYS_DEVICE_WIDTH/2.0, MIN_HEIGHT, 3, 3);
 
            } completion:^(BOOL finished) {
 
                if(finished)
                {
                    _displayLink.paused = YES;
                    _isAnimating = NO;
                }
 
            }];
        }
    }
}

 

  • 根據r5的位置,更新_shapeLayer形狀
- (void)updateShapeLayerPath
{
    // 更新_shapeLayer形狀
    UIBezierPath *tPath = [UIBezierPath bezierPath];
    [tPath moveToPoint:CGPointMake(0, 0)];  //r1點
    [tPath addLineToPoint:CGPointMake(SYS_DEVICE_WIDTH, 0)];// r2點
    [tPath addLineToPoint:CGPointMake(SYS_DEVICE_WIDTH,  MIN_HEIGHT)]; //r4點
    [tPath addQuadCurveToPoint:CGPointMake(0, MIN_HEIGHT)
                  controlPoint:CGPointMake(_curveX, _curveY)]; // r3,r4,r5肯定的一個弧線
    [tPath closePath];
    _shapeLayer.path = tPath.CGPath;
}

 

  • 計算彈簧效果座標
- (void)calculatePath
{
    // 因爲手勢結束時,r5執行了一個UIView的彈簧動畫,把這個過程的座標記錄下來,並相應的畫出_shapeLayer形狀
    CALayer *layer = _curveView.layer.presentationLayer;
    _curveX = layer.position.x;
    _curveY = layer.position.y;
    [self updateShapeLayerPath];
}
相關文章
相關標籤/搜索