阅读 21

一个方法实现:添加一个带箭头还有圆角的边框


/** 边框位置 */
typedef NS_ENUM(NSInteger, FCBorderPosition) {
    FCBorderPositionTop     = 1 << 0,
    FCBorderPositionLeft    = 1 << 1,
    FCBorderPositionBottom  = 1 << 2,
    FCBorderPositionRight   = 1 << 3,
    FCBorderPositionAll     = FCBorderPositionTop | FCBorderPositionLeft | FCBorderPositionBottom | FCBorderPositionRight,
};

/**
 给view添加一个带箭头的边框
 
 @param direction 箭头朝向
 @param offset 箭头的坐标,如果是在左右朝向,传箭头中心位置的y值;如果是上下朝向,传箭头中心位置x值
 @param width 箭头的宽度
 @param height 箭头的高度
 @param cornerRadius 圆角半径,<=0不设圆角
 @param borderWidth 边框宽度
 @param borderColor 边框颜色
 */
- (void)addArrowBorderAt:(FCBorderPosition)direction 
offset:(CGFloat)offset 
width:(CGFloat)width 
height:(CGFloat)height 
cornerRadius:(CGFloat)cornerRadius 
borderWidth:(CGFloat)borderWidth 
borderColor:(UIColor *)borderColor;
复制代码

这个方法很长,因为确实要求复杂:

  • 要箭头
  • 要圆角
  • 还要有边框

来张图解释一下效果:

这时仿的QQ的弹框效果。

各个参数的意思注释里应该很清楚了。下面是实现代码:

-(void)addArrowBorderAt:(FCBorderPosition)direction offset:(CGFloat)offset width:(CGFloat)width height:(CGFloat)height cornerRadius:(CGFloat)cornerRadius borderWidth:(CGFloat)borderWidth borderColor:(UIColor *)borderColor{
    
    [self removeFCBorder];
    
    //只有一个mask层
    CAShapeLayer *mask = [[CAShapeLayer alloc] init];
    mask.frame = self.bounds;
    mask.name = FCBorderMaskName;
    self.layer.mask = mask;
    
    UIBezierPath *path = [[UIBezierPath alloc] init];
    
    CGFloat minX = 0, minY = 0, maxX = self.bounds.size.width, maxY = self.bounds.size.height;
    if (direction == FCBorderPositionTop) {
        minY = height;
    }else if (direction == FCBorderPositionRight){
        maxX -= height;
    }else if (direction == FCBorderPositionLeft){
        minX += height;
    }else if (direction == FCBorderPositionBottom){
        maxY -= height;
    }
    
    //上边
    [path moveToPoint:CGPointMake(minX+cornerRadius, minY)];
    if (direction == FCBorderPositionTop) {
        [path addLineToPoint:CGPointMake(offset-width/2, minY)];
        [path addLineToPoint:CGPointMake(offset, minY-height)];
        [path addLineToPoint:CGPointMake(offset+width/2, minY)];
    }
    [path addLineToPoint:CGPointMake(maxX-cornerRadius, minY)];
    
    //右上角
    if (cornerRadius>0) {
        [path addArcWithCenter:CGPointMake(maxX-cornerRadius, minY+cornerRadius) radius:cornerRadius startAngle:-M_PI_2 endAngle:0 clockwise:YES];
    }
    
    
    
    //右边
    if (direction == FCBorderPositionRight) {
        [path addLineToPoint:CGPointMake(maxX, offset-width/2)];
        [path addLineToPoint:CGPointMake(maxX+height, offset)];
        [path addLineToPoint:CGPointMake(maxX, offset+width/2)];
    }
    [path addLineToPoint:CGPointMake(maxX, maxY-cornerRadius)];
    
    //右下角
    if (cornerRadius>0) {
        [path addArcWithCenter:CGPointMake(maxX-cornerRadius, maxY-cornerRadius) radius:cornerRadius startAngle:0 endAngle:M_PI_2 clockwise:YES];
    }
    
    
    
    //下边
    if (direction == FCBorderPositionBottom) {
        [path addLineToPoint:CGPointMake(offset-width/2, maxY)];
        [path addLineToPoint:CGPointMake(offset, maxY+height)];
        [path addLineToPoint:CGPointMake(offset+width/2, maxY)];
    }
    [path addLineToPoint:CGPointMake(minX+cornerRadius, maxY)];
    
    //左下角
    if (cornerRadius>0) {
        [path addArcWithCenter:CGPointMake(minX+cornerRadius, maxY-cornerRadius) radius:cornerRadius startAngle:M_PI_2 endAngle:M_PI clockwise:YES];
    }
    
    
    //右边
    if (direction == FCBorderPositionLeft) {
        [path addLineToPoint:CGPointMake(minX, offset-width/2)];
        [path addLineToPoint:CGPointMake(minX-height, offset)];
        [path addLineToPoint:CGPointMake(minX, offset+width/2)];
    }
    [path addLineToPoint:CGPointMake(minX, minY+cornerRadius)];
    
    //右下角
    if (cornerRadius>0) {
        [path addArcWithCenter:CGPointMake(minX+cornerRadius, minY+cornerRadius) radius:cornerRadius startAngle:M_PI endAngle:M_PI_2*3 clockwise:YES];
    }
    
    mask.path = [path CGPath];
    
    if (borderWidth>0) {
        CAShapeLayer *border = [[CAShapeLayer alloc] init];
        border.path = [path CGPath];
        border.strokeColor = borderColor.CGColor;
        border.lineWidth = borderWidth*2;
        border.fillColor = [UIColor clearColor].CGColor;
        [self.layer addSublayer:border];
        
        [self markFCBorder:border];
    }
}

