二.iOS核心动画 - 关于寄宿图

引言

在上一篇博客关于图层和视图中已经认识了CALayer,并且动手创建了一个白色的和一个红色的图层。事实上CALayer不仅能展示单调的颜色,还能包含一张任意的图片。这一篇博客我们就来探索一下CALayer的寄宿图。

CALayer 属性

CALayer作为图层,和视图一样,它也有很多属性,而且有些属性你会发现它和视图的某些属性是相对应的,比如frame,bounds,position,mask等等,还有一些熟悉是它所有特有的,下面我们就来介绍一下它。

contents

contents属性被定义为Any的可选类型,在OC中被定义为id类型,意味这它可以是任何类型的对象。无论我们给contents熟悉赋值什么,它都可以编译通过并且正常运行,但是如果你给它赋的不是CGImage,那么将什么也看不见。

这个奇怪的表现是Mac OS的历史原因造成,它之所以被定义为任意类型,是因为在Mac OS系统上,这个属性对CGImage和NSImage类型都起作用。

事实上,我们真正要赋值的类型应该是CGImageRef,它是一个指向CGImage的结构的指针。UIImage有一个CGImage属性,它返回一个“CGImageRef”,在OC中如果你想把这个值直接复制给CALayer的contents,编译的时候将会报错,因为CGImageRef并不是一个真正的Cocoa对象,而是一个Core Foundation类型。

不过我们可以使用bridged关键字转换:

layer.contents = (__bridge id)image.CGImage;

在Swift中我们可以直接使用:

let layer = CALayer()

layer.contents = UIImage(named: "image")?.cgImage

让我们创建一个CALayer来展示一张图片。


    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .black
        let layer = CALayer()
        layer.backgroundColor = UIColor.white.cgColor
        layer.contents = UIImage(named: "apple")?.cgImage
        layer.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
        layer.position = self.view.center
        self.view.layer.addSublayer(layer)
    }

实现效果如下:

我们创建了一个白色的CALayer,并为它的contents设置了一张寄宿图片。

在这里并没有使用UIImageView来加载图片,那么任何继承自UIView的视图都可以加载图片了,因为每个视图都对应一个图层。

contentGravity

上面的Apple虽然加载出来了,但是看起来有一点奇怪,虽然并不明显。我们回忆一下当使用UIImageView加载图片时,默认图片充满整个视图,超过的部分会压缩,不足的部分会拉伸。

使用CALayer加载寄宿图也会有相同的问题。在UIImageView中我们使用它的contentMode属性来修改图片的填充方式。

CALayer中有一个对应的属性contentsGravity,contentsGravity有以下值可以供选择:

    /** Layer `contentsGravity' values. **/
    @available(iOS 2.0, *)
    public static let center: CALayerContentsGravity

    
    @available(iOS 2.0, *)
    public static let top: CALayerContentsGravity

    
    @available(iOS 2.0, *)
    public static let bottom: CALayerContentsGravity

    
    @available(iOS 2.0, *)
    public static let left: CALayerContentsGravity

    
    @available(iOS 2.0, *)
    public static let right: CALayerContentsGravity

    
    @available(iOS 2.0, *)
    public static let topLeft: CALayerContentsGravity

    
    @available(iOS 2.0, *)
    public static let topRight: CALayerContentsGravity

    
    @available(iOS 2.0, *)
    public static let bottomLeft: CALayerContentsGravity

    
    @available(iOS 2.0, *)
    public static let bottomRight: CALayerContentsGravity

    
    @available(iOS 2.0, *)
    public static let resize: CALayerContentsGravity

    
    @available(iOS 2.0, *)
    public static let resizeAspect: CALayerContentsGravity

    
    @available(iOS 2.0, *)
    public static let resizeAspectFill: CALayerContentsGravity

和contentMode一样,contentsGravity决定了图层在视图中的拉伸和缩放方式,我们使用resizeAspectFill等同于content的scaleAspectFit。

layer.contentsGravity = .resizeAspectFill

我们来看一下设置之后的对比效果。

这样看起来就正常了许多了吧。

contentsScale

contentsScale属性使用起来并不明显,尤其是在我们设置了contentsGravity属性之后,contentsScale的改变几乎没有任何效果。

contentsScale实际上定义了寄宿图的像素春和视图大小的比例,默认是1.0。

