我有一个可以调用的子类UIView CircleView.CircleView会自动将圆角半径设置为其宽度的一半,以使其成为圆形.
问题是当"CircleView"通过AutoLayout约束调整大小时......例如在设备轮换上 ......由于"cornerRadius"属性必须赶上,并且操作系统仅发送,因此在调整大小之前它会严重失真单个"边界"更改为视图的框架.
我想知道是否有人有一个好的,明确的策略来实现"CircleView"的方式在这种情况下不会扭曲,但仍然会将其内容掩盖为圆形并允许在所述UIView周围存在边框.
rob*_*off 39
从iOS 11开始,cornerRadius如果您在动画块中更新它,UIKit将动画.只需layer.cornerRadius在UIView动画块中设置视图,或(以处理界面方向更改),将其设置为layoutSubviews或viewDidLayoutSubviews.
所以你想要这个:
(我启用了Debug> Slow Animations以使平滑更容易看到.)
旁边的咆哮,随意跳过这一段:事实证明这比应该更难,因为iOS SDK没有以方便的方式提供自转旋转动画的参数(持续时间,时间曲线).您可以(我认为)通过覆盖-viewWillTransitionToSize:withTransitionCoordinator:您的视图控制器来调用-animateAlongsideTransition:completion:转换协调器,并在您通过的回调中获取transitionDuration和completionCurve从中获取UIViewControllerTransitionCoordinatorContext.然后你需要将这些信息传递给你CircleView,它必须保存它(因为它还没有被调整大小!),然后当它收到时layoutSubviews,它可以用它来创建一个带有那些保存的动画参数的CABasicAnimationfor 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
}
Run Code Online (Sandbox Code Playgroud)
请注意,视图的边界在视图接收之前设置layoutSubviews,因此在我们更新之前设置cornerRadius.这就是在请求动画bounds.size之前安装cornerRadius动画的原因.每个属性的动画都安装在属性的setter中.
当我们设置时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)
}
Run Code Online (Sandbox Code Playgroud)
在上面的代码中,如果我们被要求采取行动cornerRadius,我们会寻找一个CABasicAnimation开启bounds.size.如果我们找到一个,我们将其复制,更改密钥路径cornerRadius,并将其保存在自定义CAAction(类Action,我将在下面显示)中.我们还保存cornerRadius属性的当前值,因为Core Animation actionForLayer: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
Run Code Online (Sandbox Code Playgroud)
该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
Run Code Online (Sandbox Code Playgroud)
我的回答是受到西蒙这个答案的启发.
| 归档时间: |
|
| 查看次数: |
5334 次 |
| 最近记录: |