美文网首页
app功能介绍的小动画

app功能介绍的小动画

作者: 杭州七木科技 | 来源:发表于2018-01-18 19:12 被阅读0次

    本文开始之前,我们看下界面效果:


    动画执行代理方法1.gif

    我们看下本文涉及到的知识点:


    app功能介绍的动画.png
    动画思路:
    动画思路.png

    1.CAShapeLayer的概念与应用

    CAShapeLayer继承自CALayer,因此,可使用CALayer的所有属性。但是,CAShapeLayer需要和贝塞尔曲线配合使用才有意义。
    

    CAShapeLayer与UIBezierPath的关系

    1.CAShapeLayer中shape代表形状的意思,所以需要形状才能生效。
    2.贝塞尔曲线可以创建基于矢量的路径,而UIBezierPath类是对CGPathRef的封装。
    3.贝塞尔曲线给CAShapeLayer提供路径,CAShapeLayer在提供的路径中进行渲染。路径会闭环,所以绘制出了Shape。
    4.用于CAShapeLayer的贝塞尔曲线作为path,其path是一个首尾相接的闭环的曲线,即使该贝塞尔曲线不是一个闭环的曲线。
    

    大概步骤:
    1.UI配置
    由于UI比较简单,就用故事面板拖拽了5个view,
    把5个view关联到一个数组里。

    @property(nonatomic,strong)IBOutletCollection(UIView) NSArray * viewsArray;
    

    2.遮罩图层的设置
    2.1 获得可见视图的frame,根据需要在原视图上进行扩大。
    坐标系的转化

     // 代码含义:拿到view.superview中的view.frame相对于self的位置
        CGRect visualRect = [self convertRect:view.frame toView:view.superview];
    

    拿到在遮罩图层的坐标系,并且扩大frame。

    - (CGRect)obtainVisualFrame
    {
        if (self.currentIndex>=_count) {
            return CGRectZero;
        }
        
        UIView * view = [self.dataSource guideMaskView:self viewForItemAtIndex:self.currentIndex]; //拿到视图上对应的view
    #warning 转换坐标系 重点
        // 代码含义:拿到view.superview中的view.frame相对于self的位置
        CGRect visualRect = [self convertRect:view.frame toView:view.superview];
        UIEdgeInsets edgeInsets = UIEdgeInsetsMake(-20, -20, -20, -20);
        if (self.delegate &&[self.delegate respondsToSelector:@selector(guideMaskView:insetsForItemAtIndex:)]) {
            [self.delegate guideMaskView:self insetsForItemAtIndex:self.currentIndex];
        }
        visualRect.origin.x += edgeInsets.left;
        visualRect.origin.y += edgeInsets.right;
        visualRect.size.width -=(edgeInsets.left+edgeInsets.right);
        visualRect.size.height -= (edgeInsets.bottom + edgeInsets.top);
        
        return visualRect; // x减小,y减小 宽高分别变大
        
    }
    

    遮罩图层的配置,主要在于UIBezierPath和CAShapeLayer的关联。

    -(void)showMask
    { 
        CGPathRef fromPath = self.maskLayer.path; //一个不可变的图形路径
        self.maskLayer.frame = self.bounds;
        self.maskLayer.fillColor = [UIColor blackColor].CGColor;
        CGFloat maskCornerRadius = 5;
        if (self.delegate && [self.delegate respondsToSelector:@selector(guideMaskView:cornerRadiusForItemAtIndex:)]) {
            maskCornerRadius = [self.delegate guideMaskView:self cornerRadiusForItemAtIndex:self.currentIndex]; // 获得圆角
        }
        
        UIBezierPath * visualPath = [UIBezierPath bezierPathWithRoundedRect:[self obtainVisualFrame] cornerRadius:maskCornerRadius];
        /// 获取终点路径
        UIBezierPath *toPath = [UIBezierPath bezierPathWithRect:self.bounds];
        
        [toPath appendPath:visualPath];// 添加路径
        
        /// 遮罩的路径
        // 设置CAShapeLayer与UIBezierPath关联
    
        self.maskLayer.path = toPath.CGPath; // 设置遮罩路径 重点代码
        self.maskLayer.fillRule = kCAFillRuleEvenOdd; // 空心矩形框
    #pragma mark - 设置遮罩部分
        self.layer.mask = self.maskLayer;// 设置遮罩给当前视图的view
        
        /// 开始移动动画
        CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"path"];
        anim.duration  = 0.3;
        anim.fromValue = (__bridge id _Nullable)(fromPath);
        anim.toValue   = (__bridge id _Nullable)(toPath.CGPath);
        [self.maskLayer addAnimation:anim forKey:NULL];
        
    }
    

    3.介绍view的配置
    我们这里需要根据子视图Viewd的frame,配置指示view的位置,也就是箭头和文字描述的位置

    #pragma mark - 配置items的frame
    -(void)configureItemsFrame
    {
       // 文字颜色
        if (self.dataSource && [self.dataSource respondsToSelector:@selector(guideView:colorForDescriptionAtIndex:)]) {
            self.textLabel.textColor = [self.dataSource guideView:self colorForDescriptionAtIndex:self.currentIndex];
        }
        // 文字的大小
        if (self.dataSource && [self.dataSource respondsToSelector:@selector(guideView:fontForDescriptionLabelAtIndex:)]) {
            self.textLabel.font = [self.dataSource guideView:self fontForDescriptionLabelAtIndex:self.currentIndex];
        }
        // 描述文字
        NSString * des = [self.dataSource guideView:self descriptionLabelForItemAtIndex:self.currentIndex];
        self.textLabel.text = des;
        
        CGFloat desInsetsX = 50;
        
        // 文字与左右边框的距离
        if (self.delegate && [self.delegate respondsToSelector:@selector(guideMaskView:insetsForItemAtIndex:)]) {
            desInsetsX = [self.delegate guideMaskView:self horizontalSpaceForDescriptionLabelAtIndex:self.currentIndex];
        }
        
        
        CGFloat space = 10;
        if(self.delegate && [self.delegate respondsToSelector:@selector(guideMaskView:spaceForSubViewsAtIndex:)])
        {
            space = [self.delegate guideMaskView:self spaceForSubViewsAtIndex:self.currentIndex];
        }
        // 设置文字与箭头的位置
        CGRect textRect,arrowRect;
        CGSize imgSize = self.arrowView.image.size;
        CGFloat maxWidth = self.bounds.size.width - desInsetsX*2 ; //最大宽度为屏幕尺寸 - 2个边框
        /*
         typedef NS_OPTIONS(NSInteger, NSStringDrawingOptions) {  
         
         NSStringDrawingUsesLineFragmentOrigin = 1 << 0,  
         // 整个文本将以每行组成的矩形为单位计算整个文本的尺寸  
         // The specified origin is the line fragment origin, not the base line origin  
         
         NSStringDrawingUsesFontLeading = 1 << 1,  
         // 使用字体的行间距来计算文本占用的范围,即每一行的底部到下一行的底部的距离计算  
         // Uses the font leading for calculating line heights  
         
         NSStringDrawingUsesDeviceMetrics = 1 << 3,  
         // 将文字以图像符号计算文本占用范围,而不是以字符计算。也即是以每一个字体所占用的空间来计算文本范围  
         // Uses image glyph bounds instead of typographic bounds  
         
         NSStringDrawingTruncatesLastVisibleLine  
         // 当文本不能适合的放进指定的边界之内,则自动在最后一行添加省略符号。如果NSStringDrawingUsesLineFragmentOrigin没有设置,则该选项不生效  
         // Truncates and adds the ellipsis character to the last visible line if the text doesn't fit into the bounds specified. Ignored if NSStringDrawingUsesLineFragmentOrigin is not also set.  
         
         }  
         */
        CGSize textSize = [des boundingRectWithSize:CGSizeMake(maxWidth, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName:self.textLabel.font} context:NULL].size;
        CGAffineTransform transform = CGAffineTransformIdentity;// 对设置进行还原
        // 获取items 的方位
        // 需要设置对应方向缩放
        cyGuideMaskItemRegion itemRegion = [self obtainVisualRegion];
        switch (itemRegion) {
            case cyGuideMaskItemRegionLeftTop:
            {
                // 左上
                transform = CGAffineTransformMakeScale(-1, 1);
                arrowRect =  CGRectMake(CGRectGetMidX([self obtainVisualFrame]) -imgSize.width*2, CGRectGetMaxY([self obtainVisualFrame]) + space, imgSize.width, imgSize.height);
                CGFloat x;
                if (textSize.width< CGRectGetWidth([self obtainVisualFrame])) {
                    x = CGRectGetMidX(arrowRect) - textSize.width*0.5;
                }else
                {
                    x = desInsetsX;
                }
                textRect = CGRectMake(x, CGRectGetMaxY(arrowRect) + space, textSize.width, textSize.height); // 左上的尺寸
                
            }
                break;
            case cyGuideMaskItemRegionRightTop:
            {
                
                /// 右上
                arrowRect = CGRectMake(CGRectGetMidX([self obtainVisualFrame]) - imgSize.width * 0.5,
                                       CGRectGetMaxY([self obtainVisualFrame]) + space,
                                       imgSize.width,
                                       imgSize.height);
                
                CGFloat x = 0;
                
                if (textSize.width < CGRectGetWidth([self obtainVisualFrame]))
                {
                    x = CGRectGetMinX(arrowRect) - textSize.width * 0.5;
                }
                else
                {
                    x = desInsetsX + maxWidth - textSize.width;
                }
                
                textRect = CGRectMake(x, CGRectGetMaxY(arrowRect) + space, textSize.width, textSize.height);
            }
                break;
            case cyGuideMaskItemRegionLeftBottom:
            {
                
                /// 左下
                transform = CGAffineTransformMakeScale(-1, -1);
                arrowRect = CGRectMake(CGRectGetMidX([self obtainVisualFrame]) - imgSize.width * 0.5,
                                       CGRectGetMinY([self obtainVisualFrame]) - space - imgSize.height,
                                       imgSize.width,
                                       imgSize.height);
                
                CGFloat x = 0;
                
                if (textSize.width < CGRectGetWidth([self obtainVisualFrame]))
                {
                    x = CGRectGetMaxX(arrowRect) - textSize.width * 0.5;
                }
                else
                {
                    x = desInsetsX;
                }
                
                textRect = CGRectMake(x, CGRectGetMinY(arrowRect) - space - textSize.height, textSize.width, textSize.height);
                
            }
                break;
            case cyGuideMaskItemRegionRightBottom:
            {
                
                /// 右下
                transform = CGAffineTransformMakeScale(1, -1);
                arrowRect = CGRectMake(CGRectGetMidX([self obtainVisualFrame]) - imgSize.width * 0.5,
                                       CGRectGetMinY([self obtainVisualFrame]) - space - imgSize.height,
                                       imgSize.width,
                                       imgSize.height);
                
                CGFloat x = 0;
                
                if (textSize.width < CGRectGetWidth([self obtainVisualFrame]))
                {
                    x = CGRectGetMinX(arrowRect) - textSize.width * 0.5;
                }
                else
                {
                    x = desInsetsX + maxWidth - textSize.width;
                }
                
                textRect = CGRectMake(x, CGRectGetMinY(arrowRect) - space - textSize.height, textSize.width, textSize.height);
                
            }
                break;
           
        }
        [UIView animateWithDuration:0.3 animations:^{
            self.arrowView.transform = transform;
            self.arrowView.frame = arrowRect;
            self.textLabel.frame = textRect;
        }];
      
    }
    

    至于如何如何拿到可见区域的方位,见demo里的具体代码。
    4.显示和关闭遮罩动画,通过更改透明度的动画来控制显示或关闭。
    显示遮罩

    -(void)show
    {
        if (self.dataSource) {
            _count = [self.dataSource numbersOfItemsInGuideMaskView:self]; //拿到item的总数
        }
        /// 如果当前没有可以显示的 item 的数量
        if (_count < 1)  return;
        // 把透明度由0 - 1
        [[UIApplication sharedApplication].keyWindow addSubview:self]; // 把自身添加到keyWindow上去
            self.alpha = 0;
        [UIView animateWithDuration:1 animations:^{
            self.alpha = 1;
        }];
        self.currentIndex = 0;
    }
    

    关闭遮罩

    -(void)hide
    {
    // 隐藏操作
        [UIView animateWithDuration:.3f animations:^{
            self.alpha = 0;
        } completion:^(BOOL finished) {
            [self removeFromSuperview]; // 移除自身视图
        }];
    }
    

    以上简单整理了此demo的主要代码。
    app程序功能介绍动画git地址

    相关文章

      网友评论

          本文标题:app功能介绍的小动画

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