• 果冻视图制作教程


    本文翻译自:VBFJellyView tutorial

    距离我上一篇文章已经过去很久了!

    这个月非常疯狂。我去了马德里,在ironhack上面教了3天iOS知识(UIViews,CoreGraphics,Layers)。过程非常棒,所有人都很好,我非常享受。

    同样,我也加入了minube团队来帮助他们开发一个新版本的app。这是一个非常令人兴奋的项目,非常荣幸跟这群天才一起工作!

    今天,我将在这里聊一个非常有趣的东西。我叫它“果冻视图”。

    在阅读了这篇关于重新设计Skype版的iOS应用,我对他们的活动视图非常感兴趣(上面的gif图),于是我开始用UIKit Dynamics 动态框架来实现这种效果。

    特别感谢这篇文章Recreating Skype's Action Sheet Animation,他拯救了我。我必须承认,我一开始不知道CADisplay,我之前是使用NSTimer(你可以掐死我)来绘制这个图层的。不管怎么样,让我们开始吧!

    怎么做呢?

    这里的技巧很简单:有9个小的视图使用UIAttachmentBehaviour一个个连接起来。下面的图片展示了全部子视图(绿色部分)和连接件(黄色部分)。

    全部这些用代码实现如下,也非常简单:

    - (void) setup {
        _nDivisions = 3;
    
        int item = 0;
        for (int i = 0; i < self.nDivisions; i++) {
            for (int k = 0; k < self.nDivisions; k++) {
                CGFloat hSeparation = self.viewSize.width/(self.nDivisions - 1);
                CGFloat vSeparation = self.viewSize.height/(self.nDivisions - 1);
    
                CGFloat hAmountToCenter = self.bounds.size.width/2 - self.viewSize.width/2;
                CGFloat vAmountToCenter = self.bounds.size.height/2 - self.viewSize.height/2;
    
                UIView *view = [[UIView alloc]initWithFrame:CGRectMake(self.bounds.origin.x + hAmountToCenter + hSeparation*k,
                                                                   self.bounds.origin.y + vAmountToCenter + vSeparation*i,
                                                                   10,
                                                                   10)];
                view.tag = item;
                view.backgroundColor = [UIColor clearColor];
                [self addSubview:view];
                item += 1;
            }
        }
    
        [self attachViews];
    }
    
    - (void) attachViews {
        self.mainAnimator = [[UIDynamicAnimator alloc]initWithReferenceView:self];
        CGFloat separation = self.viewSize.width/(self.nDivisions - 1);
        for (int i = 0; i < [self.subviews count]; i++) {
            UIView *view = self.subviews[i];
            for (UIView *nextView in self.subviews) {
                if ((view.center.x - nextView.center.x == separation)||(view.center.y - nextView.center.y == separation)) {
                    UIAttachmentBehavior *attach = [[UIAttachmentBehavior alloc]initWithItem:view
                                                                          attachedToItem:nextView];
                    attach.damping = self.damping;
                    attach.frequency = self.frequency;
                    [self.mainAnimator addBehavior:attach];
    
                    UIDynamicItemBehavior *bh = [[UIDynamicItemBehavior alloc]initWithItems:@[view]];
                    bh.elasticity = self.elasticity;
                    bh.density = self.density;
                    [self.mainAnimator addBehavior:bh];
                }
            }
        }
    }
    

    这样就可以了。如果你添加其它的行为(例如重力,碰撞等)也仍然可以工作。下面是一个添加了一些重力和碰撞行为的例子。

    skeletonView

    在顶部添加一个CAShapeLayer

    为了绘制这个“果冻视图”,我使用一个CAShapeLayer类。原因是如果你要添加一些子图层时会很方便。

    这个Shape 图层的路径非常简单,就是3 X 3 的正方形。我们只需要使用中间点作为控制点,添加4个曲线(每条边一个)。

    代码如下:

    - (void) setupMainLayer {
        self.viewLayer = [CAShapeLayer layer];
        self.viewLayer.path = [self getViewPath].CGPath;
        self.viewLayer.fillColor = self.fillColor.CGColor;
        self.viewLayer.cornerRadius = 10;
        [self.layer addSublayer:self.viewLayer];
    }
    
    - (UIBezierPath *) getViewPath {
        UIBezierPath *bPath = [UIBezierPath bezierPath];
        [bPath moveToPoint:((UIView *)self.subviews[0]).center] ;
        [bPath addQuadCurveToPoint:((UIView *)self.subviews[2]).center
                      controlPoint:((UIView *)self.subviews[1]).center];
        [bPath addQuadCurveToPoint:((UIView *)self.subviews[8]).center
                      controlPoint:((UIView *)self.subviews[5]).center];
        [bPath addQuadCurveToPoint:((UIView *)self.subviews[6]).center
                      controlPoint:((UIView *)self.subviews[7]).center];
        [bPath addQuadCurveToPoint:((UIView *)self.subviews[0]).center
                      controlPoint:((UIView *)self.subviews[3]).center];
        return bPath;
    }
    

    添加魔力

    这个魔力就是 CADisplayLink。

    一个CADisplayLink对象就是一个定时器对象,允许你的应用以屏幕刷新频率异步地刷新它的绘制。

    这么强大和简单。但这里的问题是我无法从9个正方形子视图中捕获所有的变化。只需提供一个绘制每个正方形的CGPath的函数,其余CADisplayLink会帮我们完成。

    - (void) show {
        [self setupMainLayer];
        self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(reDraw:)];
        [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    
    }
    
    - (void) reDraw:(CADisplayLink *)dLink {
        self.viewLayer.path = [self getViewPath].CGPath;
    }
    

    一些例子和包装

    我已经创建了一个项目,包含了4个例子来展示一些关于如何在你的项目中使用的idea。我也上传了一个短的youtube视频来演示。

    这些例子是:

    1. 普通的VBFJellyView + UIPanGestureRecognizer 。我只是移动边角,用新值来更新主的动画器,从而让它重新计算。中间点的位置是通过UIKit Dynamics更新的,感谢 snap behaviour。

    2. JellyButton。我子类化了VBFJellyView,添加一个UITapGestureRecognizer。一旦这个视图被触摸,一个 push behaviour会应用到控制点上(向外)。

    3. JellyAlert。我子类化了VBFJellyView,添加了一个重力和碰撞行为。对于碰撞行为,我添加了一个水平分界线。一旦这个视图被触摸,这个重力行为会移除,这个视图就会掉下来,进而离开屏幕。

    4.普通的VBFJellyView + UIPanGestureRecognizer + 重力 + 碰撞行为。

    Show me the code !!!

    最后,去Github查找完整的代码。

  • 相关阅读:
    写在最前面
    Bzoj 2281 [Sdoi2011]黑白棋 题解
    bzoj3125: CITY 题解
    CDQZ 集训大总结
    CDQZ集训DAY10 日记
    CDQZ集训DAY9 日记
    CDQZ集训DAY7 日记
    CDQZ集训DAY6 日记
    CDQZ集训DAY5 日记
    CDQZ集训DAY4 日记
  • 原文地址:https://www.cnblogs.com/YungMing/p/4347113.html
Copyright © 2020-2023  润新知