Core Animation 学习小记

UIView负责交互事件的处理,CALayer负责视图的展示,各司其职。

backing image

UIViewCALayer作用
contentsUIImage.cgImage进行赋值,可以让UIView展示图片,称为backing image
contentsRect展示backing image的指定区域内容,rect按比例指定
contentsCenter当layer大小改变时,切分出backing image的被拉伸压缩和不改变的区域
cotentModecontentsGravity图片展示的模式,包括拉伸、位置等
contentScaleFactorcontentsScale1point包含的pixel个数,越小图片展示出来越大,可能会超出视图的范围
clipsToBoundsmasksToBounds决定是否展示超出边界的内容

coordinate systems

UIViewCALayer作用
frameframe在父视图中的位置和大小
boundsbounds定义子视图位置的参考系,定义某个点为(0, 0)之后子视图的frame就相对该点进行确定,改变该属性子视图的位置将发生变化
centerposition在父视图坐标系中的中心坐标

CALayer还有一个anchorPoint属性,默认值为(0.5, 0.5),表示该点位于视图和图层的中心,视图的transform以该属性确定的位置作为支点。改变该属性 position的坐标并不会发生变化,但是frame会做出调整 -> 使得anchorPoint确定的值(百分比)满足在视图中的位置。示例如下:

现在想要改变三张图片的旋转基点,注意参考坐标系x从左往右,y从上到下。

self.secondHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f); 
self.minuteHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f); 
self.hourHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);

效果如下图:
在这里插入图片描述

确定不同坐标系下点的坐标

CALayer具有这样的方法:convert(_ p: CGPoint, from l: CALayer?)convert(_ p: CGPoint, to l: CALayer?),将其它layer坐标系下的点转化为自己坐标系下的点或者相反。

CALayer还可以通过contains(_ p: CGPoint)方法判断点是否在自己的frame范围之内。但是当父视图有多个子视图时,有更好的方法去判断父视图坐标系下的一个点首先满足哪个子视图:hitTest(_ p: CGPoint) -> CALayer?,该方法根据子视图的zPosition大小由上到下依次判断。

自动布局

当父视图的尺寸发生改变时(旋转屏幕)可以通过CALayerautoresizingMask对其子视图进行自动布局约束。比如指定其中一个子图层的该属性为[.flexibleBottomMargin, .flexibleTopMargin],表示自动布局时该子图层调整大小使得到父视图上边距和下边距的距离不变。

visual effects

边框

CALayer可以控制borderWidthborderColor,绘制为内边框且显示在子图层之上。

阴影

CALayer作用
shadowOpacity阴影的不透明度,默认为0
shadowColor阴影的颜色,默认为黑色
shadowOffset阴影的偏移,为CGSize类型 ,默认为(0, -3)
shadowRadius阴影的模糊度,默人为0,越大阴影越模糊,图层的深度更加明显

与边框不同的是,阴影会根据图层的外形进行渲染,且在边框之外显示。带来的问题就是如果masksToBoundstrue,阴影将不会显示。正确的做法是再创建一个外图层用来显示阴影,内图层则负责剪裁。

由于阴影继承自图层内容,所以计算阴影的形状非常的消耗资源,所以最好通过shadowPath指定阴影的形状,需要注意的是使用UIBezierPath的话里面的坐标都是以layer所属视图为参考系的,UIKit渲染的会更加容易。

不透明度

UIViewalphaCALayeropacity属性都是用来确定视图的不透明度,这两个属性都是影响子层级的,如果想让每个子视图拥有不同的透明度

transforms

CGAffineTransform

仿射变换相当于对图层的每个点在平面上进行了坐标变换:
在这里插入图片描述在这里插入图片描述
ad代表了拉伸/缩小系数,bc与旋转有关。下面看一个例子:

    @objc func doTransform() {
        var transform = CGAffineTransform.identity
        transform = transform.rotated(by: .pi / 4)			//顺时针旋转45度
        transform = transform.scaledBy(x: 2, y: 2)		    //放大2倍
        transform = transform.translatedBy(x: 100, y: 100)  //向右、向下平移100
        tempView.layer.setAffineTransform(transform)
    }

CATransform3D

在三维空间进行变换增加了z坐标轴,旋转需要注意的点比较多,首先看一下rotate的方法:func CATransform3DRotate(_ t: CATransform3D, _ angle: CGFloat, _ x: CGFloat, _ y: CGFloat, _ z: CGFloat) -> CATransform3Dx、y、z指的是旋转的中心轴线,一般想按哪个轴线就赋值1
在这里插入图片描述
由于是三维视角,如果不通过属性m34 = -1.0 / d指定透视效果,则达不到想要的效果,d越小透视效果越明显,一般指定500~1000。示例如下:

    @objc func do3DTransform() {
        var transform3D = CATransform3DIdentity
        //注意改属性要在变换前指定,否则无效
        transform3D.m34 = -1 / 500.0
        transform3D = CATransform3DRotate(transform3D, .pi / 4, 0, 1, 0)
        tempView.layer.transform = transform3D
    }

在这里插入图片描述
如果想为子图层统一指定透视效果,可以用sublayerTransform属性,相当于一个相机,并且该相机可以跟图层一样旋转,从而调整观察的角度。

如果旋转180度,背面其实默认会绘制镜像图层,如果用不到将会造成GPU浪费,可以调整doubleSided的值控制是否绘制背面的图层。

