iOS 动画十三:Stroke and Path Animations

1,457 阅读1分钟

这里我们实现一个 stroke and path animations 下拉刷新动画。最后效果如下:

pull-to-refresh animation

1. 创建虚线圆
ovalShapeLayer.strokeColor = UIColor.white.cgColor
    ovalShapeLayer.fillColor = UIColor.clear.cgColor
    ovalShapeLayer.lineWidth = 4.0
    // 虚线组成为2单位实线3单位虚线
    ovalShapeLayer.lineDashPattern = [2, 3] // alternates between a 2-user-space-unit-long painted segment and a 3-user-space-unit-long unpainted segment
    
    let refreshRadius = frame.size.height/2 * 0.8
    
    ovalShapeLayer.path = UIBezierPath(ovalIn: CGRect(
        x: frame.size.width/2 - refreshRadius,
        y: frame.size.height/2 - refreshRadius,
        width: 2 * refreshRadius,
        height: 2 * refreshRadius)).cgPath
    
    layer.addSublayer(ovalShapeLayer)
    
    let airplaneImage = UIImage(named: "airplane.png")!
    airplaneLayer.contents = airplaneImage.cgImage
    airplaneLayer.bounds = CGRect(x: 0.0, y: 0.0,width: airplaneImage.size.width, height: airplaneImage.size.height)
    
    airplaneLayer.position = CGPoint(
        x: frame.size.width/2 + frame.size.height/2 * 0.8, y: frame.size.height/2)
    layer.addSublayer(airplaneLayer)
    
    airplaneLayer.opacity = 0.0
2. 实现 Path Animations
func beginRefreshing() {
    isRefreshing = true
    
    UIView.animate(withDuration: 0.3) {
      var newInsets = self.scrollView.contentInset
      newInsets.top += self.frame.size.height
      self.scrollView.contentInset = newInsets
    }
    
    let strokeStartAnimation = CABasicAnimation(keyPath: "strokeStart")
    strokeStartAnimation.fromValue = -0.5
    strokeStartAnimation.toValue = 1.0
    
    let strokeEndAnimation = CABasicAnimation(keyPath: "strokeEnd")
    strokeEndAnimation.fromValue = 0.0
    strokeEndAnimation.toValue = 1.0
    
    let strokeAnimationGroup = CAAnimationGroup()
    strokeAnimationGroup.duration = 1.5
    strokeAnimationGroup.repeatDuration = 5.0
    strokeAnimationGroup.animations = [strokeStartAnimation, strokeEndAnimation]
    ovalShapeLayer.add(strokeAnimationGroup, forKey: nil)
    
    let flightAnimation = CAKeyframeAnimation(keyPath: "position")
    flightAnimation.path = ovalShapeLayer.path
    flightAnimation.calculationMode = kCAAnimationPaced
    
    let airplaneOrientationAnimation = CABasicAnimation(keyPath: "transform.rotation")
    airplaneOrientationAnimation.fromValue = 0
    airplaneOrientationAnimation.toValue = 2.0 * .pi
    
    let flightAnimationGroup = CAAnimationGroup()
    flightAnimationGroup.duration = 1.5
    flightAnimationGroup.repeatDuration = 5.0
    flightAnimationGroup.animations = [flightAnimation,airplaneOrientationAnimation]
    airplaneLayer.add(flightAnimationGroup, forKey: nil)
  }

func endRefreshing() {
    
    isRefreshing = false
    
    UIView.animate(withDuration: 0.3, delay:0.0, options: .curveEaseOut,
      animations: {
        var newInsets = self.scrollView.contentInset
        newInsets.top -= self.frame.size.height
        self.scrollView.contentInset = newInsets
      },
      completion: {_ in
        //finished
      }
    )
  }
  
  func redrawFromProgress(_ progress: CGFloat) {
     ovalShapeLayer.strokeEnd = progress
     airplaneLayer.opacity = Float(progress)
  }


3. strokeStart和strokeEnd 注释

可以把strokeStart理解成一个橡皮擦,如果我们让strokeStart从0到1的话 那么这个线就会被从(0,0)一直檫除到(100,0)

可以把strokeEnd理解为一个画笔,strokeEnd从0 动画到 1 那么动画表现为线越来越长。

eg. 对勾动画

先把路径用贝赛尔曲线画出来,然后用strokeEnd做动画:

#pragma mark -- 成功的路径
-(CGPathRef)getSuccessPath{
    
    // 圆
    UIBezierPath *ciclePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.frame.size.width/2,self.frame.size.height/2) radius:0.8*self.currentSize.width/2 startAngle:M_PI * 3 / 2 endAngle:M_PI * 7 / 2 clockwise:YES];
    // 对勾
    CGFloat W = self.frame.size.width *0.9;
    CGFloat H = self.frame.size.height*0.9;
    UIBezierPath *subPath  = [UIBezierPath bezierPath];
    [subPath moveToPoint:CGPointMake(W/4,H/2)];
    [subPath addLineToPoint:CGPointMake(W/2,(H/2)+ H/4)];
    [subPath addLineToPoint:CGPointMake(W-(W/8),H/4)];
    
    // 添加到路径上 由于CAShapeLayer只能添加一条路径  幸好贝赛尔曲线有个appendPath: 方法 可以让我们画多条路径
    [ciclePath appendPath:subPath];
    return ciclePath.CGPath;
}

#pragma mark -- 成功的动画
- (CABasicAnimation*)successAnimtion{
    
    // 第一个满圆旋转
    CABasicAnimation *aniamtion1 = [CABasicAnimation animation];
    aniamtion1.keyPath = @"strokeEnd";
    aniamtion1.fromValue = @0;
    aniamtion1.toValue = @1;
    aniamtion1.duration = 1.5;
    
    // 这个是缓冲函数
    // 可以自定义
    //
//    aniamtion1.timingFunction = [CAMediaTimingFunction functionWithControlPoints:1 :1 :1 :1];
    
    aniamtion1.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    aniamtion1.fillMode = kCAFillModeBackwards;
    return aniamtion1;
   }

demo下载

参考:

  1. strokeStart和strokeEnd

感谢 @谢微一直都得踢足球