您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
Swift语言iOS开发:CALayer十则示例(上)
 
作者:Raywenderlich 来源:51CTO 发布于 2015-04-07
   次浏览      
 

如你所知,我们在iOS应用中看到的都是视图(view),包括按钮视图、表视图、滑动条视图,还有可以容纳其他视图的父视图等。

但你或许不知道在iOS中支撑起每个视图的是一个叫做"图层(layer)"的类,确切地说是CALayer。

本文中您会了解CALayer及其工作原理,还有应用CALayer打造酷炫效果的十则示例,比如绘制矢量图形、渐变色,甚至是粒子系统。

本文要求读者熟悉iOS应用开发和Swift语言的基础知识,包括利用Storyboard构建用户界面。

注:如果您尚未掌握这些基础,不必担心,我们有不少相关教程,例如使用Swift语言编写iOS应用和iOS学徒。

准备开始

要理解图层是什么,最简便的方式就是"实地考察"。我们这就创建一个简单的项目,从头开始玩转图层。

准备好写代码了吗?好!启动Xcode,然后:

1.选择File\New\Project菜单项。

2.在对话框中选择iOS\Application\Single View Application。

3.点击Next,Product Name填写CALayerPlayground,然后输入你自己的Organization Name和Identifier。

4.Language选Swift,Devices选Universal。

5.取消选择Core Data,点击Next。

6.把项目保存到合适的位置(个人习惯把项目放在用户目录下建立的Source文件夹),点击Create。

好,文件准备就绪,接下来就是创建视图了:

7.在项目导航栏(Project navigator)中选择Main.storyboard。

8.选择View\Assistant Editor\Show Assistant Editor菜单项,如果没有显示对象库(Object Library),请选择View\Utilities\Show Object Library。

9.然后选择Editor\Canvas\Show Bounds Rectangles,这样在向场景添加视图时就可以看到轮廓了。

10.把一个视图(View)从对象库拖入视图控制器场景,保持选中状态,在尺寸检查器(View\Utilities\Show Size Inspector)中将x和y设为150,Width和Height设为300。

11.视图保持选中,点击自动布局工具栏(Storyboard右下角)的Align按钮,选中Horizontal Center in Container和Vertical Center in Container,数值均为0,然后点击Add 2 Constraints。

12.点击Pin按钮,选中Width和Height,数值均设为300,点击Add 2 Constraints。

最后按住control从刚刚创建的视图拖到ViewController.swift文件中viewDidLoad()方法的上方,在弹框中将outlet命名为viewForLayer,如图:

点击Connect创建outlet。

将ViewController.swift中的代码改写为:

import UIKit 

class ViewController: UIViewController {

@IBOutlet weak var viewForLayer: UIView!

var l: CALayer {
return viewForLayer.layer
}

override func viewDidLoad() {
super.viewDidLoad()
setUpLayer()
}

func setUpLayer() {
l.backgroundColor = UIColor.blueColor().CGColor
l.borderWidth = 100.0
l.borderColor = UIColor.redColor().CGColor
l.shadowOpacity = 0.7
l.shadowRadius = 10.0
}

}

之前提到iOS中的每个视图都拥有一个关联的图层,你可以通过yourView.layer访问图层。这段代码首先创建了一个叫"l"(小写L)的计算属性,方便访问viewForLayer的图层,可让你少写一些代码。

这段代码还调用了setUpLayer方法设置图层属性:阴影,蓝色背景,红色粗边框。你马上就可以了解这些东西,不过现在还是先构建App,在iOS模拟器中运行(我选了iPhone 6),看看自定义的图层如何。

几行代码,效果还不错吧?还是那句话,每个视图都由图层支撑,所以你也可以对App中的任何视图做出类似修改。我们继续深入。

CALayer基本属性

CALayer有几个属性可以用来自定外观,想想刚才做的:

把图层背景色从默认的无色改为蓝色

通过把边框宽度从默认的0改为100来添加边框

把边框颜色从默认的黑色改为红色

最后把阴影透明度从0(全透明)改为0.7,产生阴影效果,此外还把阴影半径从默认的3改为10。

