软滚动动画NSScrollView scrollToPoint:

时间:2013-10-16 09:06:15

标签: cocoa core-animation nsscrollview

我想在简单的UI中在转换之间创建软动画:

first slide second slide third slide

查看移动

view

当移动视图的调用 scrollToPoint:指向该过渡没有动画时。 我是Cocoa编程的新手(iOS是我的背景)。我不知道如何正确使用.animator或NSAnimationContext。

此外,我阅读了核心动画指南但未找到解决方案。

可以在Git Hub repository

上访问来源

请帮助!!!

5 个答案:

答案 0 :(得分:22)

scrollToPoint不可动画。只有动画属性,如NSAnimatablePropertyContainer中的边界和位置等动画属性。你不需要对CALayer做任何事情:删除wantsLayer和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];
}

答案 1 :(得分:4)

Swift 4代码this answer

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()
}

答案 2 :(得分:2)

建议的答案有一个很大的缺点:如果用户在进行中的动画期间尝试滚动,则输入将引起抖动,因为动画将强制继续进行直到完成。如果您设置了非常长的动画持续时间,该问题就很明显。这是我的用例,为滚动视图设置动画以捕捉到节标题(同时尝试向上滚动):

enter image description here

我提出了以下子类:

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()
    }

}

通过覆盖普通的scroll(_ clipView: NSClipView, to point: NSPoint)(在用户滚动时调用),并在CATransaction的{​​{1}}内手动执行滚动,我们取消了当前动画。但是,我们不调用setDisableActions,而是调用reflectScrolledClipView,它将执行其他必要的内部过程,然后执行super.scroll(clipView, to: point)

以上课程会产生更好的结果:

enter image description here

答案 3 :(得分:1)

这是Andrew's answer的Swift 4扩展版本

extension NSScrollView {
    func scroll(to point: NSPoint, animationDuration: Double) {
        NSAnimationContext.beginGrouping()
        NSAnimationContext.current.duration = animationDuration
        contentView.animator().setBoundsOrigin(point)
        reflectScrolledClipView(contentView)
        NSAnimationContext.endGrouping()
    }
}

答案 4 :(得分:1)

我知道,这有点不切实际,但是我想有一种类似的方法来滚动到UIView's的{​​{1}} scrollRectToVisible(_ rect: CGRect, animated: Bool)中带有动画的矩形。我很高兴找到这篇文章,但显然接受的答案并不总是能正常工作。事实证明,剪辑视图的NSView存在问题。如果要调整视图的大小(例如,通过调整周围窗口的大小),bounds.origin将以某种方式相对于可见矩形的真实原点在y方向偏移。我不知道为什么以及要花多少钱。嗯,Apple文档中也有这样的说法:不要直接操作clipview,因为它的主要目的是在内部充当视图的滚动机。

但是我确实知道可见区域的真正起源。它是剪辑视图的bounds.origin的一部分。因此,我使用该原点来计算visibleRect的滚动原点,然后将clipview的documentVisibleRect偏移相同的数量,并且voilà:即使视图的大小被调整了,该方法也有效。

这是我对NSView的新方法的实现:

bounds.origin

请注意,我在 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 } } 中使用了翻转坐标,使其与iOS行为匹配。 顺便说一句:iOS版本NSView中的动画持续时间为0.3秒。

  

https://www.fpposchmann.de/animate-nsviews-scrolltovisible/