我想在简单的 UI 中创建过渡之间的软动画:
移动的视图
当调用scrollToPoint:移动视图以指向该过渡不动画。我是 Cocoa 编程的新手(iOS 是我的背景)。而且我不知道如何正确使用 .animator 或 NSAnimationContext。
我也阅读了核心动画指南,但没有找到解决方案。
请帮忙 !!!
我想在简单的 UI 中创建过渡之间的软动画:
移动的视图
当调用scrollToPoint:移动视图以指向该过渡不动画。我是 Cocoa 编程的新手(iOS 是我的背景)。而且我不知道如何正确使用 .animator 或 NSAnimationContext。
我也阅读了核心动画指南,但没有找到解决方案。
请帮忙 !!!
scrollToPoint 不可动画。只有 NSAnimatablePropertyContainer 中的边界和位置等可动画属性是动画的。你不需要对 CALayer 做任何事情:删除 WantLayer 和 CALayer 的东西。然后使用以下代码对其进行动画处理。
- (void)scrollToXPosition:(float)xCoord {
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:5.0];
NSClipView* clipView = [_scrollView contentView];
NSPoint newOrigin = [clipView bounds].origin;
newOrigin.x = xCoord;
[[clipView animator] setBoundsOrigin:newOrigin];
[_scrollView reflectScrolledClipView: [_scrollView contentView]]; // may not bee necessary
[NSAnimationContext endGrouping];
}
这个答案的 Swift 4 代码
func scroll(toPoint: NSPoint, animationDuration: Double) {
NSAnimationContext.beginGrouping()
NSAnimationContext.current.duration = animationDuration
let clipView = scrollView.contentView
clipView.animator().setBoundsOrigin(toPoint)
scrollView.reflectScrolledClipView(scrollView.contentView)
NSAnimationContext.endGrouping()
}
建议的答案有一个明显的缺点:如果用户在正在进行的动画期间尝试滚动,则输入将导致抖动,因为动画将强制继续直到完成。如果你设置一个非常长的动画持续时间,问题就会变得很明显。这是我的用例,动画滚动视图以捕捉到部分标题(同时尝试向上滚动):
我提出以下子类:
public class AnimatingScrollView: NSScrollView {
// This will override and cancel any running scroll animations
override public func scroll(_ clipView: NSClipView, to point: NSPoint) {
CATransaction.begin()
CATransaction.setDisableActions(true)
contentView.setBoundsOrigin(point)
CATransaction.commit()
super.scroll(clipView, to: point)
}
public func scroll(toPoint: NSPoint, animationDuration: Double) {
NSAnimationContext.beginGrouping()
NSAnimationContext.current.duration = animationDuration
contentView.animator().setBoundsOrigin(toPoint)
reflectScrolledClipView(contentView)
NSAnimationContext.endGrouping()
}
}
通过覆盖法线(在用户滚动时调用)并在with中scroll(_ clipView: NSClipView, to point: NSPoint)
手动执行 a 滚动,我们取消了当前动画。但是,我们不调用,而是调用,它将执行其他必要的内部过程,然后执行。CATransaction
setDisableActions
reflectScrolledClipView
super.scroll(clipView, to: point)
reflectScrolledClipView
以上类产生更好的结果:
extension NSScrollView {
func scroll(to point: NSPoint, animationDuration: Double) {
NSAnimationContext.beginGrouping()
NSAnimationContext.current.duration = animationDuration
contentView.animator().setBoundsOrigin(point)
reflectScrolledClipView(contentView)
NSAnimationContext.endGrouping()
}
}
我知道,这有点跑题了,但我想有一个类似的方法来滚动到一个带有动画的矩形,就像UIView's
scrollRectToVisible(_ rect: CGRect, animated: Bool)
我的NSView
. 我很高兴找到这篇文章,但显然接受的答案并不总是正常工作。事实证明,bounds.origin
clipview 有问题。如果视图正在调整大小(例如,通过调整周围窗口的大小)bounds.origin
,则会以某种方式在 y 方向上相对于可见矩形的真实原点移动。我不知道为什么和多少。好吧,Apple 文档中也有此声明不要直接操作剪辑视图,因为它的主要目的是在内部充当视图的滚动机。
但我确实知道可见区域的真正起源。它是剪辑视图的一部分documentVisibleRect
。因此,我将该原点用于计算 visibleRect 的滚动原点,并将剪辑视图的原点移动bounds.origin
相同的量,并且瞧:即使视图正在调整大小,它也可以工作。
这是我的 NSView 新方法的实现:
func scroll(toRect rect: CGRect, animationDuration duration: Double) {
if let scrollView = enclosingScrollView { // we do have a scroll view
let clipView = scrollView.contentView // and thats its clip view
var newOrigin = clipView.documentVisibleRect.origin // make a copy of the current origin
if newOrigin.x > rect.origin.x { // we are too far to the right
newOrigin.x = rect.origin.x // correct that
}
if rect.origin.x > newOrigin.x + clipView.documentVisibleRect.width - rect.width { // we are too far to the left
newOrigin.x = rect.origin.x - clipView.documentVisibleRect.width + rect.width // correct that
}
if newOrigin.y > rect.origin.y { // we are too low
newOrigin.y = rect.origin.y // correct that
}
if rect.origin.y > newOrigin.y + clipView.documentVisibleRect.height - rect.height { // we are too high
newOrigin.y = rect.origin.y - clipView.documentVisibleRect.height + rect.height // correct that
}
newOrigin.x += clipView.bounds.origin.x - clipView.documentVisibleRect.origin.x // match the new origin to bounds.origin
newOrigin.y += clipView.bounds.origin.y - clipView.documentVisibleRect.origin.y
NSAnimationContext.beginGrouping() // create the animation
NSAnimationContext.current.duration = duration // set its duration
clipView.animator().setBoundsOrigin(newOrigin) // set the new origin with animation
scrollView.reflectScrolledClipView(clipView) // and inform the scroll view about that
NSAnimationContext.endGrouping() // finaly do the animation
}
}
请注意,我使用翻转坐标NSView
来使其与 iOS 行为相匹配。BTW:iOS 版本的动画持续时间scrollRectToVisible
为 0.3 秒。