接下来看一个有趣的现象:

        //rotate the outer layer 45 degrees
        var outer = CATransform3DIdentity
        outer.m34 = -1 / 500.0
        outer = CATransform3DRotate(outer, .pi / 4, 0, 0, 1)
        alphaButton.layer.transform = outer
       //rotate the inner layer -45 degrees
        var inner = CATransform3DIdentity
        inner.m34 = -1 / 500.0
        inner = CATransform3DRotate(inner, -.pi / 4, 0, 0, 1)
        alphaLabel.layer.transform = inner

首先明确的是当父视图旋转时子视图会跟着旋转,绕z轴旋转相当于平面变换,子视图旋转-45度相当于抵消了父视图的旋转,视觉上看上去没有发生什么变换。如果二者绕xy轴进行旋转,则不会低消,取而代之的是每个视图做自己的变换,不受父视图影响。
在这里插入图片描述

Dedicated layer

CAGradientLayer

用来做图层颜色的渐变:

extension UIView {
    // 注意,颜色要是cgColor,否则不会生效
    func gradientColor(colors: [Any], locations: [NSNumber], startPoint: CGPoint, endPoint: CGPoint) {
        let gralayer = CAGradientLayer()
        //注意当UIView的bounds确定时才会生效,当用Snapkit约束确定大小时要在ViewDidAppear时调用该方法
        gralayer.frame = layer.bounds
        layer.addSublayer(gralayer)
        gralayer.colors = colors
        //每个值代表对应颜色的结束点,第一个值最好不要设成0
        gralayer.locations = locations
        gralayer.startPoint = startPoint
        gralayer.endPoint = endPoint
    }
}

CAShapeLayer

用来绘制一定形状的图层,给图层增加若干个圆角方法如下:

extension UIView {
	//注意UIView的bounds确定才会生效
	func round(corners: UIRectCorner, radius: CGFloat) {
        let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        let mask = CAShapeLayer()
        mask.path = path.cgPath
        layer.mask = mask
    }
}

Animation

Implicit Animation

隐式动画通过CATransaction实现,隐式的意思是说不需要指定动画的类型,当改变图层的某个属性时会添加默认的动画效果。比如:

    @objc func changeColor() {
        CATransaction.begin()
        CATransaction.setAnimationDuration(2)
        CATransaction.setCompletionBlock {
            var transform = CGAffineTransform.identity
            transform = transform.rotated(by: CGFloat(self.rotateCount) * .pi / 4)
            self.rotateCount += 1
            self.transactionLayer.setAffineTransform(transform)
        }
        let red =  CGFloat(Float(arc4random()) / Float(UINT32_MAX))
        let green =  CGFloat(Float(arc4random()) / Float(UINT32_MAX))
        let blue = CGFloat(Float(arc4random()) / Float(UINT32_MAX))
        transactionLayer.backgroundColor = UIColor(red: red, green: green, blue: blue, alpha: 1).cgColor
        CATransaction.commit()
    }

注意transactionLayer并不依赖于UIView存在,如果要对UIView的关联图层进行同样的操作,则设置的渐变时间不生效,这是因为其隐式动画默认被禁用了。除此之外,还可以通过过渡动画调整隐式动画的效果,如下:

    private lazy var transactionLayer: CALayer = {
        let layer = CALayer()
        layer.backgroundColor = UIColor.brown.cgColor
        //变化时从左侧渐入改变颜色
        var transition = CATransition()
        transition.type = .push
        transition.subtype = .fromLeft
        layer.actions = ["backgroundColor": transition]
        return layer
    }()

Explicit Animation

可以看下面一段示例:

    @objc func groupAnimation() {
        let bezierPath = UIBezierPath()
        bezierPath.move(to: CGPoint(x: 50, y: 150))
        bezierPath.addCurve(to: CGPoint(x: 300, y: 150), controlPoint1: CGPoint(x: 150, y: 0), controlPoint2: CGPoint(x: 200, y: 300))
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = bezierPath.cgPath
        shapeLayer.strokeColor = UIColor.red.cgColor
        view.layer.addSublayer(shapeLayer)
        let colorLayer = CALayer()
        colorLayer.frame = CGRect(x: 0, y: 0, width: 64, height: 64)
        colorLayer.position = CGPoint(x: 50, y: 150)
        colorLayer.backgroundColor = UIColor.green.cgColor
        view.layer.addSublayer(colorLayer)
        //帧动画
        let keyAnimation = CAKeyframeAnimation(keyPath: "position")
        keyAnimation.path = bezierPath.cgPath
        keyAnimation.rotationMode = .rotateAuto
        //属性动画
        let basicAnimation = CABasicAnimation(keyPath: "backgroundColor")
        basicAnimation.toValue = UIColor.red.cgColor
        //组动画
        let groupAnimation = CAAnimationGroup()
        groupAnimation.animations = [keyAnimation, basicAnimation]
        groupAnimation.duration = 4
        colorLayer.add(groupAnimation, forKey: nil)
    }

显式动画做完之后会恢复视图之前的属性,如果想在动画结束之后保存最终状态,可以通过实现CAAnimationDelegateanimationDidStop(_ anim: CAAnimation, finished flag: Bool)方法设置。如果有很多动画都设置该代理,可以通过setValue(_ value: Any?, forKey key: String) value(forKey key: String)区分。

CAPropertyAnimationkeyPath针对transform推出了虚拟属性,旋转缩放可用transform.rotation/scale

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值