iOS绘图和打印编程指导(二)-关于iOS绘图的一些概念

高质量的图形是用户界面的关键部分. 提供良好的图形不仅使你的APP看起来漂亮, 而且让你的APP看起来就是iOS系统的自然延伸. iOS提供两种绘制高质量图形的方式, 一种是使用OpenGL, 另外一种是使用原生的技术包括Quartz, Core Animation, UIKit; 本系列文章只涉及原生技术, 如果想要学习OpenGL绘图, 请看OpenGL ES Programming Guide

Quartz是主要绘图接口, 它支持基于路径的绘制, 抗锯齿渲染, 渐变填充模式, 图片, 颜色, 坐标变换; 和PDF文档的创建, 解析和显示. UIKit是OC类, 可以对线条, 图片, 颜色进行操控. Core Animation在底层支持UIKit中view的属性的动画变动, 也会经常用来创建自定义动画.

本文是对iOS绘图过程的一个概述, 以及iOS原生绘图技术的具体讲解, 同时还会对如何优化iOS绘图的一些提示和指导.

注意:不是所有的UIKit类是线程安全的, 在非主线程中进行图形绘制时, 先确认该类是否线程安全.

UIKit绘图


在iOS系统中, 不管通过何种(OpenGL, Quartz, UIKit或者Core Animation)绘图技术往屏幕绘制内容是需要通过类UIView或者其子类来进行的. 视图定义了屏幕绘图的部分, 如果使用系统变种的view来绘制, 将会自动完成, 如果你使用自定义view, 那么你需要自己编写绘图代码. 下面的内容描述了iOS原生绘图技术的一些概念.

除了直接在屏幕上绘制内容, UIKit还允许你将内容绘制到屏幕的bitmap/PDF等图形上下文. 当你在屏幕外上下文绘制时, 你此时不是在view中绘制, 所以有些绘画概念(view的绘制周期)对此不管用.(除非你获得该图像并将其绘制在图像视图或类似图像中)

视图绘制周期

UIView的子类的基本绘画模型是按需更新内容. 类UIView使得更新过程很简单和高效; 然而, UIView会收集你的更新请求然后在合适的时候将其递交给到你的绘制代码.

当一个view第一次显示或者当view中部分需要重绘时, iOS通过调用drawRect:来通知view进行绘制. 下面有个操作会触发view的更新:

  • 当view被新加入的view部分遮住, 或者移除这个部分遮住你的view
  • 将之前隐藏的view设置为显示(将view的hidden属性设置NO)
  • 将view滚动到屏幕外, 然后又滚动回来屏幕中.
  • 显示地调用setNeedDisplaysetNeedDisplayInRect:方法

系统的view会自动重绘. 对于自定义view, 你必须重写drawRect:方法, 自定义view是通过该方法来绘制内容的. 在drawRect:方法中, 你使用原生绘制技术来绘制图形, 文字, 图片, 渐变等可见的UI元素. 在view第一次显示时, iOS会往drawRect:传入一个rect参数, 该参数是一个包含整个view可见矩形区域; 在该方法接下来的调用中, rect参数可能代表是该view的部分区域. 为了提高性能, 你应该重绘受影响的内容.

在调用drawRect:方法后, view标记为已更新, 然后等下一个更新周期. 如果你的view显示的静态的内容, 那么你只需要关心因为滚动和新加入的view导致你的view的可见性的变化.

如果你想改变view的内容, 你需要调用setNeedsDisplaysetNeedsDisplayInRect:方法来触发view的更新. 比如, 你想在一定时间内对view进行多次更新, 那么你可能会创建一个timer来更新你的view. 你可能因为响应用户交互而更新view的内容.

注意:记住不要自己去调用drawRect:方法, 该方法是在屏幕重绘时由iOS系统内置代码调用, 另外在其他时间, 你是无法获取到graphic Context的, 所以你也没法进行绘制.

iOS中的坐标系和图形绘制

