Session217-Exploring Scroll View

作者: 爱德华炼金术师 | 来源:发表于2016-09-17 11:05 被阅读137次

Session传送门:https://developer.apple.com/videos/play/wwdc2013/217/
Demo传送门:https://github.com/q61946532/WWDC13_217
注:这是一篇session笔记和Demo练习,不会有详细的介绍。

前沿

在这个Session中主要介绍了两个功能的实现,这两个功能分别为

  1. 内嵌的scrollview与外部的scrollview的交互;
  2. scrollview和 UIKit Dynamics的结合使用;

这里的scrollview不单单是指UIScrollView这个类,也可以是其子类。话不多说,直接上Demo.

Demo

这个Demo效果如下所示,我们需要让内部的scrollview的滚动带动外部scrollview的滚动,我们还需要在上下滚动条纹的时候出现弹簧效果。


WWDC13_Session217_1.gif WWDC13_Session217.gif

界面搭建

这个界面的UI需要三个UIScrollView或其子类,最外部是个UIScrollView,需要设置其pagingEnable为true,我们可不想界面停止在不同页面的中间。其左半部分为UICollectionView,每一个cell里又嵌入一个UIScrollView,而右半部分为一个UIView,其背景颜色由滚动的cell的颜色决定。

连带滚动效果

这是内部的UIScrollView的交互事件去影响外部的视图属性,可以采用代理的方式,通知当交互事件发生通知控制去做相应的改变:

//ZJWCollectionViewCell.h
....
@protocol ZJWCollectionViewCellDelegate <NSObject>

- (void) scrollingCellDidBeginPulling:(ZJWCollectionViewCell *)cell;
- (void) scrollingCell:(ZJWCollectionViewCell *)cell didChangePullOffset:(CGFloat)offset;
- (void) scrollingCellDidEndPulling:(ZJWCollectionViewCell *)cell;
....

// ZJWMainViewController.m
....
- (void)scrollingCellDidBeginPulling:(ZJWCollectionViewCell *)cell
{
    _scrollView.scrollEnabled = NO;
    _colorView.backgroundColor = cell.color;
}

- (void)scrollingCell:(ZJWCollectionViewCell *)cell didChangePullOffset:(CGFloat)offset
{
    _scrollView.contentOffset = CGPointMake(offset, 0);
}

- (void)scrollingCellDidEndPulling:(ZJWCollectionViewCell *)cell
{
    _scrollView.scrollEnabled = YES;
}
@end
....

我们需要让内部的scrollview跟随手势,由于scrollview的绝对滚动距离包括了内部的滚动和外部的滚动,其绝对的滚动距离将比手势的移动大,为了让其相等,我们需要耍点小手段,设置内部scrollview的transform属性让其向右边移动外部的滚动距离,并且在滚动结束的时候取消形变。

-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    ...
     _scrollView.transform = CGAffineTransformMakeTranslation(pullOffset, 0);
}

- (void) scrollEnded
{
    ....
    _scrollView.transform = CGAffineTransformIdentity;
}

我们还需要在滚动回弹,达到内部scrollview和外部scrollview一起结束的效果,在目前的情况下由于设置了一个阀值,当内部的滚动距离大于阀值的时候外部的scrollview才开始滚动,所以外部的滚动距离小于内部滚动距离阀值的大小。为了达到回弹同时到达的效果,我们需要识别出这个回弹的情况,并且加快内部的滚动速度或者减慢外部的滚动速度。这里就要用到一个很少用到的UIScrollViewDelagete的方法:

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset

在这个方法中判断回弹的情况,并且设置外部滚动的比率: 外部滚动距离/内部滚动距离。这个比率用于减慢外部滚动速度。接下来就很简单了在设置外部滚动距离的地方,在回弹情况下使用上这个比率值。

// ZJWCollectionViewCell.m
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
    CGFloat offset = scrollView.contentOffset.x;
    
    if ( (*targetContentOffset).x == 0 && offset > 0) {
        _deceleratingBackToZero = YES;
        
        CGFloat pullOffset = MAX(0, offset - PULL_THREHOLD);
        _decelerationDistanceRatio = pullOffset / offset;
    }
}