contentsScale属性其实属于支持高分辨率屏幕机制的一部分,它用来判断在绘制图层的时候应该为寄宿图创建的空间大小,和需要显示的图片的拉伸程度。UIView有一个相同的功能的属性,但是非常少用到contentScaleFactor属性。

如果contentsScale设置为1.0,将会以每个点1个像素绘制图片,如果设置为2.0,则会以每个点2个像素绘制图片。

但是如果我们设置了contentsGravity属性为resizeAspect那么修改contentsScale不会有任何影响,因为图片会自动拉伸来适应图层,根本涉及不到分辨率的问题,不过我们可以把contentsGravity设置为center来看看效果。

        let layer = CALayer()
        layer.backgroundColor = UIColor.white.cgColor
        layer.contents = UIImage(named: "apple")?.cgImage
        layer.contentsGravity = .center
        layer.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
        layer.position = self.view.center

效果如下:

​​​​​​​

可以看见,图片显示的非常大,是图片的原始尺寸257*299。

因为CGImage没有拉伸的概念,当我们使用CGImage设置图层的寄宿图时它的拉伸因素就丢失了,不过我们可以手动进行设置。

        let layer = CALayer()
        layer.backgroundColor = UIColor.white.cgColor
        let image =  UIImage(named: "apple")
        layer.contents = image?.cgImage
        layer.contentsGravity = .center
        layer.contentsScale = image!.scale
        layer.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
        layer.position = self.view.center
        self.view.layer.addSublayer(layer)

效果如下:

可以看见,这样图片显示的就正常了许多,因为我们使用的是3倍图,当前显示的图片尺寸为85.7*99.7。

所以当我们使用图层来加载图片时,一定要手动设置图层的contentsScale属性,不过有个通用的写法如下:

layer.contentsScale = UIScreen.main.scale

maskToBounds

这个属性我们应该不陌生,在为视图设置圆角的时候我们经常会使用到它。当我们设置该属性为ture的时候,那么超出边界的部分就会被裁剪掉。

UIView也有相同的作用的属性叫做clipsTobounds。

在介绍contentGravity属性的时候我们采用了一个resizeAspectFill会发现顶部和底部会有些部分超出了图层,下面我们就使用maskToBounds属性让它不显示超出的部分:

        let layer = CALayer()
        layer.backgroundColor = UIColor.white.cgColor
        let image =  UIImage(named: "apple")
        layer.contents = image?.cgImage
        layer.contentsGravity = .resizeAspectFill
        layer.masksToBounds = true
        layer.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
        layer.position = self.view.center
        self.view.layer.addSublayer(layer)

效果如下:

​​​​​​​

contentsRect

这个属性允许我们在图层边框里显示一个图片的子域。contentsRect和bounds还有frame不同,它不是按照点来甲酸的,而是使用了单位坐标0~1,是一个相对的值,所以它是相对图片的尺寸而言的。

iOS中使用了以下3种坐标系:

  • 点:在iOS中最常见的坐标体系就是点,属于虚拟的像素,也被称作是逻辑像素。在标准设备上,一个点就是一个像素,但是在Retina屏上,一个点等于2*2个像素。iOS使用点作为屏幕的坐标体系就是为了在不同屏幕上能有一致的视觉效果。
  • 像素:物理像素通常我们并不用来进行布局,但是仍然与图片有相对关系。UIImage会自动适应屏幕的分辨率,但是一些底层的图片比如CGImage就会使用像素,所以加载CGImage的时候在不同分辨率的屏幕上会有不同的表现。
  • 单位:对于与图片大小或是图层边界相关的显示,单位坐标是一个方便的度量方式,当大小改变的时候也不需要再次调整。单位坐标在OpenGL这种纹理坐标系中用的很多。

contentsRect的默认值是{0,0,1,1},这就意味着整个寄宿图默认都是可见的。

如果我们指定了一个小点的矩形,比如{0,0,0.5,0.5},那就意味着只有1/4内容会显示,其余的部分将会被裁剪掉。

        let layer = CALayer()
        layer.backgroundColor = UIColor.white.cgColor
        let image =  UIImage(named: "apple")
        layer.contents = image?.cgImage
        layer.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
        layer.contentsRect = CGRect(x: 0, y: 0, width: 1.0, height: 1.0)
        layer.position = self.view.center
        self.view.layer.addSublayer(layer)