当iOSAPP开始绘制时, iOS会通过坐标系(coordinate system)将绘制的内容在二维空间内定位好. 这看起很好理解, 其实不然, iOSAPP中的内容绘制可能碰到不同的坐标系.

在iOS中, 绘制是在绘图上下文中进行的. 理论上, 一个绘图上文对象用来描述在那里, 又是如何进行绘制的. 包括一些基本绘制信息, 比如绘制时颜色的设置, 剪辑区域, 线宽和线的分格, 字体设置, 合成选择等等信息.

另外, 如图2-1所示, 每个绘图上下文都有一个坐标系. 更准确说, 是每个绘图上下文包含三个坐标系:

  • 绘画(用户)坐标系, 在执行绘画命令时使用.
  • 视图坐标系, 和view相关的坐标系
  • 设备坐标系, 表示屏幕上个像素
图2-1 绘制坐标, 视图坐标, 硬件坐标之间的关系

iOS的绘图框架为具体绘图目的地(屏幕, bitmap, PDF)创建绘图上下文, 并且这些绘图上下文为这些绘图目的地搭建一个初始化绘图坐标系统. 这个初始绘图坐标系就是所谓的默认坐标系, 它是对于view底层坐标系的一个1:1的映射.

每一个view都有一个当前变换矩阵(CTM), 该矩阵将当前绘制坐标系中的点映射到视图坐标系中. APP可以通过修改这个矩阵来改变未来绘图操作的行为.

每一个绘图框架是基于当前上下文来构建默认坐标系的, 在iOS中, 有两种坐标系:

  • 原点在左上角的坐标系(ULO), x轴正向是往右延伸, y轴正向是往下延伸. 该默认坐标系被UIKit和Core Animation使用
  • 原点在左下角的坐标系(LLO), x轴正向是往右延伸, y轴正向是往上延伸. 该默认坐标系被Core Graphic使用


    图2-2 iOS中的两种默认坐标系

注意:OS X系统中的默认坐标系是LLO型. 另外APPKit支持将坐标翻转成ULO型.

在调用drawRect:前, UIKit会先让绘图上下文有效, 之后搭建默认坐标系来往屏幕上绘制内容. 在drawRect:方法中, 可以对graphic Context进行一些图形状态参数的设置(比如填充颜色), 该操作不需要显示引用graphic context.

点(point) VS 像素(pixel)

在iOS中, 绘图代码中的坐标点和物理设备中的像素是有区别的. 在使用原生技术进行绘图时, 不管时绘图坐标系还是视图坐标系中的空间是逻辑空间, 单位是点. 使用逻辑坐标系的优点是, 和设备坐标系空间解耦了, 设备坐标系被系统框架用来管理屏幕上的像素.

系统会自动将视图坐标系中的点映射到设备坐标系中的像素, 这种映射不总是1:1的. 基于这种特性, 你需要牢记以下事实:
一点不总是等于一个物理像素
使用点(逻辑坐标系)的目地是提供一个和设备无关的一致性的大小输出. 大多数情况下, 不需要考虑一点的实际大小. 使用点, 可以提供一种你可以用来设置view中内容的大小, 位置的相对一致性的尺度(scale). 一点到底映射多少像素是有底层系统框架决定的. 比如, 在高分辨率屏幕中, 宽度为1点的线条在屏幕上显示的是线宽为两个像素的线条. 结果是, 如果在两个相似的设备上绘制的内容相同, 其中一个设备是高分辨率的, 那么这两个设备上的内容看起来会大致相同.

注意:在绘制和打印PDF时的上下文中, Core Graphic定义的"点"使用工业标准长度是1/32英寸.

在iOS中, UIScreen,UIView, UIImageCALayer这些类都会有个属性来获取缩放因子(scale), scale用来描述点和实际像素的关系. 比如, 每个UIKit视图都会有个contentScaleFactor属性. 在普通屏幕上, scale一般为1.0. 如果是高分辨率屏幕, scale通常为2.0. 或许在未来, 有值更高的scale. (iOS4.0之前的版本, 你应该认为scale默认为1.0)

