登录按钮的水纹动画

当点击登录按钮时,会以点击点为圆心形成水纹扩散。扩散形状为圆形,扩散颜色为粉色,在扩散过程中按钮的状态为不可点击。

水纹动画拆解为以下5个功能模块

  1. UIButton 坐标获取
  2. Draw 圆形绘制
  3. 定时器刷新
  4. 其他模块
  5. 调用模块

创建基于 UIButton 的 MyButton 类

坐标获取

func startButtonAnimation(_ msenderBt:UIButton,mevent:UIEvent){
   self.isUserInteractionEnabled = false
   let button:UIView = msenderBt as UIView
   let touchSet:NSSet=mevent.touches(for: button)! as NSSet
   var touchArray:[AnyObject] = touchSet.allObjects as [AnyObject]
   let touch1:UITouch = touchArray[0] as! UITouch
   let point1:CGPoint = touch1.location(in: button)
   
   self.circleCenterX = point1.x
   self.circleCenterY = point1.y
   timer = Timer.scheduledTimer(timeInterval: 0.02, target: self,
                                                        selector: #selector(MyButton.timeaction), userInfo: nil, repeats: true);
   RunLoop.main.add(timer!, forMode:RunLoopMode.commonModes)
}

Draw 绘制圆形

override func draw(_ rect: CGRect) {
   let ctx:CGContext = UIGraphicsGetCurrentContext()!
   let endangle:CGFloat = CGFloat(M_PI*2)
   ctx.addArc(center:CGPoint(x:circleCenterX,y:circleCenterY),radius:viewRadius,startAngle:0,endAngle:endangle,clockwise:false)
   let stockColor:UIColor = targetAnimColor
   stockColor.setStroke()
   stockColor.setFill()
   ctx.fillPath()
}

定时器刷新模块

func timeaction(){
   countNum += 1
   
   let dismissTime:DispatchTime = DispatchTime.now() + Double(Int64(0*NSEC_PER_SEC)) / Double(NSEC_PER_SEC)
   DispatchQueue.main.asyncAfter(deadline: dismissTime, execute: {() in
       self.viewRadius += 5
       self.setNeedsDisplay()
       
   })
   if(countNum>50){
       countNum = 0
       viewRadius = 0
       timer?.invalidate()
       DispatchQueue.main.asyncAfter(deadline: dismissTime, execute: {() in
           self.viewRadius = 0
           self.setNeedsDisplay()
           
       })
       self.isUserInteractionEnabled = true
   }
}

init 初始化方法和各参数的初始化

var viewRadius:CGFloat = 0
var countNum:Int = 0
var timer:Timer?
var circleCenterX:CGFloat = 0
var circleCenterY:CGFloat = 0
var targetAnimColor:UIColor = UIColor(red: 216.0 / 255.0, green: 114.0 / 255.0, blue: 213.0 / 255.0, alpha: 0.8)

override init(frame: CGRect) {
   super.init(frame: frame)
   self.backgroundColor = UIColor(red: 50/255.0, green: 185/255.0, blue: 170/255.0, alpha: 1.0)
}
required init(coder aDecoder: NSCoder) {
   super.init(coder: aDecoder)!
}

在 ViewController 调用

let loginButton:MyButton = MyButton(frame: CGRect(x: 20, y: 230, width: self.view.frame.width-20*2,height: 50))
loginButton.setTitle("登陆", for: UIControlState())
loginButton.addTarget(self, action: #selector(ViewController.loginAction(_:event:)), for: UIControlEvents.touchUpInside)
self.view.addSubview(loginButton)
func loginAction(_ sender:UIButton,event:UIEvent) {
   let bt:MyButton = sender as! MyButton
   bt.startButtonAnimation(sender, mevent: event)
}

效果如下:

2017062414983095544093.gif

按钮的渐进圆环动画

按钮渐进动画分析

第一阶段:按钮从长方形到圆角的圆角动画,颜色逐渐消失的颜色淡出动画,以及最后的长方形消失、圆弧显示出来的一系列过程。

第二阶段:动画从圆弧显示开始出来,顺时针绘制圆弧,直至圆弧最终绘制完成。主要涉及到圆弧绘制动画,以及在绘制动画过程时的蓝色填充效果。

第三阶段:从圆弧绘制完成到圆弧伸展成长方形,在伸展过程中,伴随着颜色的变化,圆角动画等基础动画效果。

下面就这三个阶段的动画进行代码实现:

第一阶段动画

创建基于 UIView 的 ButtonView 类

let FreshBlue:UIColor = UIColor(red: 25.0 / 255.0, green: 155.0 / 255.0, blue: 200.0 / 255.0, alpha: 1.0)
let FreshGreen:UIColor = UIColor(red: 150.0 / 255.0, green: 203.0 / 255.0, blue: 25.0 / 255.0, alpha: 1.0)
var view:UIView?
var viewborder:UIView?
var button_x:CGFloat = 0
var button_y:CGFloat = 0
var button_w:CGFloat = 0
var button_h:CGFloat = 0
var label:UILabel?
var circleView:CircleView?

 override init(frame: CGRect) {
   super.init(frame: frame)
   button_x = frame.origin.x;
   button_y = frame.origin.y;
   button_w = frame.size.width;
   button_h = frame.size.height;
   view = UIView(frame:CGRect(x: 0, y: 0, width: button_w, height: button_h))
   view!.backgroundColor = FreshBlue;
   viewborder = UIView(frame:CGRect(x: 0, y: 0, width: button_w, height: button_h))
   viewborder!.backgroundColor = UIColor.clear
   viewborder!.layer.borderColor = FreshBlue.cgColor;
   viewborder!.layer.borderWidth = 3.0;
   self.addSubview(view!)
   self.addSubview(viewborder!)

   circleView = CircleView(frame:CGRect(x: 0, y: 0, width: button_w, height: button_h))
   circleView!.delegate = self as CircleDelegate
   self.addSubview(circleView!)
   
   label = UILabel(frame:CGRect(x: 0, y: 0, width: button_w, height: button_h))
   label!.text = "UpLoad"
   label!.textAlignment = NSTextAlignment.center
   label!.textColor = UIColor.white
   label!.font = UIFont.systemFont(ofSize: 20.0)
   self .addSubview(label!)
}
func startAnimation(){
        
   label?.removeFromSuperview()
   let animMakeBigger:CABasicAnimation = CABasicAnimation()
   animMakeBigger.keyPath = "cornerRadius"
   animMakeBigger.fromValue=5.0
   animMakeBigger.toValue=button_h/2.0
   let animBounds:CABasicAnimation = CABasicAnimation()
   animBounds.keyPath = "bounds"
   animBounds.toValue = NSValue(cgRect:CGRect(x: button_x+(button_w-button_h)/2, y: button_h, width: button_h, height: button_h))
   let animAlpha:CABasicAnimation = CABasicAnimation()
   animAlpha.keyPath = "opacity"
   animAlpha.toValue = 0
   let animGroup:CAAnimationGroup = CAAnimationGroup()
   animGroup.duration = 1
   animGroup.repeatCount = 1
   animGroup.isRemovedOnCompletion = false
   animGroup.fillMode=kCAFillModeForwards
   animGroup.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut)
   animGroup.animations = [animMakeBigger,animBounds,animAlpha]
   let animborder:CABasicAnimation = CABasicAnimation()
   animborder.keyPath = "borderColor"
   animborder.toValue = UIColor(red: 224.0/255, green: 224.0/255, blue: 224.0/255, alpha: 1.0).cgColor
   let animGroupAll:CAAnimationGroup = CAAnimationGroup()
   animGroupAll.duration = 1
   animGroupAll.repeatCount = 1
   animGroupAll.isRemovedOnCompletion = false
   animGroupAll.fillMode=kCAFillModeForwards ;
   animGroupAll.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut)
   animGroupAll.animations = [animMakeBigger,animBounds,animborder]
   animGroupAll.delegate = self
   animGroupAll.setValue("allMyAnimationsBoard", forKey: "groupborderkey")
   CATransaction.begin()
   view?.layer.add(animGroup, forKey: "allMyAnimation")
   viewborder?.layer.add(animGroupAll, forKey: "allMyAnimationsBoard")
   CATransaction.commit()
}

