12

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

第一张幻灯片第二张幻灯片第三张幻灯片

移动的视图

看法

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

我也阅读了核心动画指南,但没有找到解决方案。

可以在Git Hub 存储库上访问源代码

请帮忙 !!!

4

5 回答 5

24

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];
}
于 2013-10-17T10:48:03.690 回答
6

这个答案的 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()
}
于 2018-04-05T12:21:08.420 回答
6

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

在此处输入图像描述

我提出以下子类:

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 滚动,我们取消了当前动画。但是,我们不调用,而是调用,它将执行其他必要的内部过程,然后执行。CATransactionsetDisableActionsreflectScrolledClipViewsuper.scroll(clipView, to: point)reflectScrolledClipView

以上类产生更好的结果:

在此处输入图像描述

于 2019-10-09T15:38:23.927 回答
2

这是安德鲁答案的 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()
    }
}
于 2019-03-20T14:39:06.467 回答
0

我知道,这有点跑题了,但我想有一个类似的方法来滚动到一个带有动画的矩形,就像UIView's scrollRectToVisible(_ rect: CGRect, animated: Bool)我的NSView. 我很高兴找到这篇文章,但显然接受的答案并不总是正常工作。事实证明,bounds.originclipview 有问题。如果视图正在调整大小(例如,通过调整周围窗口的大小)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 秒。

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

于 2019-08-12T15:53:21.253 回答