美文网首页
自定义UIScrollView

自定义UIScrollView

作者: 取水 | 来源:发表于2016-05-29 01:55 被阅读849次

    0

    学习路径:

    1

    bounds

    UISCrollView继承于UIView,有bouncing、rubber-banding(橡皮筋)特效。在UIView上,新增了contentOffset属性,可以显示的内容大大增加。想一个容易一样,可以方进入很多东西。

    是如何用UIView实现的呢?如果你还没有看上面链接的文章,强烈推荐你看完后,再回来。我们知道View有两个标定自身大小的属性frameboundsframe指定自身位于父视图的位置和大小。而bounds则是指显示相对于自身的位置和大小。而UIScrollView则是利用bounds属性来动态的显示其contentOffsize的内容。而contentOffset更改的则是其自身的bounds值。

    understanding-UIScrollView这里说的很明白。

    rubber-banding
    橡皮筋效果,之前一直不明白什么是橡皮筋效果,谷歌翻译后,也还是有些懵。后来看具体代码实现,可以简单的表示为:y = x * 0.5。假如你运动了5米,而你得到的距离是2.5米,以此类推,你得到的永远小于你实际运动的距离。

    这里有一个公式(d 为屏幕的宽度或高度,c一般为0.55,别问为为什么):

    f(x, d, c) = (x * d * c) / (d + c * x)
    where,
    x – distance from the edge
    c – constant (UIScrollView uses 0.55)
    d – dimension, either width or height
    

    当然最开始的时候,不是这样的。最初的公式是:

    y = 0.5 * x

    简单吧,简单但是效果很好。这些都是twitter Grant Paul大神提出的。

    下面我们来看看效果:

    //OC 函数实现: 正规版本
    static CGFloat rubberBandDistance(CGFloat offset, CGFloat dimension) {
        
        const CGFloat constant = 0.55f;
        CGFloat result = (constant * fabs(offset) * dimension) / (dimension + constant * fabs(offset));
        // The algorithm expects a positive offset, so we have to negate the result if the offset was negative.
        return offset < 0.0f ? -result : result;
    }
    
    //最初版本:
    static CGFloat previousRubberBandDistance(CGFloat offset) {
        CGFloat result = fabs(offset) * 0.5;
        return offset < 0.0f ? -result : result;
    }
    

    如图:手指划过一个屏幕的距离后,两种函数处理后的结果对比:

    bouncing

    当视图达到边缘后,还会继续滚动一段距离,然后慢慢回到边界。bouncing就是说的就是这个效果。这里我们用Dynamic实现。

    • UIDynamicItemBehavior 实现惯性效果。它接收一个速度参数,可以设定阻力值。
    • UIAttachmentBehavior实现弹性效果,到达边界后,弹出一段距离后再回到边界位置。
    • 我们需要自定义一个遵守UIDynamicItem协议的类,用作模型层,代替具体的UIView。

    弹出一段距离在回到终点的效果是同时使用UIAttachmentBehaviorUIDynamicItemBehavior。其中:UIAttachmentBehavior设定Anchor为终点,UIDynamicItemBehavior设定一个相反的很大速度。然后在两者的作用下,就会出现回弹效果。见下面的红色块。

    具体实现:

    //红色块效果
        LCDynamicItem *dynamicItem =  [[LCDynamicItem alloc] init];
        dynamicItem.center = redView.frame.origin;
                
        UIDynamicItemBehavior *decelerationBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[dynamicItem]] ;
        [decelerationBehavior addLinearVelocity:CGPointMake(0, -1000) forItem:dynamicItem];
        decelerationBehavior.resistance = 2.0;
                
        __block BOOL onlyOnce = YES;
                
        decelerationBehavior.action = ^{
          CGRect frame = redView.frame;
          frame.origin = dynamicItem.center;
          if (frame.origin.y > 64) {
              redView.frame  = frame;
          }
          if (onlyOnce) {
              //add attachment
              UIAttachmentBehavior *springBehavior = [[UIAttachmentBehavior alloc] initWithItem:dynamicItem attachedToAnchor:CGPointMake(20, self.view.frame.size.height - 50)];
              springBehavior.frequency = 2.0;
              springBehavior.length = 0;
              springBehavior.damping = 1;
              [self.animator addBehavior:springBehavior];
                        
              onlyOnce = NO;
          }
        };
                
        [self.animator addBehavior:decelerationBehavior];
    
    

    应用

    用自定义UIScrollView + UITableView来实现效果的APP貌似不多。到目前为止,我就看到2款APP。一款是苹果自带的[系统天气],另一款就是饿了么(商品购买界面)。然后,我有找了其他外卖商品类APP。和饿了么类似的还有美团外卖、百度外卖,不过美团外卖和百度外卖没有实现效果,饿了么实现了一部分效果。实现效果:系统天气 > 饿了么 > 美团和百度外卖。

    嗯,效果就是下面这样的。向上滑动手指,tableView到达一定的位置后,才开始滚动;向下滑动手指,当tableView回到初始位置后,才开始滚动。饿了吗实现由个问题,就是在滑动到最底部的时候,再向上滑动会有非常黏的效果。

    自己写了一个简单的Demo,实现类似的效果。不过在实现过程中,简直痛苦,需要判断各种情况下的状态和位移。常常是修正了这个问题,另一个问题又出来了。

    实现思路:

    自定义UIScrollView,添加tableView,设定其scrollEnabled=NO

    分析tableView的滚动的情况:

    • 初始状态向下滚动,直接bouncing效果就可以了。
    • 初始窗台向上滚动,未滚动到顶部。此时,tableView不滚动,继续向上位移。
    • 初始状态向上滚动,到到顶部。此时位移停止,tableView滚动。
    • 接上,继续滚动,未到达tableView的底部,继续滚动。
    • 接上,滚动到底部了。tableView不再滚动,而是开始位移动作。并添加attachment,然后慢慢回到终点。

    难点在到达边界后,上下滚动的判断和处理。这里感觉异常复杂,需要消息计算状态和位移条件。有的时候还不一定满足。就像下图一样,到达边界后,有4种情况:

    • 之前滚动速度方向向上滚动,之后向上滚动
    • 之前滚动速度方向向上滚动,之后向下滚动
    • 之前滚动速度方向向下滚动,之后向上滚动
    • 之前滚动速度方向向下滚动,之后向下滚动
    草图

    具体来看看效果如何:

    Demo在这里

    相关文章

      网友评论

          本文标题:自定义UIScrollView

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