原生绘图技术, 像Core Graphic, 会自动考虑了scale的值, 你不需要关注这个. 比如, 某个view实现了drawRect:方法, UIKit自动设置view的scale为屏幕的scale. 另外, UIKit自动修改graphic context的变换矩阵来适配view的scale. 因此, drawRect:方法中绘制的内容已经进行了合适的scale.

因为这种底层的自动映射, 当你写绘图代码时, 不需要考虑像素. 但存在需要考虑点和像素的映射关系的情况, 比如在高分辨率屏幕上显示高分辨率图片或者在低分率屏幕上绘制图形时避免缩放失真.

在iOS系统中, 当你在屏幕上绘制内容时, 绘图子系统会使用一种反锯齿的技术在底分辨率屏幕上显示高清图形. 举个例子, 你在白色屏幕上绘制一条黑色的实线, 当该线条刚好要显示在像素点上时, 屏幕会使用一系列像素显示一条黑色的线, 如果该线条要显示在两个像素之间的话, 那么屏幕会显示两条灰色像素线, 如下图2-3所示.


图2-3 一条单点线居中显示在整数点的地方

位置是整数点时, 会显示在像素点中间. 比如你要画一条从点(1.0,1.0)到点(1.0,10.0),宽度为一个像素点的垂直线, 你会得到一条模糊的灰色线条. 如果线的宽度为2个像素点, 你会得到一条黑色的实线. 基于这种规则, 宽度为奇数个像素的线比宽度为偶数个像素点的线看起来更加柔和, 除非你改动了他们的位置, 是线条刚好坐落在像素点上.

scale因子的作用是决定一个点包含多少个像素点.
在一块低分辨率的屏幕上(scale为1.0), 单点线宽等于单像素线宽. 为了在绘图抗锯齿的垂直/水平的线条, 如果线是宽度为奇数的像素,则必须将位置偏移0.5个点到整数点位置的两侧。如果线是偶数个点的宽度,为了避免模糊线,你则不能这样做。

图2-4 单点宽线在标准和视网膜显示器上的表现

在高分率屏幕上(scale因子为2.0), 单点线根本不会进行反锯齿, 因为它占据着两个完全的像素点(从-0.5到+0.5). 要绘制一条只覆盖当个物理像素的线, 你需要设置线宽为0.5点, 并偏移气味0.25点, 上图2-4展示了这两种屏幕上的比较.

当然, 基于scale因子改变绘图特性可能会产生不可预的后果. 在一些设备上, 1像素宽的线条看起来正常, 但是在高分辨率屏幕上, 线条可能太细, 细到难以看清楚. 所以, 在改变绘图特性前要考虑清楚.

获取绘图上下文(Graphic Context)

多数情况下, graphic context已经自动配置好了, view对象会为你干完这事, 以便drawRect:方法中的绘图代码可以立即执行. 作为该配置的一部分工作, 底层UIView类会创建一个graphic context(CGContextRef类型)对象.

如果你想在view之外的其他地方(比如, 在往PDF和bitmap文件中绘制内容)绘制内容, 或者使用Core Graphic方法进行绘制时, 你需要自己去获取graphic context. 该部分内容接下来会讲到.

如果你获得更多关于graphic context的信息, 修改图形状态信息, 和使用graphic contexts来创经济自定义内容, 请看苹果官文Quartz 2D Programming Guide

在屏幕上绘制

在使用Core Graphic函数在view中绘图时, 不论是在drawRect:或其他地方, 你都要用到graphic context来进行绘制(许多绘图函数的第一个参数都是CGContextRef类型). 你可以使用UIGraphicsGetCurrentContext函数来获取一个显式版本的graphic context, 该对象和方法drawRect:中获取的隐式graphic context是同一个对象. 因为是同一个graphic context, 所以这些绘画函数是基于ULO类型的坐标系.