以上只是CALayer中可以设置的部分属性。我们再试两个,在setUpLayer()中追加以下代码:

l.contents = UIImage(named: "star")?.CGImage 
l.contentsGravity = kCAGravityCenter

CALayer的contents属性可以把图层的内容设为图片,这里我们要设置一张"星星"的图片,为此你需要把图片添加到项目中,请下载图片并添加到项目中。

构建,运行,欣赏一下效果:

注意星星居中,这是因为contentsGravity属性被设为kCAGravityCenter,如你所想,重心也可以设为上、右上、右、右下、下、左下、左、左上。

更改图层外观

仅供娱乐,我们来添加几个手势识别器来控制图层外观。在Xcode中,向viewForLayer对象上拖一个轻触手势识别器(tap gesture recognizer),见下图:

注:如果你对手势识别器比较陌生,请参阅Using UIGestureRecognizer with Swift。

以此类推,再添加一个捏合手势识别器(pinch gesture recognizer)。

然后按住control依次将两个手势识别器从Storyboard场景停靠栏拖入ViewController.swift,放在setUpLayer()和类自身的闭合花括号之间。

在弹框中修改连接为Action,命名轻触识别操作为tapGestureRecognized,捏合识别操作为pinchGestureRecognized,例如:

如下改写tapGestureRecognized(_:):

@IBAction func tapGestureRecognized(sender: UITapGestureRecognizer) { 
l.shadowOpacity = l.shadowOpacity == 0.7 ? 0.0 : 0.7
}

当令视图识别出轻触手势时,代码告知viewForLayer图层在0.7和0之间切换阴影透明度。

你说视图?嗯,没错,重写CALayer的hitTest(_:)也可以实现相同效果,本文后面也会看到这个方法,不过我们这里用的方法也有道理:图层本身并不能响应手势识别,只能响应点击测试,所以我们在视图上设置了轻触手势识别器。

然后如下修改pinchGestureRecognized(_:):

@IBAction func pinchGestureRecognized(sender: UIPinchGestureRecognizer) { 
let offset: CGFloat = sender.scale < 1 ? 5.0 : -5.0
let oldFrame = l.frame
let oldOrigin = oldFrame.origin
let newOrigin = CGPoint(x: oldOrigin.x + offset, y: oldOrigin.y + offset)
let newSize = CGSize(width: oldFrame.width + (offset * -2.0), height: oldFrame.height + (offset * -2.0))
let newFrame = CGRect(origin: newOrigin, size: newSize)
if newFrame.width >= 100.0 && newFrame.width <= 300.0 {
l.borderWidth -= offset
l.cornerRadius += (offset / 2.0)
l.frame = newFrame
}
}

此处基于用户的捏合手势创建正负偏移值,借此调整图层框架大小、边缘宽度和边角半径。

图层的边角半径默认值为0,意即标准的90度直角。增大半径会产生圆角,如果想将图层变成圆形,可以设边角半径为宽度的一半。

注意:调整边角半径并不会裁剪图层内容(星星图片),除非图层的masksToBounds属性被设为true。

构建运行,尝试在视图中使用轻触和捏合手势:

嘿,再好好装扮一下都能当头像用了! :]

CALayer体验

CALayer中的属性和方法琳琅满目,此外还有几个包含特有属性和方法的子类。

要遍历如此酷炫的API,Raywenderlich.com导游先生最好不过了。

接下来,你需要以下材料:

Layer Player App

Layer Player 源代码

该App包含十种不同的CALayer示例,本文后面会依次介绍,十分方便。先来吊吊大家的胃口:

下面在讲解每个示例的同时,我建议在CALayer演示应用中亲自动手试验,还可以读读代码。不用写,只要深呼吸,轻松阅读就可以了。 :]

我相信这些酷炫的示例会启发您利用不同的CALayer为自己的App锦上添花,希望大家喜欢!

示例 #1:CALayer

前面我们看过使用CALayer的示例,也就是设置各种属性。

关于CALayer还有几点没提:

图层可以包含子图层。就像视图可以包含子视图,图层也可以有子图层,稍加利用就能打造漂亮的效果!

