iOS 实现脉冲雷达以及动态增减元素 By Swift-感谢分享
创建基本动画
这效果是从MOV文件转成GIF的,而且CSDN不支持大于2M的图片上传,优酷地址
创建一个Single View Application工程,再创建一个Swift文件,我创建的叫“PulsingRadarView”,目前结构为:
在ViewController里面持有一个Optional的PulsingRadarView的属性,表示可以为nil,然后在viewDidLoad里做一个简单的初始化工作:
class ViewController: UIViewController {
var radarView: PulsingRadarView!
override func viewDidLoad() {
super.viewDidLoad()
let radarSize = CGSizeMake(self.view.bounds.size.width, self.view.bounds.size.width)
radarView = PulsingRadarView(frame: CGRectMake(0,(self.view.bounds.size.height-radarSize.height)/2,
radarSize.width,radarSize.height))
self.view.addSubview(radarView)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
雷达是圆形的,所以宽高都是self.view.bounds.size.width。
PulsingRadarView里面现在应该是空的,我们首先导入QuartzCore,因为后面动画部分会用到CALayer,然后重写drawRect方法:
override func drawRect(rect: CGRect) {
UIColor.whiteColor().setFill()
UIRectFill(rect)
let pulsingCount = 6
let animationDuration: Double = 4
var animationLayer = CALayer()
for var i = 0; i < pulsingCount; i++ {
var pulsingLayer = CALayer()
pulsingLayer.frame = CGRectMake(0, 0, rect.size.width, rect.size.height)
pulsingLayer.borderColor = UIColor.grayColor().CGColor
pulsingLayer.borderWidth = 1
pulsingLayer.cornerRadius = rect.size.height / 2
var defaultCurve = CAMediaTimingFunction(name: kCAMediaTimingFunctionDefault)
var animationGroup = CAAnimationGroup()
animationGroup.fillMode = kCAFillModeBackwards
animationGroup.beginTime = CACurrentMediaTime() + Double(i) * animationDuration / Double(pulsingCount)
animationGroup.duration = animationDuration
animationGroup.repeatCount = HUGE
animationGroup.timingFunction = defaultCurve
var scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.autoreverses = false
scaleAnimation.fromValue = Double(0)
scaleAnimation.toValue = Double(1.5)
var opacityAnimation = CAKeyframeAnimation(keyPath: "opacity")
opacityAnimation.values = [Double(1),Double(0.7),Double(0)]
opacityAnimation.keyTimes = [Double(0),Double(0.5),Double(1)]
animationGroup.animations = [scaleAnimation,opacityAnimation]
pulsingLayer.addAnimation(animationGroup, forKey: "pulsing")
animationLayer.addSublayer(pulsingLayer)
}
self.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决定的:
- kCAFillModeRemoved 默认值,在动画开始前和动画结束后,动画对layer都没有影响,layer原本是什么样就是什么样
- kCAFillModeForwards 当动画结束后,layer会一直保持着动画最后的状态
- kCAFillModeBackwards 和kCAFillModeForwards相对,具体参考上面的
- kCAFillModeBoth kCAFillModeForwards和kCAFillModeBackwards在一起的效果
- kCAMediaTimingFunctionLinear 线性,即匀速
- kCAMediaTimingFunctionEaseIn 先慢后快
- kCAMediaTimingFunctionEaseOut 先快后慢
- kCAMediaTimingFunctionEaseInEaseOut 先慢后快再慢
- kCAMediaTimingFunctionDefault 实际效果是在动画开始时和动画播放时比较快,将结束时会变慢
动态增减元素
这效果是从MOV文件转成GIF的,而且CSDN不支持大于2M的图片上传,优酷地址
class PulsingRadarView: UIView {
let itemSize = CGSizeMake(44, 44)
var items = NSMutableArray()
func addOrReplaceItem() {
let maxCount = 10
var radarButton = UIButton(frame: CGRectMake(0, 0, itemSize.width, itemSize.height))
radarButton.setImage(UIImage(named: "UK"), forState: UIControlState.Normal)
var center = generateCenterPointInRadar()
radarButton.center = CGPointMake(center.x, center.y)
self.addSubview(radarButton)
items.addObject(radarButton)
if items.count > maxCount {
var view = items.objectAtIndex(0) as UIView
view.removeFromSuperview()
items.removeObject(view)
}
}
在一个平面直角坐标系中,以原点为圆心,1 为半径画一个圆,这个圆交 x 轴于 A 点。以 O 为旋转中心,将 A 点逆时针旋转一定的角度α至 B 点,设此时 B 点的坐标是(x,y),那么此时 y 的值就叫做α的正弦,记作 sinα;此时 x 的值就叫做α的余弦,记作 cosα;y 与 x 的比值 y/x 就叫做α的正切,记作 tanα。
private func generateCenterPointInRadar() -> CGPoint{
var angle = Double(arc4random()) % 360
var radius = Double(arc4random()) % (Double)((self.bounds.size.width - itemSize.width)/2)
var x = cos(angle) * radius
var y = sin(angle) * radius
return CGPointMake(CGFloat(x) + self.bounds.size.width / 2, CGFloat(y) + self.bounds.size.height / 2)
}
- NSTimer.scheduledTimerWithTimeInterval(0.5, target: radarView, selector: Selector("addOrReplaceItem"), userInfo: nil, repeats: true)
Timer在不用的时候一定要调用invalidate()方法,并且要在ViewController析构之前,不然ViewController不会被释放,也就永远不会被析构。这里我们就不考虑那么多了,毕竟只有一个页面,而且在真实场景里也不会这么去用,更多的情况是在网络请求回调的时候去处理。
优化
优化一
private func itemFrameIntersectsInOtherItem (frame: CGRect) -> Bool {
for item in items {
if CGRectIntersectsRect(item.frame, frame) {
return true
}
}
return false
}
......
do {
var center = generateCenterPointInRadar()
radarButton.center = CGPointMake(center.x, center.y)
} while (itemFrameIntersectsInOtherItem(radarButton.frame))
......
优化二
class PRButton: UIButton {
init(frame: CGRect) {
super.init(frame: frame)
self.alpha = 0
}
override func didMoveToWindow() {
super.didMoveToWindow()
if self.window {
UIView.animateWithDuration(1, animations: {
self.alpha = 1
})
}
}
}
在Swift里面,闭包是不能用super的,那只能这样了:
override func removeFromSuperview() {
UIView.beginAnimations("", context: nil)
UIView.setAnimationDuration(1)
self.alpha = 0
UIView.setAnimationDidStopSelector(Selector("callSuperRemoveFromSuperview"))
UIView.commitAnimations()
}
private func callSuperRemoveFromSuperview() {
super.removeFromSuperview()
}
优化三
weak var animationLayer: CALayer?
override init(frame: CGRect) {
super.init(frame: frame)
NSNotificationCenter.defaultCenter().addObserver(self,
selector: Selector("resume"),
name: UIApplicationDidBecomeActiveNotification,
object: nil)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func resume() {
if let animationLayer = self.animationLayer {
animationLayer.removeFromSuperlayer()
self.setNeedsDisplay()
}
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
GitHub下载地址
UPDATED:
iOS 实现脉冲雷达以及动态增减元素 By Swift-感谢分享的更多相关文章
- 动态生成元素动作绑定,jquery 1.9如何实现
1.7后增加了 live()1.9后被移除了 网上说可以用 on() 代替 可以实际在动态生成元素上绑定动作,没效果,求解绝方法(用低版本的jQuery这种方法,求别说..) 答: 之前有老兄回答过类 ...
- JavaScript 、jQuery动态创建元素的关键字~
JavaScript动态创建元素: 1.创建元素 如:a 标签 var alink= document.createElement("a"); 2.j添加元素属性 alink.h ...
- js学习-DOM之动态创建元素的三种方式、插入元素、onkeydown与onkeyup两个事件整理
动态创建元素的三种方式: 第一种: Document.write(); <body> <input type="button" id="btn" ...
- 移动web在ios和android下点击元素出现阴影问题
移动web开发经验总结 1.-webkit-tap-highlight-color:rgba(255,255,255,0)可以同时屏蔽ios和android下点击元素时出现的阴影.备注:transp ...
- iOS 静态库和动态库的区别&静态库的生成
linux中静态库和动态库的区别 一.不同 库从本质上来说是一种可执行代码的二进制格式,可以被载入内存中执行.库分静态库和动态库两种. 1. 静态函数库 这类库的名字一般是libxxx.a:利用静态函 ...
- Javascript:DOM动态创建元素实例应用
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- iOS 静态库,动态库与 Framework 浅析
静态库与动态库的区别 首先来看什么是库,库(Library)说白了就是一段编译好的二进制代码,加上头文件就可以供别人使用. 什么时候我们会用到库呢?一种情况是某些代码需要给别人使用,但是我们不希望别人 ...
- css清浮动与动态计算元素宽度
css常用清浮动写法 /*清除浮动*/| .clears:after{ display: block; content: ''; clear: both; height: ; visibility: ...
- JS脚本动态给元素/控件添加事件
最近突然要用到JS脚本动态给元素添加事件.如TextBox的onclick事件.但有的onclick事件原先已经定义了相应代码!这里又不能替代原有方法,而JS脚本里面有个方法可以给控件在原有事件的基础 ...
随机推荐
- MISCONF Redis is configured to save RDB snapshots
今天客户突然反馈用我们的api出现了下面的这个错误 MISCONF Redis is configured to save RDB snapshots, but is currently not ab ...
- 【转】Android Studio 的小小配置
这篇博文的内容列表: 1.编辑器的字体设置 2.预览XML布局 3.导入项目 4.显示行号 5.新建Java类和Component的子类 额~~程序猿一般话都不多,嘿嘿,那咱就直接图文吧~ ^_^ ...
- JS实例(二)
一:注册页面 包括非空验证.邮箱验证.密码相等验证,在输入之前提示文字,获得焦点时文字清除颜色变化,输入正确显示正确图片,错误显示错误图片,所有验证通过才可提交,重置会重置回初始模样. 效果图如下: ...
- less编码规范
Less 编码规范 简介 因为自己最近写css用的比较多还是less,整理了一份less规范, 代码组织 代码按如下形式按顺序组织: @import 变量声明 样式声明 // ✓ @import &q ...
- PHP 内存的分布问题
php运行,内存分为5个区域, 1.基本数据类型--->栈区 2.符合数据类型-->堆区 对象实例在堆区,对象名字在栈区(指向此对象实例的变量)
- Android开发手记(18) 数据存储三 SQLite存储数据
Android为数据存储提供了五种方式: 1.SharedPreferences 2.文件存储 3.SQLite数据库 4.ContentProvider 5.网络存储 SQLite 是以嵌入式为目的 ...
- mvc5 + ef6 + autofac搭建项目(repository+uow)(一)
直奔主题了,不那么啰嗦. 整体框架的参考来源是 O# 的框架,在此感谢锋哥一直以来的开源,让我们有的学 如下图: (图一) 一下分三个步骤说明,分别为 dbContext,repository,uo ...
- SQL小细节
平时有些小细节,不留意的话很容易得到错误的答案,我们来看下下面的代码,看看你是否能答对呢? ) ,) SELECT @str = '中国CH',@info='MyTest' SELECT [字符串]= ...
- Ajax结合Js操作灵活操作表格
Table页面: <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head& ...
- ASP.NET页面生命周期总结(完结篇)
补充: W3svc服务 负责把‘工作进程’启动起来 W3svc 连接工作进程.内核模块.IIS 主服务的一个核心的桥梁 W3svc还有一个作用就是维护应用程序池,可以设置多长时间回收,多长时间重启. ...