如果你想使用Core Graphic函数在UIKit视图中绘制, 你应该使用UIKit中的ULO坐标系. 或者你可以先对graphic context的CTM进行上下翻转后, 然后使用core graphic中的原生LLO坐标系在UIKit视图中绘制内容. 下面章节中会讲到如何翻转坐标系.

使用函数UIGraphicsGetCurrentContext获取的对象, 总是当前的graphic context. 比如, 你创建了PDF的context, 此时使用UIGraphicGetCurrentContext函数获取的就是一个PDF context. 所以在使用Core Graphic函数进行往view中绘制内容时, 必须使用UIGraphicGetCurrentContext函数来获取绘图上下文.

注意:类UIPrintPageRenderer声明了几个用来绘制可打印内容的方法, 和drawRect:方法类似, UIKit会值隐式地为这些方法创建一个graphic context, 这些graphic context建立的坐标系是ULO.

在Bitmap和PDF上下文环境中绘制

UIKit有提供绘制image到bitmap中, 以及绘制PDF文件的功能. 这两种绘制方式首先都要获取相应的上下文(bitmap context和PDF context). 获取到的上下文对象会被接下来的绘制过程中一直使用. 当你绘制完内容后, 需要你调用其他函数去关闭当前的上下文.

bitmap和PDF context使用的是UIKit提供的ULO坐标系. Core graphic框架中也有绘制bitmap和PDF的函数. 所以, 你直接使用core graphic创建的上下文需要翻转坐标系后才能用.

注意:在iOS中, 推荐使用UIKit的方法来绘制bitmap和PDF. 然后, 如果你使用core graphic来绘制也行, 但要记住要调整坐标系

色彩和色彩空间

iOS支持Quartz中全部范围的色彩空间; 但是, 大多数APP只需要RGB色彩空间. 因为iOS是为带屏幕的嵌入式硬件设计的, 所以RGB色彩空间是最适合使用的.

UIColor提供了许多便捷方法来设置RGB, HSB颜色, 而且还可以使用灰度值. 你使用这种方式创建颜色时是不需要考虑色彩空间的, 因为类UIColor已经帮你做好了.

你也可以使用core graphic中的CGContextSetRGBStrokeColorCGContextSetRGBFillColor函数来创建和设置颜色. 虽然core graphic框架支持使用色彩空间来创建颜色, 也可以创建自定义颜色, 然后使用这些颜色来绘制图形, 但是这种方式是不推荐的. 你应该使用RGB颜色来绘制.

使用UIKit和Quartz来绘图


Quartz是iOS系统绘图技术的一个总称, Core Graphic框架是Quartz的核心, 并且提供了用于绘图的主要接口(API). 该框架提供了数据类型和功能来做以下的活儿:

  • Graphic context, 绘图上下文
  • Paths, 路径
  • Image and bitmaps, 图片和位图
  • Transparency layers, 透明层
  • Colors, pattern colors, and color spaces, 颜色、图案颜色和颜色空间
  • Gradients and shadings, 渐变和阴影
  • Fonts, 字体
  • PDF content, PDF内容

UIKit在基于Quartz上建立了一套, 集中的, 用来图形操作的类. UIKit不打算设计一套全面的绘图工具, 因为Core Graphic已经实现了这个功能. UIKit支持和包含以下功能和类:

  • UIImage, 用来展示不可修改的图像
  • UIColor, 提供设备颜色的基础支持
  • UIFont, 提供其他类想要字体信息
  • UIScreen, 提供和屏幕相关的信息
  • UIBezierPath, 使用该对象能是你绘制线, 弧, 椭圆和其他一些图形.
  • 使用UIImage来创建JPEG或者PNG图片
  • 将内容绘制到bitmap中
  • 生成PDF数据
  • 绘制矩形和剪切绘制区域
  • 获取和修改当前graphic context

想要知道更多关于UIKit框架的信息, 请看UIKit官方接口文档UIKit Framework Reference

配置Graphics Context

