iOS - 用 UIBezierPath 實現果凍效果

jopen 8年前發布 | 9K 次閱讀 IOS iOS開發 移動開發

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

效果

DEMO

邏輯

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

  • 這個路徑是由r1,r2,r3,r4,r5這5個紅點確定的。其中r1,r2,r3,r4都是不動點, 唯一可以動的是r5點 。

  • 根據上面的動態圖可以看出, CAShapeLayer的形狀是隨著r5紅點的移動而相應變化的 ,所以只要獲得r5的坐標變化就可以用UIBezierPath做出相應的路徑,然后就可以形成相應的形狀。

實現

  • 初始化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點

- (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;    
}
  • 手勢解析

    • 手勢移動時,r5紅點跟著手勢移動,_shapeLayer則根據r5的坐標來擴大自己的區域

    • 手勢結束時,r5紅點通過UIView的動畫方法來改變r5的坐標,同時_shapeLayer根據r5的坐標縮小自己的區域并最終返回原形。

- (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];
}

  • r5點的作用非常重要,因為直接對CAShapeLayer實現動效不太好實現。所以通過對r5點實現彈簧動效,記錄r5點的坐標,再用UIBezierPath形成路徑,最后賦予CAShapeLayer,間接的完成了CAShapeLayer的彈簧動效。

  • 如果你有疑問或者發現錯誤請留言給我。3Q~~

來自: http://www.cocoachina.com/ios/20151231/14823.html

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!