第二阶段动画

第二阶段动画主要为绘制圆环,在此创建一个类 CircleView

protocol CircleDelegate{
    func circleAnimationStop()
}
class CircleView: UIView,CAAnimationDelegate {
    
    var lineWidth:NSNumber = 3.0
    var strokeColor:UIColor = UIColor(red: 25.0 / 255.0, green: 155.0 / 255.0, blue: 200.0 / 255.0, alpha: 1.0)
    var circle:CAShapeLayer = CAShapeLayer()
    var delegate:CircleDelegate?

    override init(frame: CGRect) {
        super.init(frame: frame)
        let startAngle:CGFloat = -90.0/180.0 * CGFloat(M_PI)
        let endAngle:CGFloat = -90.01/180.0 * CGFloat(M_PI)
        let circlePath:UIBezierPath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width/2,y: frame.size.height/2), radius: frame.size.height/2-2, startAngle: startAngle, endAngle: endAngle, clockwise: true)
        circle.path = circlePath.cgPath
        circle.lineCap = kCALineCapRound
        circle.fillColor = UIColor.clear.cgColor
        circle.lineWidth = lineWidth as CGFloat

        self.layer.addSublayer(circle)
    }
    func strokeChart(){
        circle.lineWidth = lineWidth as CGFloat
        circle.strokeColor = strokeColor.cgColor
        let pathAnimation:CABasicAnimation = CABasicAnimation()
        pathAnimation.keyPath = "strokeEnd"
        pathAnimation.delegate = self
        pathAnimation.duration = 3.0
        pathAnimation.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut)
        pathAnimation.fromValue = 0.0
        pathAnimation.toValue = 1.0
        pathAnimation .setValue("strokeEndAnimationcircle", forKey: "groupborderkeycircle")
        circle.add(pathAnimation, forKey: "strokeEndAnimationcircle")
    }
    func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        delegate!.circleAnimationStop()
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