在调用drawRect:之前, view会创建和设置好绘图上下文, 该上下文只能在drawRect:方法中存活. 你可以通过UIGraphicGetCurrentContext函数来获取对该上下文的一个引用, 该方法会返回一个CGContextRef类型的指针, 你可以将该指针传入到相关core graphic函数中来修改上下文的一些状态. 表1-1列举了一系列用于修改绘图上下文状态的函数, 同时还列举了UIKit中的替代方法.

表2-1 用来修改graphic状态的函数

Graphic state Core Graphic函数 使用UIKit替代
Current transformation martix(CTM) CGContextRotateCTM CGContextScaleCTM CGContextTranslateCTM CGContextContactCTM None
裁剪区域 CGContextClipToRect UIRectClip函数
线:宽,连接处,cap,dash(虚线),miter limit(尖角) CGContextSetLineWidth CGContextSetLineJoin CGContextSetLineCap CGContextSetLineDash CGContextSetMiterLimit None
曲线估计精度 CGContextSetFlatness None
抗锯齿设置 CGContextSetAllowsAntialiasing None
透明度(全局) CGContextSetAlpha None
颜色:填充/描边 CGContextSetRGBFillColor CGContextSetRGBStrokeColor UIColor类
渲染意图 CGContextSetRenderingIntent None
色彩空间:填充/描边 CGContextSetFillColorSpace CGContextSetStrokeColorSpace UIColor类
文本:font,fontSize,文字间距,文本绘制模式等 CGContextSetFont CGContextSetFontSize CGContextSetCharacterSpacing UIFont类
混合模式 CGContextSetBlendMode UIImage类和方法可以让你设置具体的混合模式

graphic context使用一个栈来保存图形状态(graphic state). 当Quartz创建graphic context时, 栈时空的. 使用CGContextSaveGState函数来将当前图形状态副本push到栈上. 此后, 对图形状态的修改将影响后续的绘图操作, 但不影响栈上存储的副本. 修改完后, 可以使用CGContextRestoreGState函数将栈上的图形状态pop出栈顶, 返回到以前的图形状态. 这种push和pop图形状态方式是回到先前状态的快速方式, 并且消除了单独撤销每个状态更改的需要. 这种方式也是恢复某些图形状态的唯一方式, 比如clipping path.
想要了解更多关于graphic context和使用context来配置绘制环境的知识, 请看Quartz 2D Programming Guide中的Graphics Contexts

创建路径(path)并绘制

路径(path)是由一系列线段和Bezier曲线组成的基于向量的图形. UIKit使用UIRectFrame,UIRectFill等函数在view中来绘制简单的路径, 如矩形. Core Graphic同样也有许多创建简单path(如矩形, 椭圆)的便捷函数.

对于更加复杂的路径, 你必须使用UIKit中的类UIBezierPath来自己创建路径, 或者使用core graphic框架中的函数来创建CGPathRef类型的路径. 尽管你可以使用任意Api来构建没有图形上下文的路径, 但是路径中的点任然需要引用当前坐标系(该坐标系的方向是ULO或LLO), 并且绘制出该路径时需要用到图形上下文.

绘制路径时, 你必须具有当前上下文. 该上下文可以是自定义view的drawRect:中的上下文, 也可以是bitmap上下文, 或者PDF上下文. 坐标系决定了path的绘制, 在使用UIBezierPath时, 当前坐标系是ULO方向. 因此, 你如果想在LLO方向的坐标系中绘制的话, 会使的结果大不一样. 为了获得最佳结果,您应该始终指定与用于渲染的图形上下文的当前坐标系的原点相关的点.

注意:弧(Arcs)是一种path, 但绘制该弧稍微复杂点. 你如果使用Core Graphic来创建path, 因为当前坐标系是ULO(和Core Graphic的默认的LLO上下颠倒了), 所以你画出的弧会和你预想的不一样.

