使用AutoLayout调整圆形(圆形)UIView的大小...在调整大小的动画期间如何为cornerRadius设置动画?

本·吉尔德

我有一个可以调用的UIView子类CircleViewCircleView会自动将拐角半径设置为其宽度的一半,以使其成为圆形。

问题是,当“ CircleView”被自动布局约束调整大小时(例如在设备旋转时),由于“ cornerRadius”属性必须赶上,并且OS仅发送,它会严重扭曲直到调整大小为止。对视图框架的单个“边界”更改。

我想知道是否有人有一个很好的,清晰的策略来实现“ CircleView”,这种方式在这种情况下不会变形,但仍会将其内容掩盖成圆形,并允许在UIView周围存在边框。

抢马约夫

更新:如果您的部署目标是iOS 11或更高版本:

从iOS 11开始,cornerRadius如果在动画块中更新UIKit,它将进行动画处理。只需layer.cornerRadiusUIView动画块中设置视图,或(以处理界面方向更改)在layoutSubviews或中设置它即可viewDidLayoutSubviews

原文:如果您的部署目标早于iOS 11:

所以你想要这个:

顺利调整圆圈视图的大小

(我打开了“调试”>“慢动画”以使平滑度更容易看到。)

顺带一提,请随时跳过此段:事实证明,这比应该做的要难得多,因为iOS SDK并未以方便的方式提供自动旋转动画的参数(持续时间,时序曲线)。您可以(我认为)通过重写-viewWillTransitionToSize:withTransitionCoordinator:视图控制器来调用它们,以调用-animateAlongsideTransition:completion:过渡协调器,然后在传递的回调中,从transitionDurationcompletionCurve获取UIViewControllerTransitionCoordinatorContext然后,您需要将该信息传递给您的CircleView,该信息必须保存(因为尚未调整大小!),以后再接收时layoutSubviews,它可以使用这些信息来创建带有已保存动画参数CABasicAnimationfor cornerRadius而且不小心创建一个动画时,它不是一个动画的大小调整...侧耳的末端。

哇,这听起来像是很多工作,而且您必须涉及视图控制器。这是另一种完全在内部实现的方法CircleView它现在可以工作(在iOS 9中),但是我不能保证它将来会一直工作,因为它做出了两个假设,这些假设在理论上将来可能是错误的。

这里的做法:覆盖-actionForLayer:forKey:CircleView返回的动作是,在运行时,安装了一个动画cornerRadius

这是两个假设:

  • bounds.originbounds.size获得单独的动画。(现在是正确的,但是大概将来的iOS可以使用单个动画boundsbounds如果没有bounds.size找到动画,检查动画就足够容易了。)
  • bounds.size在Core Animation要求cornerRadius动作之前动画已添加到图层

根据这些假设,当Core Animation请求cornerRadius动作时,我们可以bounds.size从图层中获取动画,将其复制,然后修改副本以使其具有动画效果cornerRadius该副本具有与原始副本相同的动画参数(除非我们对其进行修改),因此它具有正确的持续时间和时序曲线。

这是开始的CircleView

class CircleView: UIView {

    override func layoutSubviews() {
        super.layoutSubviews()
        updateCornerRadius()
    }

    private func updateCornerRadius() {
        layer.cornerRadius = min(bounds.width, bounds.height) / 2
    }

请注意,视图的边界是在视图接收之前设置的layoutSubviews,因此是在我们更新之前设置的cornerRadius这就是为什么在请求动画bounds.size之前先安装cornerRadius动画的原因。每个属性的动画都安装在属性的设置器中。

设置好后cornerRadius,Core Animation要求我们为其CAAction运行:

