我有一个可以调用的UIView子类CircleView
。CircleView会自动将拐角半径设置为其宽度的一半,以使其成为圆形。
问题是,当“ CircleView”被自动布局约束调整大小时(例如在设备旋转时),由于“ cornerRadius”属性必须赶上,并且OS仅发送,它会严重扭曲直到调整大小为止。对视图框架的单个“边界”更改。
我想知道是否有人有一个很好的,清晰的策略来实现“ CircleView”,这种方式在这种情况下不会变形,但仍会将其内容掩盖成圆形,并允许在UIView周围存在边框。
从iOS 11开始,cornerRadius
如果在动画块中更新UIKit,它将进行动画处理。只需layer.cornerRadius
在UIView
动画块中设置视图,或(以处理界面方向更改)在layoutSubviews
或中设置它即可viewDidLayoutSubviews
。
所以你想要这个:
(我打开了“调试”>“慢动画”以使平滑度更容易看到。)
顺带一提,请随时跳过此段:事实证明,这比应该做的要难得多,因为iOS SDK并未以方便的方式提供自动旋转动画的参数(持续时间,时序曲线)。您可以(我认为)通过重写-viewWillTransitionToSize:withTransitionCoordinator:
视图控制器来调用它们,以调用-animateAlongsideTransition:completion:
过渡协调器,然后在传递的回调中,从transitionDuration
和completionCurve
获取UIViewControllerTransitionCoordinatorContext
。然后,您需要将该信息传递给您的CircleView
,该信息必须保存(因为尚未调整大小!),以后再接收时layoutSubviews
,它可以使用这些信息来创建带有已保存动画参数的CABasicAnimation
for cornerRadius
。而且不小心创建一个动画时,它不是一个动画的大小调整...侧耳的末端。
哇,这听起来像是很多工作,而且您必须涉及视图控制器。这是另一种完全在内部实现的方法CircleView
。它现在可以工作(在iOS 9中),但是我不能保证它将来会一直工作,因为它做出了两个假设,这些假设在理论上将来可能是错误的。
这里的做法:覆盖-actionForLayer:forKey:
在CircleView
返回的动作是,在运行时,安装了一个动画cornerRadius
。
这是两个假设:
bounds.origin
并bounds.size
获得单独的动画。(现在是正确的,但是大概将来的iOS可以使用单个动画bounds
。bounds
如果没有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
,我们将寻找CABasicAnimation
on 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:
方法设置动画的fromValue
和toValue
属性,然后将动画添加到图层。这很复杂:UIKit使用“附加”动画,因为如果在较早的动画仍在运行时在属性上启动另一个动画,它们会更好地工作。因此,我们的行动将对此进行检查。
如果动画是可加的,则将其设置fromValue
为新旧角半径之间的差,并设置toValue
为零。由于在cornerRadius
动画运行时图层的属性已经更新,因此在动画fromValue
开始时添加该属性会使它看起来像旧的拐角半径,而toValue
在动画结束时添加零使它看起来像是新的角半径。
如果动画不是可叠加的(据我所知,如果UIKit创建了动画就不会发生),那么它只是以明显的方式设置fromValue
和toValue
。
为了方便起见,以下是整个文件:
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] 删除。
我来说两句