为了在iOS中创建路径, 推荐使用UIBezierPath来创建path, 而不是CGPath函数. 除非你需要的功能只有Core Graphic中提供, 比如向路径中添加省略号. 有关在UIKit中创建路径, 请看后续章节使用UIBezierPath来绘制图形.

有关使用UIBezierPath来绘制路径, 请看使用UIBezierPath来绘制图形. 更多关于使用Core Graphic函数来绘制路径请看Quartz 2D Programming Guide中的Paths部分.

创建图案纹理(Patterns), 渐变(Gradients), 阴影(Shadings)

Core Graphic框架包括用于创建图案纹理, 渐变和阴影的附加功能. 你可以使用这类功能来创建非单色的颜色, 并使用它们来填充你创建的路径. 图案(pattern)是由重复图像或内容组成的. 渐变和阴影提供了不同的方式来创建从颜色到颜色的平滑过渡. 这些内容的具体实现细节在文章Quartz 2D Programming Guide可以看到.

自定义坐标空间

默认情况下, UIKit创建一个简单的当前转换矩阵, 将点映射到像素上. 虽然你可以在不修改矩阵的情况下完成所有绘图, 但有时也会很方便.
当你的视图的drawRect:方法第一次被调用时, CTM会被配置好, 使的视图的origin和坐标系的原点重合, x轴正向往右, y轴正向往下. 但是你可以通过缩放(scaling), 旋转, 位移等操作来改变CTM, 从而改变底层视图或窗口的坐标系的大小, 方向, 位置.

利用坐标变换提高绘图性能

在view中绘图时, 改变CTM是一种常用的技术, 因为这样可以重用path, 也会减少绘画时的计算量. 比如, 如果你希望从点(20, 20)开始绘制正方形, 则可以创建移动到点(20,20)的路径, 然后绘制所需的四条边完成正方形. 但是, 如果你稍后决定将该正方形移动到点(10, 10), 则必须用新的起点重新创建路径. 因为创建路径是一种相对耗性能的操作, 所以最好创建原点在(0,0)的方块, 然后需改CTM, 以便将正方形位移到所需的原点.

在Core Graphic框架中, 有两种方式来修改CTM.

  1. 你可以使用定义在CGContext接口中的函数直接修改CTM
  2. 创建一个CGAffineTransform结构体, 应用各种变换后, 将该变换连接到CTM上.
    使用仿射变换可以对多种变换组合一起, 然后一次性的应用到CTM中, 你还可以评估和反转仿射变换, 并使用这些变换来修改代码中的点, 大小和矩形值. 更多关于仿射变换的信息请看Quartz 2D Programming GuideCGAffineTransform Reference部分.
翻转默认坐标系

坐标系翻转在UIKit绘图中的修改了支持CALayer,以使具有LLO坐标系的绘图环境与UIKit的默认坐标系对齐。如果只使用UIKIT方法和函数进行绘图,则不需要翻转CTM。但是,如果将Core Graphic或图像I/O函数调用与UIKit调用混合,则可能需要翻转CTM。

具体地说,如果通过直接调用Core Graphics函数来绘制图像或PDF文档,则绘图的内容在视图的上下文中将上下颠倒。您必须翻转CTM以正确显示图像和页面。

若要将绘制的内容翻转到Core Graphic上下文,以便在UIKit视图中显示时正确显示,必须分两个步骤修改CTM。将原点转换为绘图区域的左上角,然后应用比例尺转换,将y坐标修改为-1。这样做的代码类似于下面的代码:

CGContextSaveGState(graphicsContext);
CGContextTranslateCTM(graphicsContext, 0.0, imageHeight);
CGContextScaleCTM(graphicsContext, 1.0, -1.0);
CGContextDrawImage(graphicsContext, image, CGRectMake(0, 0, imageWidth, imageHeight));
CGContextRestoreGState(graphicsContext);

