这效果是从MOV文件转成GIF的,而且CSDN不支持大于2M的图片上传,优酷地址
创建一个Single View Application工程,再创建一个Swift文件,我创建的叫“PulsingRadarView”,目前结构为:
在ViewController里面持有一个Optional的PulsingRadarView的属性,表示可以为nil,然后在viewDidLoad里做一个简单的初始化工作:
classViewController:UIViewController{
varradarView:PulsingRadarView!
overridefuncviewDidLoad() {
super.viewDidLoad()
letradarSize =CGSizeMake(self.view.bounds.size.width,)
radarView=PulsingRadarView(frame:CGRectMake(0,(self.view.bounds.size.height-radarSize.height)/2,
radarSize.width,radarSize.height))
view.addSubview(radarView)
}
funcdidReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
雷达是圆形的,所以宽高都是self.view.bounds.size.width。
PulsingRadarView里面现在应该是空的,我们首先导入QuartzCore,因为后面动画部分会用到CALayer,然后重写drawRect方法:
funcdrawRect(rect:CGRect) {
UIColor.whiteColor().setFill()
UIRectFill(rect)
letpulsingCount =6
letanimationDuration:Double=4
varanimationLayer =CALayer()
forvari =0; i < pulsingCount; i++ {
varpulsingLayer =CALayer()
pulsingLayer.frame=height)
pulsingLayer.borderColor=UIColor.grayColor().CGColor
pulsingLayer.borderWidth=1
pulsingLayer.cornerRadius= rect.2
defaultCurve =CAMediaTimingFunction(name:kCAMediaTimingFunctionDefault)
varanimationGroup =CAAnimationGroup()
animationGroup.fillMode=kCAFillModeBackwards
animationGroup.beginTime=CACurrentMediaTime() +Double(i) * animationDuration /Double(pulsingCount)
animationGroup.duration= animationDuration
animationGroup.repeatCount=HUGE
animationGroup.timingFunction= defaultCurve
varscaleAnimation =CABasicAnimation(keyPath:"transform.scale")
scaleAnimation.autoreverses=false
scaleAnimation.fromValue=Double(0)
scaleAnimation.toValue=1.5)
varopacityAnimation =CAKeyframeAnimation(keyPath:"opacity")
opacityAnimation.values= [1),0.7),216)">0)]
opacityAnimation.keyTimes= [0),216)">0.5),216)">1)]
animationGroup.animations= [scaleAnimation,opacityAnimation]
pulsingLayer.addAnimation(animationGroup,forKey:"pulsing")
animationLayer.addSublayer(pulsingLayer)
}
layer.addSublayer(animationLayer)
}
先设置画布的背影色为白色,pulsingCount表示波形的条数,animationDuration表示动画的时长,然后我创建了一个animationLayer来存放所有的动画Layer------pulsingLayer,这样layer的结构看起来就像:
每个pulsingLayer代表一个圆形,循环里面先对pulsingLayer进行一些初始化工作:设置frame、边框颜色、边框大小以及radius(半径),radius自然就是自身的宽或高的一半。
CAMediaTimingFunction稍后再说。
接下来创建一个AnimationGroup,因为我们需要用到的动画将有两个:scale(缩放)、opacity(透明),而且需要控制动画开始的时间。
我们借用Controlling Animation Timing这篇文章中的几张图来说明fillMode、beginTime这两个属性:
以下每个方格代表1秒钟,下面这张图也就代表4秒钟,动画时间为1.5秒,黄色为动画开始,蓝色为动画结束,黄色到蓝色也就是动画的过程。从图中可以看到,蓝色部分结束后就是白色了,也就代表整个动画结束并且从layer上移除。
下面这张图开始动画时间偏移了1秒,其余不变。
默认情况下,所有的Layer无论创建的先后顺序有何不同,它们的时间线都是一致的,beginTime为0,表示加入Layer之后就立即开始动画(或者说在当前时间播放动画),而如果要偏移1秒(如上图),则要CACurrentMediaTime()+1,获取当前系统的绝对时间(秒数)并+1。我们要实现脉冲效果,就要使每一个animationGroup的动画以不同的beginTime来进行,所以要设置beginTime =CACurrentMediaTime() + Double(i) * animationDuration / Double(pulsingCount),Swift不支持隐式类型转换,用Double()显式的强转一下。
但是通过上图可以看到,偏移后动画开始前有一个空档,这是由fillMode决定的:
优酷地址
classPulsingRadarView:UIView{
letitemSize =CGSizeMake(44,216)">44)
varitems =NSMutableArray()
letmaxCount =10
varradarButton =UIButton(frame:itemSize.height))
radarButton.setImage(UIImage(named:"UK"),forState:UIControlState.Normal)
center =generateCenterPointInRadar()
radarButton.center=CGPointMake(center.x,center.y)
self.addSubview(radarButton)
items.addObject(radarButton)
ifitems.count> maxCount {
varview =objectAtIndex(0)asUIView
view.removeFromSuperview()
removeObject(view)
}
}
在一个平面直角坐标系中,以原点为圆心,1 为半径画一个圆,这个圆交 x 轴于 A 点。以 O 为旋转中心,将 A 点逆时针旋转一定的角度α至 B 点,设此时 B 点的坐标是(x,y),那么此时 y 的值就叫做α的正弦,记作 sinα;此时 x 的值就叫做α的余弦,记作 cosα;y 与 x 的比值 y/x 就叫做α的正切,记作 tanα。
varangle =Double(arc4random()) %360
varradius =arc4random()) % (Double)((width-width)2)
varx =cos(angle) * radius
vary =sin(angle) * radius
returnCGPointMake(CGFloat(x)+/2(y).height)
}
foriteminitems{
ifCGRectIntersectsRect(item.frame,frame) {
returntrue
}
}
returnfalse
}
do{
varcenter = generateCenterPointInRadar()
radarButton.center = CGPointMake(center.x,center.y)
}while(itemFrameIntersectsInOtherItem(radarButton.frame))
......
init(frame: CGRect) {
super.init(frame: frame)
self.alpha =0
}
funcdidMoveToWindow() {
super.didMoveToWindow()
if self.window {
UIView.animateWithDuration(1,animations: {
self.alpha = 1
})
}
}
}
UIView.beginAnimations("",context:nil)
UIView.setAnimationDuration(1)
alpha=0
.setAnimationDidStopSelector(Selector("callSuperRemoveFromSuperview"))
.commitAnimations()
}
funccallSuperRemoveFromSuperview() {
.removeFromSuperview()
}
init(frame:CGRect) {
init(frame: frame)
NSNotificationCenter.defaultCenter().addObserver selector:Selector("resume"),
name:UIApplicationDidBecomeActiveNotification,
object:nil)
}
requiredinit(coder aDecoder:NSCoder) {
fatalError("init(coder:) has not been implemented")
}
funcresume() {
ifletanimationLayer =self.animationLayer{
animationLayer.removeFromSuperlayer()
.setNeedsDisplay()
}
}
deinit{
().removeObserver 这样一来,动画就可以在回到应用程序的时候重新开始了,我把animationLaye以weak的方式引用到了属性里面,这是为了在resume里好方便判断。