图层属性自带动画效果。修改图层属性时,存在默认的动画效果,你也可以自定义动画行为。

图层是轻量概念。相对视图而言,图层更加轻量,因此图层可以帮助提升性能。

图层有大量实用属性。前面你已经看过几条了,我们继续探索!

刚刚说CALayer图层有很多属性,我们来看一批实用属性:有些属性你可能第一次见,但真的很方便!

// 1 
let layer = CALayer()
layer.frame = someView.bounds

// 2
layer.contents = UIImage(named: "star")?.CGImage
layer.contentsGravity = kCAGravityCenter

// 3
layer.magnificationFilter = kCAFilterLinear
layer.geometryFlipped = false

// 4
layer.backgroundColor = UIColor(red: 11/255.0, green: 86/255.0, blue: 14/255.0, alpha: 1.0).CGColor
layer.opacity = 1.0
layer.hidden = false
layer.masksToBounds = false

// 5
layer.cornerRadius = 100.0
layer.borderWidth = 12.0
layer.borderColor = UIColor.whiteColor().CGColor

// 6
layer.shadowOpacity = 0.75
layer.shadowOffset = CGSize(width: 0, height: 3)
layer.shadowRadius = 3.0
someView.layer.addSublayer(layer)

在以上代码中:

创建一个CALayer实例,并把框架设为someView边框。

将图层内容设为一张图片,并使其在图层内居中,注意赋值的类型是底层的Quartz图像数据(CGImage)。

使用过滤器,过滤器在图像利用contentsGravity放大时发挥作用,可用于改变大小(缩放、比例缩放、填充比例缩放)和位置(中心、上、右上、右等等)。以上属性的改变没有动画效果,另外如果geometryFlipped未设为true,几何位置和阴影会上下颠倒。继续:

把背景色设为Ray最爱的深绿色。:] 然后让图层透明、可见。同时令图层不要遮罩内容,意思是如果图层尺寸小于内容(星星图片),图像不会被裁减。

图层边角半径设为图层宽度的一半,使边缘变为圆形,注意图层颜色赋值类型为Quartz颜色引用(CGColor)。

创建阴影,设shouldRasterize为true(后文还会提到),然后将图层加入视图结构树。

结果如下:

CALayer还有两个附加属性有助于改善性能:shouldRasterize和drawsAsynchronously。

shouldRasterize默认为false,设为true可以改善性能,因为图层内容只需要一次渲染。相对画面中移动但自身外观不变的对象效果拔群。

drawsAsynchronously默认值也是false。与shouldRasterize相对,该属性适用于图层内容需要反复重绘的情况,此时设成true可能会改善性能,比如需要反复绘制大量粒子的粒子发射器图层(可以参考后面的CAEmitterLayer示例)。

谨记:如果想将已有图层的shouldRasterize或drawsAsynchronously属性设为true,一定要三思而后行,考虑可能造成的影响,对比true与false的性能差异,辨明属性设置是否有积极效果。设置不当甚至会导致性能大幅下降。

无论如何还是先回到图层演示应用,其中有些控件可以用来调整CALayer的属性:

调节试试看,感受一下,利用CALayer可以实现怎样的效果。

注:图层不属于响应链(responder chain),无法像视图一样直接响应触摸和手势,我们在CALayerPlayground中见识过。不过图层有点击测试,后面的CATransformLayer会提到。你也可以向图层添加自定义动画,CAReplicatorLayer中会出现。

示例 #2:CAScrollLayer

CAScrollLayer显示一部分可滚动图层,该图层十分基础,无法直接响应用户的触摸操作,也不能直接检查可滚动图层的边界,故可避免越界无限滚动。

UIScrollView用的不是CAScrollLayer,而是直接改动图层边界。

CAScrollLayer的滚动模式可设为水平、垂直或者二维,你也可以用代码命令视图滚动到指定位置:

// In ScrollingView.swift 
import UIKit

class ScrollingView: UIView {
// 1
override class func layerClass() -> AnyClass {
return CAScrollLayer.self
}
}

// In CAScrollLayerViewController.swift
import UIKit

