UIScrollView。实现“无限”滚动/缩放的想法?

20

因此,UITableView支持基本上“无限”滚动。可能有限制,但是该家伙可以滚动很长时间。我想用UIScrollView模仿这种行为,但有两个根本性障碍:

1)scrollView.contentSize在创建时固定。 2)缩放可能会炸毁任何懒惰加载方案,因为它可能导致无限数据爆炸。

其他人是否考虑过这个想法?是的,我知道,我们实际上在谈论重新创建Google Maps。非常感谢您提供任何见解。

干杯, 道格


UITableView是UIScrollView的子类。 - Jonathan.
5个回答

13

我刚刚完成了无限滚动的实现。 我的实现中,UITableViewCell 包含一个 scrollView 和导航按钮。scrollView 包含 x 个视图,每个视图的宽度都相同。这些视图水平对齐,并启用了分页。

scrollView.clipsToBounds = YES;
scrollView.scrollEnabled = YES;
scrollView.pagingEnabled = YES;
scrollView.showsHorizontalScrollIndicator = NO;

我的代码逻辑如下:

  1. 在初始化函数中,我
    • 创建所有视图(用于滚动视图)并
    • 将它们放入一个数组中,并
    • 将它们添加到滚动视图中
  2. 然后我调用一个函数,在循环中计算每个视图的位置(每次检测到滚动时,也需要调用此函数)。 它总是取数组的第一个元素,并将其框架设置为(0,0,...,...),第二个元素为(i*宽度,0,...,...)等等。 被调用的函数如下:

    - (void)updateOffsetsOfViews{
        int xpos = 0;
        for (int i=0; i<[views count]; i++) {
            UIImageView *_view = [views objectAtIndex:i];
            CGRect aFrame = _view.frame;
            aFrame.origin.x = xpos;
            aFrame.origin.y = 0.0;
            _view.frame = aFrame;
            xpos += viewWidth;
        }
        float center = 0;
        if(fmod([views count],2) == 1){
            center = viewWidth * ([views count]-1)/2;
        }else {
            center = viewWidth * [views count]/2;
        }
        [scrollView setContentOffset:CGPointMake(center, 0)];
        lastOffset = center;
    }
    
  3. 然后(仍在初始化过程中),我添加了一个观察者

  4. [scrollView addObserver:self forKeyPath:@"contentOffset" options:0 context:nil];
    

    每次scrollView中的内容发生更改时,都会调用(observeValueForKeyPath)函数,它看起来像这样:

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object  change:(NSDictionary *)change context:(void *)context
    {
        UIImageView *_viewFirst = (UIImageView *)[views objectAtIndex:0];
        if ( fmod([scrollView contentOffset].x,viewWidth)  == 0.0) {
            if ([scrollView contentOffset].x > lastOffset) {
                [views removeObjectAtIndex:0];
                [views addObject:_viewFirst];                
                [self updateOffsetsOfViews];    
            }else if ([scrollView contentOffset].x < lastOffset) {
                UIImageView *_viewLast = (UIImageView *)[views lastObject];
                [views removeLastObject];
                [views insertObject:_viewLast atIndex:0];            
                [self updateOffsetsOfViews];
            }
        }
    }
    
  5. 在dealloc或viewDidUnload方法中(取决于您的实现方式),不要忘记移除观察者。

  6. [scrollView removeObserver:self forKeyPath:@"contentOffset"];   
    
    希望这能帮到你,你可能会注意到一些额外的开销,但在我的实现中,我还支持像滚动5页(嗯...无限)和自动滚动等功能,所以你可能会看到一些可以丢弃的东西。

1
很好的解决方案,但不要使用 == 进行浮点数比较,会出现错误。 - user529758
我尝试了你的代码,但是当我快速滚动两页时,代码无法正确处理。 - jeswang

10

虽然无法真正实现无限UIScrollView,但是有一些简单的技巧可以用来模拟该行为。

  1. 处理固定的contentSize:让滚动视图处理一些固定大小的视图,并在启动或实例化时设置内容偏移量,以便您看到受处理视图的中心部分。然后只需监视内容偏移量(使用KVO或其他方法),如果接近任何边缘,则更新视图内容(适当偏移)并将滚动视图的contentOffset属性重置为回到中间。
  2. 处理缩放:类似地进行操作,但这次观察滚动视图上的缩放因子。每当它达到某个点时,请对您呈现的任何数据进行一些操作,以使其显示为缩放状态,然后将缩放因子重置为1.0。例如,如果您正在滚动图像并将其缩放为两倍大小,请以编程方式应用某种变换,使图像的大小增加一倍,然后将滚动视图的缩放因子重置为1.0。图像仍然会出现缩放状态,但滚动视图将能够继续根据需要进一步缩放。(Google Maps更进一步地采用延迟加载更详细的视图,随着用户缩放,您可以选择实现或不实现这一点。)

4
这个解决方案的问题在于setContentOffset会取消任何正在进行的滚动并使其突然停止。我不知道如何确定视图移动的速度,也不知道如果能确定速度的话如何重新启动滚动。 - Grumdrig
但是为什么要改变内容偏移量呢?让它滚动,只需确保跟上并移动图像视图以对应用户所看到的内容即可。 - Ben Zotto
2
似乎 setContentOffset:animated: 会取消当前的滚动,但是 setContentOffset: 不会... 在 UIScrollView 的子类中重写 setContentOffset: 并调整偏移量可以解决问题!(至少在 iOS SDK 4.3 上是这样) - Damien Debin

4

StreetScroller示例项目来自苹果公司,演示了如何在UIScrollView中实现无限滚动。


2
请记住,当滚动被动画化时,contentOffset 会多次变化,不仅是按页面分页,而且还会随着动画中的每一步而变化。

0

也许将contentSize设置为巨大值,然后移动有限数量的基础视图以跟踪视图位置(如平铺示例中所示)可以解决问题。

为了减轻最终到达边缘并不得不突然重新调整视图(取消当前正在运动的任何滚动)的可能性,可以在视图静止时定期重新调整视图中心。

无论如何,这就是我要尝试的。


这似乎很好用,供您参考。我使用了4个与滚动同步的大小相同的包含视图。我的代码是基于(上半部分的)滚动示例编写的。 - Grumdrig
官方的苹果无限滚动示例项目建议使用这种方法。 - zakdances

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接