复制代码

首先说思路:layer的mask可以把不需要的地方遮住,所以绘制一个特定的mask给layer就可以把圆角和箭头切割出来。然后使用一个另外一个layer,它的图形跟mask一样,但是用来绘制边框,把这个layer加上去边框就有了。

大部分的工作都是在绘制mask的路径,分为4个边一步一步的加,看一个边的情况就可以了解了:

//上边
    //从左上角开始
    [path moveToPoint:CGPointMake(minX+cornerRadius, minY)];
    if (direction == FCBorderPositionTop) {
        //多增加3个点,路线饶了一下
        [path addLineToPoint:CGPointMake(offset-width/2, minY)];
        [path addLineToPoint:CGPointMake(offset, minY-height)];
        [path addLineToPoint:CGPointMake(offset+width/2, minY)];
    }
    //到达右上角
    [path addLineToPoint:CGPointMake(maxX-cornerRadius, minY)];
    
    //右上角
    if (cornerRadius>0) {
        [path addArcWithCenter:CGPointMake(maxX-cornerRadius, minY+cornerRadius) radius:cornerRadius startAngle:-M_PI_2 endAngle:0 clockwise:YES];
    }
复制代码

整个过程就是从左上角画线,画到右上角。 如果箭头在上边,则在上边加入箭头,加入箭头其实就是多加3个点:箭头的左下角、顶点和右上角。这样走了一下折线。

如果有圆角就再绘制圆角。

边框就是一个CAShapeLayer,没什么可说的,有个问题是它使用了和mask同样的path,这样它的线条有一半是在mask之外,会被切掉,所以lineWidth做了一个乘2的处理:border.lineWidth = borderWidth*2;

最后[self removeFCBorder];[self markFCBorder:border];是为了标记加入的边框layer,保证它的唯一,否则这个方法多调用几次就会有多个边框叠加,特别是frame发生改变后,就会出现奇怪的边框了。

static NSString *FCBorderLayerKey = @"FCBorderLayerKey";
static NSString *FCBorderMaskName = @"FCBorderMaskName";

-(void)markFCBorder:(CALayer *)layer{
    
    objc_setAssociatedObject(self, &FCBorderLayerKey, layer, OBJC_ASSOCIATION_RETAIN);
}

-(void)removeFCBorder{
    if ([self.layer.mask.name isEqualToString:FCBorderMaskName]) {
        self.layer.mask = nil;
    }
    
    CAShapeLayer *oldLayer = objc_getAssociatedObject(self, &FCBorderLayerKey);
    if (oldLayer) [oldLayer removeFromSuperlayer];
}
复制代码

这里使用了runtime的关联对象函数做了联系。

另:写了一个弹框的工具类,欢迎查看FCPopActionView

关注下面的标签,发现更多相似文章
评论