美文网首页iOS DeveloperiOS程序猿
[iOS 动画]徒手做一个会动的icon

[iOS 动画]徒手做一个会动的icon

作者: stephenwzl | 来源:发表于2016-11-21 14:26 被阅读326次

    难度:⭐️
    最终效果:

    最终效果最终效果

    在平时堆UI的时候,避免不了要碰到UED要给我们出一点点难题,比如这次又叫我抄一下淘宝的下拉刷新了(手动斜眼)。
    说干就干,首先新建一个View封装所有的代码:

    import UIKit
    
    class AnimatedArrow: UIView {  
      override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
      }
    }
    

    init中调用我们添加子视图的代码。这时候来分析一下我们需要添加点什么:

    • 一个画圆圈的layer
    • 一个包含箭头的layer

    其中箭头这个图层还要分为左中右三根直线。
    我们先来画这个圆圈, 我们需要一个CAShapeLayer,把这个layer的frame设置一个正方形,然后利用贝塞尔曲线在这个正方形的Rect中画一个圆,把这个layer的路径设置为圆的路径。
    然后设置一下layer基本的填充色、描边色、线帽(线的起点和终点两端的形状)。
    为了做圆形的填充动画(其实就是描边),我们需要把描边的起点和终点设置一下。起点为0,终点由代码控制,设置为self.progress.

    稍微注意一下UIKit Graphics 方法和 Core Graphics方法的区别,Core Graphics是从macOS继承下来的,UIGraphics是iOS独有的一套。所以贝塞尔曲线类UIBezierPath是在UIKit里面的。

    var circleLayer:CAShapeLayer?
      func loadCircleLayer() {
        let layer = CAShapeLayer()
    
        //incase self.bounds.size is not a square
        let radius = min(self.bounds.width, self.bounds.height)
        let frame = CGRect(x: 0, y: 0, width: radius, height: radius)
        layer.frame = frame
        layer.strokeColor = UIColor.black.cgColor
        layer.fillColor = UIColor.clear.cgColor
        
        let path = UIBezierPath(ovalIn: frame)
        layer.path = path.cgPath
        layer.lineWidth = 1
        layer.lineCap = kCALineCapRound
        layer.strokeStart = 0
        layer.strokeEnd = self.progress
        
        self.layer.addSublayer(layer)
        self.circleLayer = layer
      }
    

    再来看一下self.progress,复写了一下它的set方法,在set的时候赋值并且改变circleLayer的描边终点。至于最大值是0.95,那是为了留一个缺口,好看出旋转动画。

    private var _progress:CGFloat = 0  
    var progress:CGFloat {
        get {
          return _progress
        }
        set {
          _progress = newValue
          self.circleLayer?.strokeEnd = min(0.95, newValue)
        }
     }
    

    现在我们来画中间的这个箭头,首先判断一下比例:

    可以看出来中间这根线大概是边长的二分之一,然后水平垂直都居中。我们来创建一下它的路径:

    为什么有个 0.5 ? 因为线宽1,为了居中,就要左偏移0.5

    func middlePath() -> CGPath {
        let width = self.bounds.size.width / 2;
        let path = UIBezierPath()
        path.move(to: CGPoint(x: width - 0.5, y: width / 2))
        path.addLine(to: CGPoint(x: width - 0.5, y: 3 * width / 2))
        return path.cgPath
    }
    

    左边和右边这两条线起点是中间线的下端点,终点是左右一半的水平终点和垂直居中处,那就好办了,画一下他们俩的路径:

      func leftPath() -> CGPath {
        let width = self.bounds.size.width / 2;
        let path = UIBezierPath()
        path.move(to: CGPoint(x: width / 2, y: width))
        path.addLine(to: CGPoint(x: width - 0.5, y: 3 * width / 2))
        return path.cgPath
      }
      
      func rightPath() -> CGPath {
        let width = self.bounds.size.width / 2;
        let path = UIBezierPath()
        path.move(to: CGPoint(x: 3 * width / 2, y: width))
        path.addLine(to: CGPoint(x: width - 0.5, y: 3 * width / 2))
        return path.cgPath
      }
      
    

    现在,根据这三条路径创建图层:

      func templateLayer(path:CGPath) -> CAShapeLayer {
        let layer = CAShapeLayer()
        layer.frame = self.bounds
        layer.strokeColor = UIColor.black.cgColor
        layer.path = path
        layer.lineWidth = 1
        layer.lineCap = kCALineCapRound
        layer.fillColor = UIColor.clear.cgColor
        return layer
      }
      
      var containerLayer:CALayer?
      func loadArrowLayer() {
        self.containerLayer = CALayer()
        self.containerLayer?.frame = self.bounds
        self.containerLayer?.addSublayer(templateLayer(path: middlePath()))
        self.containerLayer?.addSublayer(templateLayer(path: leftPath()))
        self.containerLayer?.addSublayer(templateLayer(path: rightPath()))
        self.layer.addSublayer(self.containerLayer!)
      }  
    

    然后把它们组合起来:

    func commonInit() {
        loadCircleLayer()
        loadArrowLayer()
    }
    

    创建动画:
    圆形的填充progress是根据操作进行的,这个我们可以通过监听UIScrollView的contentOffset实现。还有一个就是箭头的隐藏和转圈,几行代码就可以了:

      func startAnimation() {
        self.containerLayer?.isHidden = true
        let animation = CABasicAnimation(keyPath: "transform.rotation.z")
        animation.fromValue = 0
        animation.toValue = M_PI * 2
        animation.duration = 1
        animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
        animation.repeatCount = Float.infinity
        self.circleLayer?.add(animation, forKey: "infinity_rotate")
      }
      
      func stopAnimation() {
        self.containerLayer?.isHidden = false
        self.circleLayer?.removeAnimation(forKey: "infinity_rotate")
      }
    

    剩下要做的事情就是把这些操作组合一下,比如只在 progress 为0.95的时候才能触发 startAnimation() 这样可以避免视图显示不全。
    最后,我把这个View添加到视图中,通过 set progress 来进行填充,然后调用动画就可以了。

    - EOF -

    相关文章

      网友评论

        本文标题:[iOS 动画]徒手做一个会动的icon

        本文链接:https://www.haomeiwen.com/subject/iudupttx.html