app功能介绍的小动画

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


app功能介绍的小动画_第1张图片
动画执行代理方法1.gif

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


app功能介绍的小动画_第2张图片
app功能介绍的动画.png

动画思路:
app功能介绍的小动画_第3张图片
动画思路.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功能介绍的小动画)