6

我想要得到的是与此滚动视图相同的行为:

App Store 功能滚动视图(左)

我知道这是使用 HTML 而不是本机 API,但我正在尝试将其实现为 UIKit 组件。

现在,对于我正在寻找的行为:

  • 请注意,这是一个分页滚动视图,但“页面大小”小于视图的宽度。
  • 当你从左到右滚动它时,每一页都会“捕捉”到最左边的项目。
  • 当您从右端向左滚动它时,它会“捕捉”到最右边的项目。

相同的页面,但现在从右到左:

App Store 功能滚动视图(右)

我试过的:

  • 我尝试使滚动视图小于它的超级视图并覆盖 hitTest,这让我得到了从左到右的行为。
  • 我已经尝试实现 scrollViewWillEndDragging:withVelocity:targetContentOffset: 并设置我想要的 targetContentOffset 但由于我无法更改速度,它只是滚动太慢或太快。
  • 我已经尝试实现 scrollViewDidEndDecelerating: 然后动画到正确的偏移量,但滚动视图首先停止然后移动,它看起来不自然。
  • 我尝试实现 scrollViewDidEndDragging:willDecelerate: 然后动画到正确的偏移量,但滚动视图“跳跃”并且动画不正确。

我没主意了。

谢谢!

更新:

我最终使用了 Rob Mayoff 的方法,它看起来很干净。我对其进行了更改,使其在速度为 0 时起作用,例如当用户拖动、停止和释放手指时。

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView 
                     withVelocity:(CGPoint)velocity 
              targetContentOffset:(CGPoint *)targetContentOffset {
    CGFloat maxOffset = scrollView.contentSize.width - scrollView.bounds.size.width;
    CGFloat minOffset = 0;

    if (velocity.x == 0) {
        CGFloat targetX = MAX(minOffset,MIN(maxOffset, targetContentOffset->x));

        CGFloat diff = targetX - baseOffset;

        if (ABS(diff) > offsetStep/2) {
            if (diff > 0) {
                //going left
                baseOffset = MIN(maxOffset, baseOffset + offsetStep);
            } else {
                //going right
                baseOffset = MAX(minOffset, baseOffset - offsetStep);
            }
        }
    } else {
        if (velocity.x > 0) {
            baseOffset = MIN(maxOffset, baseOffset + offsetStep);
        } else {
            baseOffset = MAX(minOffset, baseOffset - offsetStep);
        }
    }

    targetContentOffset->x = baseOffset;
}

此解决方案的唯一问题是滑动滚动视图不会产生“反弹”效果。感觉“卡住”了。

4

4 回答 4

15

SettingscrollView.decelerationRate = UIScrollViewDecelerationRateFast与 implementation 相结合scrollViewWillEndDragging:withVelocity:targetContentOffset:,似乎对我使用集合视图有用。

首先,我给自己一些实例变量:

@implementation ViewController {
    NSString *cellClassName;
    CGFloat baseOffset;
    CGFloat offsetStep;
}

viewDidLoad中,我设置了视图decelerationRate

- (void)viewDidLoad {
    [super viewDidLoad];
    cellClassName = NSStringFromClass([MyCell class]);
    [self.collectionView registerNib:[UINib nibWithNibName:cellClassName bundle:nil] forCellWithReuseIdentifier:cellClassName];
    self.collectionView.decelerationRate = UIScrollViewDecelerationRateFast;
}

我需要offsetStep是适合视图屏幕边界的整数个项目的大小。我计算它viewDidLayoutSubviews

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
    CGFloat stepUnit = layout.itemSize.width + layout.minimumLineSpacing;
    offsetStep = stepUnit * floorf(self.collectionView.bounds.size.width / stepUnit);
}

在滚动开始之前,我需要baseOffset成为视图的 X 偏移量。我初始化它viewDidAppear:

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    baseOffset = self.collectionView.contentOffset.x;
}

然后我需要强制视图以offsetStep. 我在scrollViewWillEndDragging:withVelocity:targetContentOffset:. 根据velocity,我增加或baseOffset减少offsetStep。但我钳制baseOffset到最小值 0 和最大值contentSize.width - bounds.size.width.

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
    if (velocity.x < 0) {
        baseOffset = MAX(0, baseOffset - offsetStep);
    } else if (velocity.x > 0) {
        baseOffset = MIN(scrollView.contentSize.width - scrollView.bounds.size.width, baseOffset + offsetStep);
    }
    targetContentOffset->x = baseOffset;
}

请注意,我不在乎是什么targetContentOffset->x

这具有与最左侧可见项的左边缘对齐的效果,直到用户一直滚动到最后一项。此时它与最右侧可见项的右边缘对齐,直到用户一直滚动到左侧。这似乎与 App Store 应用程序的行为相匹配。

如果这对您不起作用,您可以尝试将最后一行 ( targetContentOffset->x = baseOffset) 替换为:

    dispatch_async(dispatch_get_main_queue(), ^{
        [scrollView setContentOffset:CGPointMake(baseOffset, 0) animated:YES];
    });

这也适用于我。

你可以在这个 git 存储库中找到我的测试应用程序。

于 2013-04-12T01:57:24.140 回答
12

您可以打开pagingEnabled,或与anddecelerationRate = UIScrollViewDecelerationRateFast结合使用(这将最大限度地减少滚动到一个位置然后再次动画到另一个位置的效果)。这可能不是您想要的,但它非常接近。并且通过使用它可以避免最后的瞬间跳跃。scrollViewDidEndDraggingscrollViewDidEndDeceleratinganimateWithDuration

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
    if (!decelerate)
        [self snapScrollView:scrollView];
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    [self snapScrollView:scrollView];
}

然后,您可以编写一个snapScrollView,例如:

- (void)snapScrollView:(UIScrollView *)scrollView
{
    CGPoint offset = scrollView.contentOffset;

    if ((offset.x + scrollView.frame.size.width) >= scrollView.contentSize.width)
    {
        // no snap needed ... we're at the end of the scrollview
        return;
    }

    // calculate where you want it to snap to

    offset.x = floorf(offset.x / kIconOffset + 0.5) * kIconOffset;

    // now snap it to there

    [UIView animateWithDuration:0.1
                     animations:^{
                         scrollView.contentOffset = offset;
                     }];
}
于 2013-04-12T00:57:20.733 回答
6

简单的解决方案,像 App Store 一样工作,无论有没有速度。kCellBaseWidth— 单元格的宽度。

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
                     withVelocity:(CGPoint)velocity
              targetContentOffset:(inout CGPoint *)targetContentOffset
{
    NSInteger index = lrint(targetContentOffset->x/kCellBaseWidth);
    targetContentOffset->x = index * kCellBaseWidth;
}
于 2014-04-23T11:56:37.547 回答
2

为斯威夫特代言。@avdyushin的答案是迄今为止最简单的,正如 Ben 提到的,效果很好。尽管我确实从@Rob的回答中添加了一段关于滚动视图结束的内容。总之,这个解决方案似乎工作得很好。

func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {

    if ((scrollView.contentOffset.x + scrollView.frame.size.width) >= scrollView.contentSize.width) {
        // no snap needed ... we're at the end of the scrollview
        return
    }

    let index: CGFloat = CGFloat(lrintf(Float(targetContentOffset.memory.x) / kCellBaseWidth))
    targetContentOffset.memory.x = index * kCellBaseWidth

}

只需将您的添加minimumLineSpacingkCellBaseWidth,然后瞧。

于 2016-07-14T18:15:27.130 回答