-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    CGFloat offset = scrollView.contentOffset.x;
    ....
    if (_pulling) {
        CGFloat pullOffset;
        
        if (_deceleratingBackToZero) {
            pullOffset =  offset * _decelerationDistanceRatio;
        } else {
            pullOffset = MAX(0, offset - PULL_THREHOLD);
        }
        ....
    }
}

- (void) scrollEnded
{
    ....
    _deceleratingBackToZero = NO;
}

弹簧效果

需要实现弹簧效果,就要利用到自定义UICollectionView的布局和UIKit Dynamics的AttachmentBehavior效果,为了让UICollectionView方便利用UIKit Dynamics的布局,苹果在iOS 7中专门写了一个分类UIDynamicAnimator.h + UICollectionViewAdditions。新建一个UICollectionViewFlowLayout的字类,这里叫做ZJWSpringFlowLayout。我们需要重写以下方法:

-(void)prepareLayout;
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect;
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;

我们将布局完全交给了 UIDynamicAnimator。在-(void)prepareLayout里初始化UIDynamicAnimator。在 - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds 方法中重新设置锚点的位置。这个需要提一点的是scrollview滚动的时候期实质就是改变了其bounds,有这么一个关系:contentsOffset = bounds.origin。

-(void)prepareLayout
{
    [super prepareLayout];
    
    if (!_dynamicAnimator) {
        _dynamicAnimator = [[UIDynamicAnimator alloc] initWithCollectionViewLayout:self];
        
        CGSize contentSize = [self collectionViewContentSize];
        NSArray *items = [super layoutAttributesForElementsInRect:CGRectMake(0, 0, contentSize.width, contentSize.height)];
        
        for (UICollectionViewLayoutAttributes *item in items) {
            UIAttachmentBehavior *spring = [[UIAttachmentBehavior alloc] initWithItem:item attachedToAnchor:[item center]];
            spring.length = 0;
            spring.damping = 0.5;
            spring.frequency = 0.8;
            [_dynamicAnimator addBehavior:spring];
        }
    }
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    return [_dynamicAnimator itemsInRect:rect];
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    return [_dynamicAnimator layoutAttributesForCellAtIndexPath:indexPath];
}

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
    UIScrollView *scrollview = self.collectionView;
    CGFloat scrollDelta = newBounds.origin.y - scrollview.bounds.origin.y;
    
    for (UIAttachmentBehavior *spring in _dynamicAnimator.behaviors) {
        UICollectionViewLayoutAttributes *item = [spring.items firstObject];
        CGPoint center = item.center;
        center.y += scrollDelta;
        item.center = center;
        
        [_dynamicAnimator updateItemUsingCurrentState:item];
    }
    return NO;
}

以上的设置还不足以达到我们想要的效果,这个问题出在每个UIAttachmentBehavior的锚点设置的变化是相同的,他们的动态效果也相同,相对距离也是一样的。我们看不出区别,我们需要做点变化,根据交互的触摸点来决定各个UIAttachmentBehavior的锚点,距离接触点越远锚点的距离越远,拉伸效果越明显。

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
    ...
    CGPoint touchLocation = [scrollview.panGestureRecognizer locationInView:scrollview];

    for (UIAttachmentBehavior *spring in _dynamicAnimator.behaviors) {
        CGPoint anchorPoint = spring.anchorPoint;
        CGFloat distanceFromTouch = fabs(touchLocation.y  - anchorPoint.y);
        CGFloat scrollResistance = distanceFromTouch / 500;
        ...
        center.y += scrollDelta * scrollResistance;
        ....
    }
    return NO;
}

经过以上一系列操作,就基本完成了Demo,详细的代码可以去 GitHub 上查看。

相关文章

网友评论

    本文标题:Session217-Exploring Scroll View

    本文链接:https://www.haomeiwen.com/subject/vowiettx.html