class CAScrollLayerViewController: UIViewController {
@IBOutlet weak var scrollingView: ScrollingView!

// 2
var scrollingViewLayer: CAScrollLayer {
return scrollingView.layer as CAScrollLayer
}

override func viewDidLoad() {
super.viewDidLoad()
// 3
scrollingViewLayer.scrollMode = kCAScrollBoth
}

@IBAction func tapRecognized(sender: UITapGestureRecognizer) {
// 4
var newPoint = CGPoint(x: 250, y: 250)
UIView.animateWithDuration(0.3, delay: 0, options: .CurveEaseInOut, animations: {
[unowned self] in
self.scrollingViewLayer.scrollToPoint(newPoint)
}, completion: nil)
}

}

以上代码:

定义一个继承UIView的类,重写layerClass()返回CAScrollLayer,该方法等同于创建一个新图层作为子图层(CALayer示例中做过)。

一个用以方便简化访问自定义视图滚动图层的计算属性。

设滚动模式为二维滚动。

识别出轻触手势时,让滚动图层在UIView动画中滚到新建的点。(注:scrollToPoint(_:)和scrollToRect(_:)不会自动使用动画效果。)

案例研究:如果ScrollingView实例包含大于滚动视图边界的图片视图,在运行上述代码并点击视图时结果如下:

图层演示应用中有可以锁定滚动方向(水平或垂直)的开关。

以下经验规律用于决定是否使用CAScrollLayer:

如果想使用轻量级的对象,只需用代码操作滚动:可以考虑CAScrollLayer。

如果想让用户操作滚动,UIScrollView大概是更好的选择。要了解更多,请参考我们的视频教程。

如果是滚动大型图片:考虑使用CATiledLayer(见后文)。

示例 #3:CATextLayer

CATextLayer能够对普通文本或属性字串进行简单快速的渲染。与UILabel不同,CATextLayer无法指定UIFont,只能使用CTFontRef或CGFontRef。

像下面这样的代码完全可以掌控文本的字体、字体大小、颜色、对齐、折行(wrap)和截断(truncation)规则,也有动画效果:

// 1 
let textLayer = CATextLayer()
textLayer.frame = someView.bounds

// 2
var string = ""
for _ in 1...20 {
string += "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce auctor arcu quis velit congue dictum. "
}

textLayer.string = string

// 3
let fontName: CFStringRef = "Noteworthy-Light"
textLayer.font = CTFontCreateWithName(fontName, fontSize, nil)

// 4
textLayer.foregroundColor = UIColor.darkGrayColor().CGColor
textLayer.wrapped = true
textLayer.alignmentMode = kCAAlignmentLeft
textLayer.contentsScale = UIScreen.mainScreen().scale
someView.layer.addSublayer(textLayer)

以上代码解释如下:

创建一个CATextLayer实例,令边界与someView相同。

重复一段文本,创建字符串并赋给文本图层。

创建一个字体,赋给文本图层。

将文本图层设为折行、左对齐,你也可以设自然对齐(natural)、右对齐(right)、居中对齐(center)或两端对齐(justified),按屏幕设置contentsScale属性,然后把图层添加到视图结构树。

不仅是CATextLayer,所有图层类的渲染缩放系数都默认为1。在添加到视图时,图层自身的contentsScale缩放系数会自动调整,适应当前画面。你需要为手动创建的图层明确指定contentsScale属性,否则默认的缩放系数1会在Retina显示屏上产生部分模糊。

如果创建的文本图层添加到了方形的someView,效果会像这样:

你可以设置截断(Truncation)属性,生效时被截断的部分文本会由省略号代替显示。默认设定为无截断,位置可设为开头、末尾或中间截断:

图层演示应用中,你可以随心所欲地修改很多CATextLayer属性:

示例 #4:AVPlayerLayer

AVPlayerLayer是建立在AVFoundation基础上的实用图层,持有一个AVPlayer,用来播放音视频媒体文件(AVPlayerItems),举例如下:

