【问题标题】:Increasing UIScrollView rubber banding resistance增加 UIScrollView 橡皮筋阻力
【发布时间】:2013-12-24 14:47:39
【问题描述】:

我想在我的应用程序中实现一个场景,我想让UIScrollView 在您开始强制滚动超出其正常内容边界时表现得更加“僵硬”。

我的意思是,当您位于滚动视图的顶部或底部时,如果您向下点击并继续拖动,您通常可以让滚动视图继续滚动超出其边界,但它会逐渐建立阻力,直到它通常停止在视图边界中间的一半左右。当您抬起手指时,它会弹回滚动区域的边界。

我想要实现的是,我想让“越界”拖动效果更加沉重,而不是用户拖动滚动视图并且它在中间“触底”穿过滚动视图边界时,它会完全停止在超过其滚动边界的 20% 左右。

我一直在尝试在 scrollViewDidScroll: 委托方法中覆盖滚动视图的 contentOffset,但这似乎不起作用,因为在那里重新设置 contentOffset 似乎会搞砸同样的委托调用方法。

我的下一个想法是监视与滚动视图关联的UIPanGestureRecognizer,并尝试根据由此产生的事件确定正确的UIScrollView contentOffset。话虽这么说,我认为这可能会开始变得很麻烦,所以我想我会在这里询问我在尝试可能会造成混乱的事情之前没有考虑过的任何其他解决方案。

谢谢!