如果用CGImageRef来创建UIImage对象, UIKit会为你自动执行翻转转换. 每一个UIImage背后是有CGImageRef来支持的. 你可以通过类UIIMageCGImage属性来访问这个隐藏的对象. 通过这个隐藏的对象我可以对图像做一些特性的处理(Core Graphic有部分针对image的能力是UIKit不具备的). 当你对CGImageRef对象做完特殊处理后, 你可以用它在创建UIImage对象.

注意:您可以使用核心图形函数CGContextDrawImage将图像绘制到任何渲染目的地。这个函数有两个参数,第一个参数用于图形上下文,第二个参数用于矩形,矩形定义了图像的大小及其在绘图表面(如视图)中的位置。当你使用CGContextDrawImage绘制图像后,如果你不调整当前的坐标系统到LLO定位,图像在UIKit视图中的显示时倒立的。此外,该矩形传递到该函数的原点是相对于当被调用函数中的坐标系原点的。

使用不同坐标绘图的副作用

当参照一种绘图技术的默认坐标系绘制对象并在另一种绘图技术的图形上下文中呈现对象时,一些渲染怪异就会显现出来。你可能需要调整你的代码来考虑这些副作用。

圆弧与旋转
如果使用诸如CGContextAddArcCGPathAddArc之类的函数绘制路径并假定为LLO坐标系,则需要翻转CTM以在UIKit视图中正确呈现弧。但是,如果使用相同的函数创建带有位于ULO坐标系中的点的弧,然后在UIKit视图中呈现路径,则会注意到该弧是原始弧的改变版本。弧的终止端点现在指向与该端点将使用UIBezierPath类创建的弧相反的方向。例如,向下指向的箭头现在指向向上(如图1-5所示),并且弧形“弯曲”的方向也不同。考虑基于ULO的坐标系,您必须更改Core Graphic绘制的弧的方向;这个方向由这些函数的startAngleendAngle参数控制。

图2-5 使用Core Graphic和UIKit绘制的弧

如果你旋转一个对象(例如,通过调用CGContextRotateCTM),你可以观察到同样的镜像效果。如果使用引用ULO坐标系的Core Graphic调用旋转对象,则在UIKit中呈现对象的方向是相反的。使用CGContextRotateCTM时,您必须在代码中考虑不同的旋转方向;可以通过反转角度参数的符号(例如,负值变成正值)来完成此操作。

阴影
阴影从其对象上落下的方向由偏移值指定,并且该偏移的含义是绘图框架的一种约定。在UIKit中,正X和Y偏移使阴影向下并向着物体的右侧。在Core Graphic中,正X和Y偏移使阴影上升到物体的右边。翻转CTM以使对象与UIKit的默认坐标系对齐不会影响对象的阴影,因此阴影不会正确跟踪对象。要使其正确跟踪,必须对当前坐标系适当地修改偏移值。

注意:在iOS 3.2之前,Core Graphics和UIKit共享了阴影方向的相同约定:正偏移值使阴影向下移动并指向对象的右侧。

应用Core Animation来制造效果


Core Animation是一个Objective-C框架,它为快速和方便地创建流畅、实时的动画提供了基础设施。核心动画本身不是绘图技术,因为它不提供用于创建形状、图像或其他类型的内容的原始功能。相反,它是一种用来操作和显示内容(使用其他绘图技术创建的)的技术。

大多数应用程序都可以从iOS中的某种形式的核心动画中获益。动画可以给用户关于正在发生的事情提供一种反馈。例如,当用户在“设置”应用程序中导航时,屏幕会根据用户是沿着首选项层次结构进一步导航还是返回到根节点而滑动进出视图。这种反馈是重要的,并为用户提供上下文信息。它还增强了应用程序的视觉风格。

在大多数情况下,你可以轻松地获得核心动画的好处。例如,UIView类的几个属性(包括视图的frame、中心、颜色和不透明度等)可以被配置为在值改变时触发动画。您必须做一些工作来让UIKit知道您希望执行这些动画,但是动画本身是为您创建并自动运行的。有关如何触发内置视图动画的信息,请参见UIView Class Reference中的动画内容。