当设置为{0,0,1,1}时效果如下,也就是默认的状态:

layer.contentsRect = CGRect(x: 0, y: 0, width: 0.5, height: 0.5)

当修改contentsRect为{0,0,0.5,0.5}时,效果如下:

layer.contentsRect = CGRect(x: 0, y: 0, width: 1.5, height: 1.5)

当我们设置它为{0,0,1.5,1.5}时,看一下效果:

我们发现这样设置也是可以的,这种情况下显示的区域会变大,并且最外面的像素会被拉伸来填充剩下的区域。

contentsCenter

这个属性名字听起来像是要设置寄宿的中心位置,但其实它是一个CGRect而不是CGPoint。contentsCenter定义了一个固定的边框和在一个图层上可以进行拉伸的区域。
直接修改contentsCenter的值并不会看见任何效果,只有当图层发生拉伸时才能看见它的效果。

默认情况下,contentsCenter是{0,0,1,1},(当conttensGravity为默认值,或者没有设置conttensGravity的时候)这就意味着如果图层的大小发生改变,图片会被均匀地拉伸。

为了更清晰的演示这个效果,我们换一张图片,图片的原始宽高比例为137/226。

当contentsCenter的值为默认值时,代码如下:

        let layer = CALayer()
        layer.backgroundColor = UIColor.white.cgColor
        let image =  UIImage(named: "bounds")
        layer.contents = image?.cgImage
        layer.frame = CGRect(x: 0, y: 0, width: 226, height: 137)
        layer.position = self.view.center
        self.view.layer.addSublayer(layer)

效果如下:

接下来我们修改一下图层的大小,首先修改视图的宽度为350,看一下效果:

layer.frame = CGRect(x: 0, y: 0, width: 350, height: 137)

我们发现图片整体发生了拉伸,包括星星和圆都被拉伸变形了。

然后设置contentsCenter的值为{0.25,0,0.5,1}

        layer.frame = CGRect(x: 0, y: 0, width: 350, height: 137)
        layer.contentsCenter = CGRect(x: 0.25, y: 0.0, width: 0.5, height: 1.0)

再看一下运行效果:

可以看见图片虽然被拉伸了,但是四个角的星星并没有发生变形。

这是因为,我们通过contentsCenter设置了寄宿的拉伸区域,将横向拉伸区域设置为从图片宽度的0.25地方开始,总宽度为图片宽度的0.5。

默认拉伸区域-整个图片
修改contentsCenter后的拉伸区域-绿色区域

这意味着我们可以随意重设尺寸,边框仍然会是连续的。他工作起来的效果和UIImage里的-resizableImageWithCapInsets: 方法效果非常类似,只是它可以运用到任何寄宿图,甚至包括在Core Graphics运行时绘制的图形。

结语

本篇博客主要介绍了一些寄宿图相关的属性,相信你对CALayer应该有了一个更深刻的理解。

下一篇博客中,我们将会讨论一下图层的几何,介绍他们的frame,bounds,center,position等等。

相关推荐

  1. Spring 核心IOC 容器学习

    2024-06-17 13:04:01       25 阅读
  2. 关于js的动画效果

    2024-06-17 13:04:01       27 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-17 13:04:01       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-17 13:04:01       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-17 13:04:01       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-17 13:04:01       18 阅读

热门阅读

  1. 创建Docker容器与外部机通信(端口映射的方式)

    2024-06-17 13:04:01       7 阅读
  2. 前端开发之计算机网络模型认识

    2024-06-17 13:04:01       8 阅读
  3. 掌握现代C++的模板元编程类型检测技术

    2024-06-17 13:04:01       8 阅读
  4. LINUX 进阶 3.1

    2024-06-17 13:04:01       7 阅读
  5. 小程序页面路由传参方法

    2024-06-17 13:04:01       7 阅读
  6. VIRT高是因为分配了太多地址空间导致。

    2024-06-17 13:04:01       6 阅读
  7. RabbitMQ消息的可靠传输和防止消息丢失

    2024-06-17 13:04:01       6 阅读
  8. 品质主管的面试题目

    2024-06-17 13:04:01       6 阅读
  9. dayjs将星期的第一天设置为周一

    2024-06-17 13:04:01       4 阅读
  10. 5、存储管理

    2024-06-17 13:04:01       4 阅读