• 干货之运用CALayer创建星级评分组件(五角星)


    本篇记录星级评分组件的创建过程以及CALayer的运用。

    为了实现一个星级评分的组件,使用了CALayer,涉及到mask、CGPathRef、UIBezierPath、动画和一个计算多角星关键节点的算法。

    CALayer管理基于图像的内容,并让我们可以在内容上添加动画。UIView及其子类拥有一个属性layer,我们可以运用该属性做出非常多的效果。例如圆角、多边形、甚至自定义形状的view,局部遮挡,擦除模糊效果,局部内容依次闪亮效果,弧形进度条等等。

    首先查看CALayer的一个属性mask,这个属性也是CALayer类型,跟他的名字意义一样,就是用来遮挡内容的,默认为nil,所以我们可以看到一个UIView上的内容。如果给一个可见内容的view的layer属性赋值CALayer实例,那这个view立刻就“消失”了。如下代码:

    CALayer *maskLayer = [CALayer layer];

    maskLayer.frame = testView.bounds;

    self.layer.mask = maskLayer;

    再看另一个类CAShapeLayer,继承与CALayer,并具有众多额外属性,例如path、fillColor、strokeColor。其中的path属性,可以决定“不遮挡”路径范围内的内容。如果我们将之前的mask属性赋值为一个CAShapeLayer实例,或者在之前的CALayer实例上addSublayer该实例,并指定一个中心圆的路径,那就实现了常见的圆形头像显示效果。与设置UIView的layer的corner作用一样。

    需要注意一点,如果添加的CAShapeLayer的fillColor为[UIColor clearColor].CGColor,即使在path范围内,也将失去“不遮挡”作用,而该值默认为不透明黑色。简单来说,如果透明就将遮挡内容。可以发现,mask及其sublayer上的填充和路径颜色,都是不会显示的,只用于决定是否遮挡自身所在layer的内容。

    利用这一特点,可以实现“开门”和“关门”的类似效果,在一个CALayer上添加两个sublayer作为“门”。还可以实现泼墨等更多的效果。

    值得一提的是,每个sublayer也可以利用frame和mask属性做出相对于view的局部的遮挡效果。

    回到CAShapeLayer的fillColor属性,该属性将path范围内填充上某种颜色;strokeColor是指定path轨迹上的颜色,strokeStart、strokeEnd指定轨迹的开始和结束的绘制点;还有line相关的属性指定线的属性。运用这些属性,可以做出任意形状的进度条、加载动画等。

    从以上记录可以看出path属性很重要,该属性是CGPathRef类型。常用创建方法为 CGPathCreateMutable(void)系列方法:

        CGMutablePathRef path = CGPathCreateMutable();
        CGPoint firstPoint = [[keyPointsArray firstObject] CGPointValue];
        CGPathMoveToPoint(path, nil, firstPoint.x, firstPoint.y);
        
        for (int i = 1; i < keyPointsArray.count; i++) {
            CGPoint currentPoint = [keyPointsArray[i] CGPointValue];
            CGPathAddLineToPoint(path, nil, currentPoint.x, currentPoint.y);
        }
        
        CGPathAddLineToPoint(path, nil, firstPoint.x, firstPoint.y);
        
        _maskLayer.path = path;
        _starShapeLayer.path = path;
        CGPathRelease(path);

    也可以使用UIBezierPath的系列类方法,添加好路径后,由方法- (CGPathRef)CGPath得到path。例如:

        UIBezierPath *bezierPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(_bgShapeLayer.frame.size.width / 2.0, _bgShapeLayer.frame.size.height / 2.0) radius:_radius startAngle:0 endAngle: 2 * M_PI clockwise:YES];
        _bgShapeLayer.path = bezierPath.CGPath;

    下面开始记录星级评分组件的创建。

    下图为最终效果:

    点击星星的左右半边会决定填充一半还是全部内容。还可以设置百分比填充内容。支持>5角星。

    单个星星的内部设计为:view设置了灰色背景色;view.layer.mask的path为五角星;view.layer添加一个bgShapeLayer,背景为黄色,控制其width实现填充百分比;再添加一个starShapeLayer,path为同样的五角星,设置路径颜色作为边框。

    这里的难点在于计算多角星的交点,如果为五角星,则需要10个关键点的坐标。

    下面记录求交点的算法:

    1.先确定一个初始顶点

    2.根据等分弧度和半径,由三角函数的正余弦相关公式求得全部外顶点

    3.求出内交点的两条相交直线

    4.由二元一次方程求出交点

    具体实现代码如下:

    - (NSArray *)getStarKeyPoints
    {
        CGPoint center = CGPointMake(self.frame.size.width / 2.0, _radius);
        CGFloat sectionAngle = 2 * M_PI / _topPointCount;
        
        NSMutableArray *keyPointsArray = [NSMutableArray array];
        CGPoint firstPoint = CGPointMake(center.x, 0);
        [keyPointsArray addObject:[NSValue valueWithCGPoint:firstPoint]];
        
        //外围顶点
        for (int i = 1; i < _topPointCount; i++) {
            CGFloat x = cosf(i * sectionAngle - M_PI_2) * _radius;
            CGFloat y = sinf(i * sectionAngle - M_PI_2) * _radius;
            
            CGPoint point = CGPointMake(x + center.x, y + center.y);
            
            [keyPointsArray addObject:[NSValue valueWithCGPoint:point]];
        }
        
        //内交点
        NSMutableArray *crossPointsArray = [NSMutableArray array];
    
        //采用二元一次方程求解
        //AC点确定直线方程y = kx + b
        //过B点直线y = B.y
        for (int i = 0; i < _topPointCount; i++) {
            CGPoint A = [keyPointsArray[i] CGPointValue];
            
            NSInteger index = i + 1;
            if (index > _topPointCount - 1) {
                index -= _topPointCount;
            }
            CGPoint B = [keyPointsArray[index] CGPointValue];
            
            index = i + 2;
            if (index > _topPointCount - 1) {
                index -= _topPointCount;
            }
            CGPoint C = [keyPointsArray[index] CGPointValue];
            
            index = i - 1;
            if (index < 0) {
                index += _topPointCount;
            }
            CGPoint E = [keyPointsArray[index] CGPointValue];
            
            CGFloat F_x = 0.0, F_y = 0.0, k1 = 0.0, k2 = 0.0, b1 = 0.0, b2 = 0.0;
            
            if (A.x == C.x) {
                F_x = A.x;
            } else {
                k1 = (A.y - C.y) / (A.x - C.x);
                b1 = A.y - k1 * A.x;
            }
            
            if (B.x == E.x) {
                F_x = B.x;
            } else {
                k2 = (B.y - E.y) / (B.x - E.x);
                b2 = B.y - k2 * B.x;
            }
            
            if (A.x == C.x) {
                F_y = k2 * F_x + b2;
            }else if (B.x == E.x) {
                F_y = k1 * F_x + b1;
            }else{
                if (k1 == 0) {
                    F_y = A.y;
                    F_x = (F_y - b2) / k2;
                } else {
                    F_y = (b1 * k2 - b2 * k1) / (k2 - k1);
                    F_x = (F_y - b1) / k1;
                }
            }
    
            CGPoint pointF = CGPointMake(F_x, F_y);
            [crossPointsArray addObject:[NSValue valueWithCGPoint:pointF]];
        }
        
        //合并数据
        for (int i = 0; i < crossPointsArray.count; i++) {
            [keyPointsArray insertObject:crossPointsArray[i] atIndex:(i * 2 + 1)];
        }
        
        return keyPointsArray;
    }

    将关键点线段添加到path中,生成多角形路径,用于mask和边框layer。

    最后添加点击手势,根据触摸点的范围确定操作。

    实现单个五角星以后,常见的将五个五角星并排放置,统一管理每个五角星的填充百分比。

    ALWStarComment已在Base项目中更新:https://github.com/ALongWay/base.git

  • 相关阅读:
    基本数据类型包装类
    LeetCode算法题-Robot Return to Origin(Java实现)
    LeetCode算法题-Two Sum IV
    LeetCode算法题-Set Mismatch(Java实现)
    LeetCode算法题-Maximum Average Subarray I(Java实现)
    LeetCode算法题-Average of Levels in Binary Tree(Java实现)
    LeetCode算法题-Sum of Square Numbers(Java实现)
    LeetCode算法题-Maximum Product of Three Numbers(Java实现)
    LeetCode算法题-Merge Two Binary Trees(Java实现)
    LeetCode算法题-Construct String from Binary Tree(Java实现)
  • 原文地址:https://www.cnblogs.com/ALongWay/p/6031272.html
Copyright © 2020-2023  润新知