【问题讨论】:

    标签: ios iphone objective-c ipad uiscrollview


    【解决方案1】:

    此代码将在您想要的边距处停止滚动

    - (void)scrollViewDidScroll:(UIScrollView *)scrollView
    {
        CGFloat margin = scrollView.frame.size.height/4.0;
        if (scrollView.contentOffset.y < -margin) {
            CGPoint offset = scrollView.contentOffset;
            offset.y = -margin;
            scrollView.contentOffset = offset;
        } else if (scrollView.contentOffset.y > (scrollView.contentSize.height-scrollView.frame.size.height) + margin) {
            CGPoint offset = scrollView.contentOffset;
            offset.y = (scrollView.contentSize.height-scrollView.frame.size.height) + margin;
            scrollView.contentOffset = offset;
        }
    }
    

    希望对你有帮助

    【讨论】:

    • 谢谢!嗯,是的,这确实有效,但这并不是我真正想要的效果。我认为我的问题需要更详细一点,但我的意思是我希望让滚动视图在拖动时放慢速度并更快地停止。此代码示例只是限制滚动视图 contentOffset,因此它会突然停止...
    • @TiM 我决定为您的问题制作一个 git hub 项目,我把它放在一个单独的答案中......请检查它stackoverflow.com/a/20440423/1722480
    【解决方案2】:

    这并不容易。

    如果这只是一个一次性的滚动视图,我可能会建议使用 UIKit Dynamics 和一些推送、附件和弹簧行为来获得你想要的效果。

    如果您希望为应用程序中的每个表格视图执行此操作,我认为观看平移手势识别器是一种足够合理的方法。就在我的头顶上,我会观察手势的状态,当它结束时,我会捕捉视图的垂直速度,使用UIScrollView 方法来计算它的停止位置,然后将它从当前位置动画化到静止位置带有弹簧动画。您必须自己使用平移的结束速度和剩余距离 + 过冲量来计算持续时间。

    【讨论】:

    • 哇哦!我实际上没有考虑过 UIKit Dynamics。可悲的是,在这种情况下,我仍然需要以 iOS 6 为目标,所以这次我不能使用它们,但这对于 iOS 7 选项来说绝对是一个不错的选择。我尝试再次直接修改 contentOffset ,但它仍然无法正常工作,因此看起来直接访问平移手势识别器将是可行的方法。感谢您的帮助!
    【解决方案3】:

    我做了一个 GitHub 项目来处理这个案例

    Bounce Scroll View

    你可以通过它来控制滚动的阻力

    【讨论】:

    • 哇哦!您花时间离开并实际实施了一个演示项目,这给我留下了深刻的印象!啊,可悲的是,这仍然不是我所追求的。我玩过这个项目,但动作不正确,如果你一直在边界上下拖动,它也会抖动很多。我感觉直接修改 contentOffset 不起作用,因为下次调用“didScroll”委托时,会为您提供已修改的值。棘手的东西!
    • 其实我有点无聊,决定做一些新的事情......我很遗憾听到这个让我检查一下,但请确保你关闭限制
    • 不幸的是,至少在 iOS 7 中,这个 sn-p 非常有问题,在大多数情况下都不起作用。
    【解决方案4】:

    我正在使用 Swift 编写以下代码。这是用于水平滚动的,可以很容易地适应垂直滚动。根据是否启用分页,解决方案会有所不同。两者都在下面给出。

    class ScrollViewDelegate : NSObject, UIScrollViewDelegate
    {
        let maxOffset: CGFloat  // offset of the rightmost content (scrollview contentSize.width - frame.width)
        var prevOffset: CGFloat = 0  // previous offset (after adjusting the value)
    
        var totalDistance: CGFloat = 0  // total distance it would have moved (had we not restricted)
        let reductionFactor: CGFloat = 0.2  // percent of total distance it will be allowed to move (under restriction)
        let scaleFactor: CGFloat = UIScreen.mainScreen().scale  // pixels per point, for smooth translation in respective devices
    
        init(maxOffset: CGFloat)
        {
            self.maxOffset = maxOffset  // scrollView.contentSize.width - scrollView.frame.size.width
        }
    
        func scrollViewDidScroll(scrollView: UIScrollView)
        {
            let flipped = scrollView.contentOffset.x >= maxOffset  // dealing with left edge or right edge rubber band
            let currentOffset = flipped ? maxOffset - scrollView.contentOffset.x : scrollView.contentOffset.x  // for right edge, flip the values as if screen is folded in half towards the left
    
            if(currentOffset <= 0)  // if dragging/moving beyond the edge
            {
                if(currentOffset <= prevOffset)  // if dragging/moving beyond previous offset
                {
                    totalDistance += currentOffset - prevOffset  // add the "proposed delta" move to total distance
                    prevOffset = round(scaleFactor * totalDistance * reductionFactor) / scaleFactor  // set the prevOffset to fraction of total distance
                    scrollView.contentOffset.x = flipped ? maxOffset - prevOffset : prevOffset  // set the target offset, after negating any flipping
                }
                else  // if dragging/moving is reversed, though still beyond the edge
                {
                    totalDistance = currentOffset / reductionFactor  // set totalDistance from offset (reverse of prevOffset calculation above)
                    prevOffset = currentOffset  // set prevOffset
                }
            }
            else  // if dragging/moving inside the edge
            {
                totalDistance = 0  // reset the values
                prevOffset = 0
            }
        }
    }
    

    启用分页后,返回到静止点的弹回似乎无法按预期工作。橡皮筋不是停在页面边界,而是越过它并停在非页面偏移处。如果从边缘拉动是快速轻弹,即使在您抬起手指之后,它也会继续朝那个方向移动,然后再反转方向并回到静止点,就会发生这种情况。如果您只是暂停并离开,甚至将其轻弹回静止点,它似乎工作正常。为了解决这个问题,在下面的代码中,我尝试识别超调的可能性,并在它返回并尝试越过预期的页面边界时强制停止它。

    class PageScrollViewDelegate : NSObject, UIScrollViewDelegate
    {
        let maxOffset: CGFloat  // offset of the rightmost content (scrollview contentSize.width - frame.width)
        var prevOffset: CGFloat = 0  // previous offset (after adjusting the value)
    
        var totalDistance: CGFloat = 0  // total distance it would have moved (had we not restricted)
        let reductionFactor: CGFloat = 0.2  // percent of total distance it will be allowed to move (under restriction)
        let scaleFactor: CGFloat = UIScreen.mainScreen().scale  // pixels per point, for smooth translation in respective devices
    
        var draggingOver: Bool = false  // finger dragging is over or not
        var overshoot: Bool = false  // is there a chance for page to overshoot page boundary while falling back
    
        init(maxOffset: CGFloat)
        {
            self.maxOffset = maxOffset  // scrollView.contentSize.width - scrollView.frame.size.width
        }
    
        func scrollViewWillBeginDragging(scrollView: UIScrollView)
        {
            draggingOver = false  // reset the flags
            overshoot = false
        }
    
        func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool)
        {
            draggingOver = true  // finger dragging is over
        }
    
        func scrollViewDidScroll(scrollView: UIScrollView)
        {
            let flipped = scrollView.contentOffset.x >= 0.5 * maxOffset // dealing with left edge or right edge rubber band
            let currentOffset = flipped ? maxOffset - scrollView.contentOffset.x : scrollView.contentOffset.x  // for right edge, flip the values as if screen is folded in half towards the left
    
            if(currentOffset <= 0)  // if dragging/moving beyond the edge
            {
                if(currentOffset <= prevOffset)  // if dragging/moving beyond previous offset
                {
                    overshoot = draggingOver  // is content moving farther away even after dragging is over (caused by fast flick, which can cause overshooting page boundary while falling back)
    
                    totalDistance += currentOffset - prevOffset  // add the "proposed delta" move to total distance
                    prevOffset = round(scaleFactor * totalDistance * reductionFactor) / scaleFactor  // set the prevOffset to fraction of total distance
                    scrollView.contentOffset.x = flipped ? maxOffset - prevOffset : prevOffset  // set the target offset, after negating any flipping
                }
                else  // if dragging/moving is reversed, though still beyond the edge
                {
                    totalDistance = currentOffset / reductionFactor  // set totalDistance from offset (reverse of prevOffset calculation above)
                    prevOffset = currentOffset  // set prevOffset
                }
            }
            else  // if dragging/moving inside the edge
            {
                if(overshoot)  // if this movement is a result of overshooting
                {
                    scrollView.setContentOffset(CGPointMake(flipped ? maxOffset : 0, scrollView.contentOffset.y), animated: false)  // bring it to resting point and stop further scrolling (this is a patch to control overshooting)
                }
    
                totalDistance = 0  // reset the values
                prevOffset = 0
            }
        }
    }
    

    【讨论】:

    • 哇!惊人的!当我有机会时,我将不得不尝试一下。这看起来是我正在寻找的正确答案!
    • @TiM,谢谢。我已经修改了代码来处理两个边缘。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-09-30
    • 2019-09-05
    • 2014-06-15
    • 2011-01-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多