iOS CoreAnimation(四) anchorPoint,锚点

在了解锚点之前,需要了解 UIView 三个属性:frame,bounds,center

“这三个属性太简单了吧?” 

假设 view2 是 view1 的子视图,view1 设置 bounds.origin 为 (-30, -30),那么 view2 视觉上会向右、下方各移动 30,view1 位置不变。如果这个例子理解了,就差不多了,后面也会解释。

PS:这三个属性,CALayer 对应地叫做:frame,bounds,position。UIView 提供的是他们的 getter、setter,后文都用 CALayer 的属性名。

 

frame:origin 和 size 分别描述 在父图层坐标系下的坐标 和 图层大小(是给外部看的)

bounds:origin 描述图层左上角在内部坐标系下的坐标;size 描述内部坐标系下 图层的大小(是给内部用的)

position:锚点 anchorPoint 在父图层坐标系下的坐标(UIView 中叫 center)

anchorPoint:可以理解为图层的“把柄”,是一个点,用的是单位坐标,默认值(0.5,0.5)

这些属性平时感觉挺简单的,为什么解释的那么复杂呢?通过下面几个例子来看。 

 

如图,frame 描述了图层在父图层坐标系中的位置、大小,很好理解

bounds 指定图层左上角在自己的坐标系中的坐标为 (0,0),以及图层大小,也很好理解

anchorPoint 是默认值 (0.5, 0.5),也就是图层中心点

position(center)是 anchorPoint 在父图层坐标系中的坐标,(30, 35)

先理解一下锚点作为 把柄 的思想:此时如果只修改 position 属性值,由于 anchorPoint 没变,仍代表中心点,那么图层肯定会随着中心点的坐标变化而平移,即在父图层上的位置随着 position 改变。(frame 是计算属性,此时读取 frame 肯定也更新了)

那么如果我修改了锚点的值呢?也就是把柄的位置变了,由于锚点的 position 没变,也就是锚点的绝对位置(在父图层坐标系的坐标)不变,看下图,锚点设置为 (0, 0),也就是图层的左上角是把柄,position 没变,把柄必须放在 position 位置,最终结果是,图层必须调整自己的位置,适应新锚点。

 

回到前面提的问题,view1 设置了 bounds.origin,表示图层左上角在内部坐标系中的坐标是(-30,-30),那(0,0)点肯定右下移动了 30,左上角不再是(0,0)。

view2 的位置变化就有两种理解方式了:浅层的理解是,view2 的 frame 没变,(0,0)点位置变了,肯定也“跟过去”;实际是因为 view2 的 position 没变,仍为 00,把柄被抓住向右下移动 30

再强调一下,frame 由 bounds,position(anchorPoint),transform 及其他属性* 计算得出,transform 以后会详细介绍,下面只是一个例子:视图在设置 transform 旋转后,frame 的大小将代表覆盖整个矩形区域的大小,但 bounds 保持不变,frame 的宽高和 bounds 的宽高可能不再一致了。

刚接触这方面的可能就懵了,再来一个例子:视图设置 transform 变小 0.6 倍,frame 和 bounds 的区别:

bounds 的宽是 375,frame 的宽是 225!本文最开始已经介绍,frame 更像是暴露给外部用的,告诉同级视图自己的位置、大小;bounds 像是内部布局使用。

内部图层使用正常的 bounds 进行布局,最后当前视图施加 transform,对当前渲染结果“截图”后缩放,形成最终显示的内容,新的 frame 也可以计算得出了。

*其他属性:后面系列的文章会详细介绍 transform、alignmentRectInsets 等影响 frame 计算结果的属性。

锚点有什么用呢?

为什么要通过设置锚点改变图层的位置,我用 frame 不简单吗?其实这个主要用于动画,有个钟表 demo。

我们有3张表针的图片,要让图片旋转也很容易,一个属性就可以搞定(transform,以后会细说),关键是,它转的时候,是以中心点为圆心转圈对吧?怎么样让他以表针的一端为中心转呢?诶,为什么会以中心点为圆心?(这里重点在锚点,旋转、transform 先不急)

为什么锚点就是旋转时的圆心呢?其实不仅在旋转时,在设置任意 transfrom、手动设置 bounds.size 等情况 都与锚点有关。简单的例子,比如 bounds 的宽增大了40,因为 [ position 没变 且 锚点没变 ]  那么 锚点在父图层的坐标不变,为了维持这个特性,该图层只能以锚点为中心,左右各扩张 20,来吸收共 40 点变化,最后 frame.x 也只减少 20。旋转时,如果要保持 position 不变 + 锚点不变,唯一能实现的办法是,以锚点为圆心!!!

一定要理解 锚点 anchorPoint 和 锚点(在父图层)的坐标 position 这两个概念,对于以后文章非常重要

下图是用了默认的锚点的效果,如果设置锚点在 (0.5, 0.9),即图层偏下的位置,想象一下,为了保持 position 不变,图层只能上移。旋转时,上面已经论提过了,会以锚点为圆心。

下面是 demo 代码,随便一看就行了,前面的概念更重要

class ViewController: UIViewController {
    // 这里为了只展示核心逻辑,视图都用的 storyboard
    // 表盘图片
    @IBOutlet weak var board: UIImageView!
    // 分针图片
    @IBOutlet weak var minutePointer: UIImageView!
    // 时针图片
    @IBOutlet weak var hourPointer: UIImageView!
    // 秒针图片
    @IBOutlet weak var secondPointer: UIImageView!
    
    weak var timer: Timer?
    
    override func viewDidLoad() {
        
        super.viewDidLoad()
        // 下面三行可以先不写,试试效果,再加上
        hourPointer.layer.anchorPoint = CGPoint(x: 0.5, y: 0.95)
        minutePointer.layer.anchorPoint = CGPoint(x: 0.5, y: 0.95)
        secondPointer.layer.anchorPoint = CGPoint(x: 0.5, y: 0.95)
        
        // 每秒触发一次,改指针旋转角度
        timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(tick), userInfo: nil, repeats: true)
        timer!.fire()
    }
    
    @objc func tick() {
        let (hour, minute, second) = time
        let hAngle = CGFloat.pi*CGFloat(hour)/6
        let mAngle = CGFloat.pi*CGFloat(minute)/30
        let sAngle = CGFloat.pi*CGFloat(second)/30
         
        minutePointer.transform = CGAffineTransform(rotationAngle: mAngle)
        secondPointer.transform = CGAffineTransform(rotationAngle: sAngle)
        hourPointer.transform = CGAffineTransform(rotationAngle: hAngle)
    }
    
    // 下面 获得当前时、分、秒,不重要的代码
    private var time: (Int8, Int8, Int8) {
        let date = Date()
        let formatter = DateFormatter()
        formatter.dateFormat = "HH:mm:ss"
        let dateStr = formatter.string(from: date)
        
        let start2 = dateStr.index(dateStr.startIndex, offsetBy: 2)
        let hour = Int8(dateStr.substring(to: start2))!
        let minite = Int8(dateStr.substring(with: Range(uncheckedBounds: (dateStr.index(start2, offsetBy: 1),dateStr.index(start2, offsetBy: 3) ) )))!
        let sec = Int8(dateStr.substring(with: Range(uncheckedBounds: (dateStr.index(start2, offsetBy: 4),dateStr.index(start2, offsetBy: 6) ) )))! - 1
        return (hour, minite, sec)
    }
}

    设置锚点后 效果就正常了。

     

}

一定要理解 position 与 anchorPoint,以及 bounds 与 frame,不然后面到 transform 没法理解。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值