在这里代码已经实现了第二阶段,如果从第一阶段动画进入到第二阶段呢?在第一阶段的动画中已经设置了 delegate 回调对象,因此在第一阶段的动画结束后的回调方法中启动第二阶段动画。

func animationDidStop(_ anim: CAAnimation, finished flag: Bool){
   if(flag){
       let animType = anim.value(forKey: "groupborderkey")
       let animType1 = anim.value(forKey: "groupborderkey1")
       if(animType != nil){
           if ((animType as! NSString).isEqual(to: "allMyAnimationsBoard")){
               circleView?.strokeChart()
           }
       }else if(animType1 != nil){
           if((animType1 as! NSString).isEqual(to: "allMyAnimationsBoardspread1")){
               label = UILabel(frame:CGRect(x: 0, y: 0, width: button_w, height: button_h))
               label!.text = "Complete"
               label!.textAlignment = NSTextAlignment.center
               label!.textColor = UIColor.white
               label!.font = UIFont.systemFont(ofSize: 20)
               self.addSubview(label!)
           }
       }
   }
}

第三阶段动画

func startAnimationSpread(){
   let animMakeBigger:CABasicAnimation = CABasicAnimation()
   animMakeBigger.keyPath = "cornerRadius"
   animMakeBigger.fromValue=button_h/2.0
   animMakeBigger.toValue=0
   let animBounds:CABasicAnimation = CABasicAnimation()
   animBounds.keyPath = "bounds"
   animBounds.fromValue = NSValue(cgRect:CGRect(x: button_x+(button_w-button_h)/2, y: button_h, width: button_h, height: button_h))
   animBounds.toValue = NSValue(cgRect:CGRect(x: 0, y: 0, width: button_w, height: button_h))
   let animAlpha:CABasicAnimation = CABasicAnimation()
   animAlpha.keyPath = "opacity";
   animAlpha.toValue = 1.0
   let animBackground:CABasicAnimation = CABasicAnimation()
   animBackground.keyPath = "backgroundColor"
   animBackground.toValue = FreshGreen.cgColor
   let group:CAAnimationGroup = CAAnimationGroup()
   group.duration = 1
   group.repeatCount = 1
   group.isRemovedOnCompletion = false
   group.fillMode=kCAFillModeForwards
   group.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut)
   group.animations = [animMakeBigger,animBounds,animAlpha,animBackground]
   let animBorder:CABasicAnimation = CABasicAnimation()
   animBorder.keyPath = "borderColor"
   animBorder.toValue = FreshGreen.cgColor
   let allGroup:CAAnimationGroup = CAAnimationGroup()
   allGroup.duration = 1
   allGroup.repeatCount = 1
   allGroup.isRemovedOnCompletion = false
   allGroup.fillMode=kCAFillModeForwards
   allGroup.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut)
   allGroup.animations = [animMakeBigger,animBounds,animAlpha,animBorder]
   CATransaction.begin()
   view?.layer.add(group, forKey: "allMyAnimationspread1")
   allGroup.setValue("allMyAnimationsBoardspread1", forKey: "groupborderkey1")
   allGroup.delegate = self
   viewborder?.layer.add(allGroup, forKey: "allMyAnimationsBoardspread1")
   CATransaction.commit()
}

最后在 ViewController 调用

var singleTap1:UITapGestureRecognizer?
var singleTap2:UITapGestureRecognizer?
var singleTap3:UITapGestureRecognizer?
var buttonview1:ButtonView?
var buttonview2:ButtonView?
var buttonview3:ButtonView?
override func viewDidLoad() {
   super.viewDidLoad()
   buttonview1 = ButtonView(frame:CGRect(x: 100, y: 150, width: 210, height: 70))
   buttonview2 = ButtonView(frame:CGRect(x: 100, y: 275, width: 210, height: 70))
   buttonview3 = ButtonView(frame:CGRect(x: 100, y: 400, width: 210, height: 70))
   
   singleTap1 = UITapGestureRecognizer(target: self, action: #selector(ViewController.viewAction1))
   singleTap2 = UITapGestureRecognizer(target: self, action: #selector(ViewController.viewAction2))
   singleTap3 = UITapGestureRecognizer(target: self, action: #selector(ViewController.viewAction3))
   
   buttonview1?.addGestureRecognizer(singleTap1!)
   buttonview2?.addGestureRecognizer(singleTap2!)
   buttonview3?.addGestureRecognizer(singleTap3!)
   
   self.view.addSubview(buttonview1!)
   self.view.addSubview(buttonview2!)
   self.view.addSubview(buttonview3!)
}
func viewAction1(){
   buttonview1?.startAnimation()
}
func viewAction2(){
   buttonview2?.startAnimation()
}
func viewAction3(){
   buttonview3?.startAnimation()
}

效果如下:

20170624149831082641559.gif

以上代码:点击查看