    override func action(for layer: CALayer, forKey event: String) -> CAAction? {
        if event == "cornerRadius" {
            if let boundsAnimation = layer.animation(forKey: "bounds.size") as? CABasicAnimation {
                let animation = boundsAnimation.copy() as! CABasicAnimation
                animation.keyPath = "cornerRadius"
                let action = Action()
                action.pendingAnimation = animation
                action.priorCornerRadius = layer.cornerRadius
                return action
            }
        }
        return super.action(for: layer, forKey: event)
    }

在上面的代码中,如果要求我们执行操作cornerRadius,我们将寻找CABasicAnimationon bounds.size如果找到一个,我们将其复制,将密钥路径更改为cornerRadius,然后将其保存在一个自定义CAAction类中Action该类将在下面显示。我们还保存了cornerRadius属性的当前值,因为Core AnimationactionForLayer:forKey: 更新属性之前会调用

之后actionForLayer:forKey:的回报,核心动画更新cornerRadius层的属性。然后它通过发送动作来运行动作runActionForKey:object:arguments:该动作的工作是安装合适的动画。这是的自定义子类CAAction,我嵌套在其中CircleView

    private class Action: NSObject, CAAction {
        var pendingAnimation: CABasicAnimation?
        var priorCornerRadius: CGFloat = 0
        public func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) {
            if let layer = anObject as? CALayer, let pendingAnimation = pendingAnimation {
                if pendingAnimation.isAdditive {
                    pendingAnimation.fromValue = priorCornerRadius - layer.cornerRadius
                    pendingAnimation.toValue = 0
                } else {
                    pendingAnimation.fromValue = priorCornerRadius
                    pendingAnimation.toValue = layer.cornerRadius
                }
                layer.add(pendingAnimation, forKey: "cornerRadius")
            }
        }
    }
} // end of CircleView

runActionForKey:object:arguments:方法设置动画fromValuetoValue属性,然后将动画添加到图层。这很复杂:UIKit使用“附加”动画,因为如果在较早的动画仍在运行时在属性上启动另一个动画,它们会更好地工作。因此,我们的行动将对此进行检查。

如果动画是可加的,则将其设置fromValue为新旧角半径之间的差,并设置toValue为零。由于在cornerRadius动画运行时图层的属性已经更新,因此在动画fromValue开始时添加该属性会使它看起来像旧的拐角半径,而toValue在动画结束时添加零使它看起来像是新的角半径。

如果动画不是可叠加的(据我所知,如果UIKit创建了动画就不会发生),那么它只是以明显的方式设置fromValuetoValue

为了方便起见,以下是整个文件:

import UIKit

class CircleView: UIView {

    override func layoutSubviews() {
        super.layoutSubviews()
        updateCornerRadius()
    }

    private func updateCornerRadius() {
        layer.cornerRadius = min(bounds.width, bounds.height) / 2
    }

    override func action(for layer: CALayer, forKey event: String) -> CAAction? {
        if event == "cornerRadius" {
            if let boundsAnimation = layer.animation(forKey: "bounds.size") as? CABasicAnimation {
                let animation = boundsAnimation.copy() as! CABasicAnimation
                animation.keyPath = "cornerRadius"
                let action = Action()
                action.pendingAnimation = animation
                action.priorCornerRadius = layer.cornerRadius
                return action
            }
        }
        return super.action(for: layer, forKey: event)
    }

    private class Action: NSObject, CAAction {
        var pendingAnimation: CABasicAnimation?
        var priorCornerRadius: CGFloat = 0
        public func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) {
            if let layer = anObject as? CALayer, let pendingAnimation = pendingAnimation {
                if pendingAnimation.isAdditive {
                    pendingAnimation.fromValue = priorCornerRadius - layer.cornerRadius
                    pendingAnimation.toValue = 0
                } else {
                    pendingAnimation.fromValue = priorCornerRadius
                    pendingAnimation.toValue = layer.cornerRadius
                }
                layer.add(pendingAnimation, forKey: "cornerRadius")
            }
        }
    }
} // end of CircleView

我的回答受到西蒙的回答的启发

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

Related 相关文章

热门标签

归档