override func viewDidLoad() { 
super.viewDidLoad()
// 1
let playerLayer = AVPlayerLayer()
playerLayer.frame = someView.bounds

// 2
let url = NSBundle.mainBundle().URLForResource("someVideo", withExtension: "m4v")
let player = AVPlayer(URL: url)

// 3
player.actionAtItemEnd = .None
playerLayer.player = player
someView.layer.addSublayer(playerLayer)

// 4
NSNotificationCenter.defaultCenter().addObserver(self, selector: "playerDidReachEndNotificationHandler:", name: "AVPlayerItemDidPlayToEndTimeNotification", object: player.currentItem)
}

deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}

// 5
@IBAction func playButtonTapped(sender: UIButton) {
if playButton.titleLabel?.text == "Play" {
player.play()
playButton.setTitle("Pause", forState: .Normal)
} else {
player.pause()
playButton.setTitle("Play", forState: .Normal)
}

updatePlayButtonTitle()
updateRateSegmentedControl()
}

// 6
func playerDidReachEndNotificationHandler(notification: NSNotification) {
let playerItem = notification.object as AVPlayerItem
playerItem.seekToTime(kCMTimeZero)
}

上述代码解释:

新建一个播放器图层,设置框架。

使用AV asset资源创建一个播放器。

告知命令播放器在播放完成后停止。其他选项还有暂停或自动播放下一个媒体资源。

注册AVPlayer通知,在一个文件播放完毕后发送通知,并在析构函数中删除作为观察者的控制器。

点击播放按钮时,触发控件播放AV asset并设置按钮文字。

注意这只是个入门示例,在实际项目中往往不会采用文字按钮控制播放。

AVPlayerLayer和其中创建的AVPlayer会像这样显示为AVPlayerItem实例的第一帧:

AVPlayerLayer还有一些附加属性:

videoGravity设置视频显示的缩放行为。

readyForDisplay检测是否准备好播放视频。

另一方面,AVPlayer也有不少附加属性和方法,有一个值得注意的是rate属性,对于0到1之间的播放速率,0代表暂停,1代表常速播放(1x)。

不过rate属性的设置是与播放行为联动的,也就是说调用pause()方法和把rate设为0是等价的,调用play()与把rate设为1也一样。

那快进、慢动作和反向播放呢?交给AVPlayerLayer把。rate大于1时会令播放器以相应倍速进行播放,例如rate设为2就是二倍速。

如你所想,rate为负时会让播放器以相应倍速反向播放。

然而,在以非常规速率播放之前,AVPlayerItem上会调用适当方法,验证是否能够以相应速率进行播放:

canPlayFastForward()对应大于1

canPlaySlowForward()对应0到1之间

canPlayReverse()对应-1

canPlaySlowReverse()对应-1到0之间

canPlayFastReverse()对应小于-1

绝大多数视频都支持以不同速率正向播放,可以反向播放的视频相对少一些。演示应用也包含了播放控件:

   
次浏览       
 
相关文章

手机软件测试用例设计实践
手机客户端UI测试分析
iPhone消息推送机制实现与探讨
Android手机开发(一)
 
相关文档

Android_UI官方设计教程
手机开发平台介绍
android拍照及上传功能
Android讲义智能手机开发
相关课程

Android高级移动应用程序
Android系统开发
Android应用开发
手机软件测试
最新活动计划
LLM大模型应用与项目构建 12-26[特惠]
QT应用开发 11-21[线上]
C++高级编程 11-27[北京]
业务建模&领域驱动设计 11-15[北京]
用户研究与用户建模 11-21[北京]
SysML和EA进行系统设计建模 11-28[北京]

android人机界面指南
Android手机开发(一)
Android手机开发(二)
Android手机开发(三)
Android手机开发(四)
iPhone消息推送机制实现探讨
手机软件测试用例设计实践
手机客户端UI测试分析
手机软件自动化测试研究报告
更多...   


Android高级移动应用程序
Android应用开发
Android系统开发
手机软件测试
嵌入式软件测试
Android软、硬、云整合


领先IT公司 android开发平台最佳实践
北京 Android开发技术进阶
某新能源领域企业 Android开发技术
某航天公司 Android、IOS应用软件开发
阿尔卡特 Linux内核驱动
艾默生 嵌入式软件架构设计
西门子 嵌入式架构设计
更多...