当你想做些基本动画外事情时,你必须更直接地与核心动画类和方法进行交互。以下部分提供有关Core Animation的信息,并展示如何使用它的类和方法在iOS中创建典型的动画。有关核心动画和如何使用它的更多信息,请参见核心动画编程指南

关于Layer

Core Animation的关键技术是layer对象。layer是轻量级对象,本质上类似于视图,但实际上是模型对象,它封装了要显示的内容的几何、定时和可视属性。内容本身以三种方式之一提供:

  • 可以将CGImageRef分配给layer对象的contents属性
  • 可以将delegate分配给图层,并让delegate处理绘图
  • 可以对CALayer子类进行重写,并重写其中一种显示方法。

当您操纵层对象的属性时,您实际操纵的是模型级数据,它决定了应该如何显示关联的内容。该内容的实际渲染与代码分开处理,并进行了优化,以确保其快速完成。您必须做的是设置layer内容,配置动画属性,然后让Core Animation接管。

关于动画

当涉及到动画层,核心动画使用单独的动画对象来控制动画的时间和行为。CAAnimation类及其子类提供了可以在代码中使用的不同类型的动画行为。您可以创建将属性从一个值迁移到另一个值的简单动画,或者您可以创建复杂的关键帧动画,该关键帧动画通过您提供的一组值和定时函数跟踪动画。

核心动画还可以将多个动画组合成一个单一的单元,称为事务。CATransaction对象管理一组动画来作为一个动画单元。你也可以使用这个类的方法来设置动画的持续时间。

有关如何创建自定义动画的示例,参加demoAnimation Types and Timing Programming Guide

考虑缩放因子和Core Animation Layer

直接使用核心动画layer来提供内容的应用程序可能需要调整绘图代码中的scale因子。通常,当您在视图的drawRect:方法中或在layer的委托方法drawLayer:inContext:中绘制时,系统会自动调整图形上下文的scale因子。然而,当你的view做下列操作时,知道或改变这个scale因子仍然是必要的:

  • 创建具有不同scale因子的额外的核心动画layer,并将它们组合成自己的内容
  • 直接设置核心动画layer的contents属性

Core Animation的合成引擎查看每个层的contentsScale属性,以确定在合成期间是否需要缩放该层的内容。如果应用程序创建没有关联view的layer,则每个新layer对象的Scale因子最初被设置为1.0。如果不改变比例因子,如果随后在高分辨率屏幕上绘制图层,则自动调整图层的内容以补偿比例因子的差异。如果不希望缩放内容,可以通过为contentsScale属性设置新值将层的缩放因子更改为2.0,但是如果这样做而不提供高分辨率内容,则现有内容可能看起来比预期的小。要解决这个问题,你需要为你的层提供更高分辨率的内容。

重要提示:层的contentsGravity属性在决定是否在高分辨率屏幕上缩放标准分辨率的layer中的内容方面发挥作用。默认情况下,此属性设置为值kCAGravityResize,这导致层内容被缩放以适应层的边界。将gravity更改为非调整大小(nonresizing)选项会消除自动缩放,否则将发生缩放。在这种情况下,您可能需要相应地调整内容或比例因子(scale)。

当直接设置layer的contents属性时,调整layer的content以适应不同的scale因子是明智选择。Quartz图像没有scale因子的概念,因此直接与像素一起工作。因此,在创建用为layer内容的CGImageRef对象之前,请检查scale因子并相应地调整图像的大小。特别地,从应用程序bundle中加载适当大小的图像,或者使用UIGraphicsBeginImageContextWithOptions函数创建图像时, 其scale因子需要与layer的scale因子匹配的。如果不创建高分辨率位图,则现有的bitmap可以按前面讨论的那样缩放。

关于如何设置和加载高分辨率图像请看Loading Images into Your App,

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,290评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,399评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,021评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,034评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,412评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,651评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,902评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,605评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,339评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,586评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,076评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,400评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,060评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,851评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,685评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,595评论 2 270

推荐阅读更多精彩内容