iOS 颜色渐变圆环

实现方式一:使用 CAGradientLayer 和 CAShapLayer

#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>

@interface GradientLayer : CALayer

// 默认值 0.2. 取值范围为 (0, 1]
@property (nonatomic, assign) CGFloat lineWithFactor;

// 默认为白色
@property (nonatomic, copy) UIColor *tintColor;

@end
#import "GradientLayer.h"

@implementation GradientLayer
{
    CGRect _preBounds;

    CAGradientLayer *_firstGradientLayer;
    CAGradientLayer *_secondGradientLayer;
    CAGradientLayer *_thirdGradientLayer;
    CAGradientLayer *_forthGradientLayer;
}
@synthesize tintColor = _tintColor;

+ (instancetype)layer {
    GradientLayer *layer = [[self alloc] init];
    return layer;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        [self paramInit];
        [self layersInit];
    }
    return self;
}

- (void)layoutSublayers {
    if (!CGRectEqualToRect(_preBounds, self.bounds)) {
        [self layout];
    }
}

#pragma mark - Implementation Property

- (void)setTintColor:(UIColor *)tintColor {
    if (!tintColor) {
        _tintColor = [UIColor whiteColor];
    } else {
        _tintColor = [tintColor copy];
    }

    [self configColors];
}

- (UIColor *)tintColor {
    if (!_tintColor) {
        return [UIColor whiteColor];
    }
    return _tintColor;
}

#pragma mark - Private Selectors

- (void)paramInit {
    self.lineWithFactor = 0.2f;
}

- (void)layersInit {
    _firstGradientLayer = [CAGradientLayer layer];
    _firstGradientLayer.startPoint = CGPointMake(1, 1);
    _firstGradientLayer.endPoint = CGPointMake(0, 0);

    _secondGradientLayer = [CAGradientLayer layer];
    _secondGradientLayer.startPoint = CGPointMake(1, 0);
    _secondGradientLayer.endPoint = CGPointMake(0, 1);

    _thirdGradientLayer = [CAGradientLayer layer];
    _thirdGradientLayer.startPoint = CGPointMake(0, 0);
    _thirdGradientLayer.endPoint = CGPointMake(1, 1);

    _forthGradientLayer = [CAGradientLayer layer];
    _forthGradientLayer.startPoint = CGPointMake(0, 1);
    _forthGradientLayer.endPoint = CGPointMake(1, 0);

    [self configColors];

    [self addSublayer:_firstGradientLayer];
    [self addSublayer:_secondGradientLayer];
    [self addSublayer:_thirdGradientLayer];
    [self addSublayer:_forthGradientLayer];
}

- (void)layout {
    CGFloat width = MIN(CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds)) / 2.0f;
    CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
    CGFloat lineWidth = self.lineWithFactor * width;

    _firstGradientLayer.frame = CGRectMake(center.x, 0, width, width);
    _secondGradientLayer.frame = CGRectMake(center.x - width, 0, width, width);
    _thirdGradientLayer.frame = CGRectMake(center.x - width, center.y, width, width);
    _forthGradientLayer.frame = CGRectMake(center.x, center.y, width, width);

    UIBezierPath *path = [[UIBezierPath alloc] init];
    [path addArcWithCenter:center radius:width - lineWidth / 2.0f startAngle:0 endAngle:2 * M_PI clockwise:YES];
    CAShapeLayer *shape = [CAShapeLayer layer];
    shape.path = path.CGPath;
    shape.lineWidth = lineWidth;
    shape.strokeStart = 0.0f;
    shape.strokeEnd =  1.0f;
    shape.strokeColor = [UIColor blueColor].CGColor;
    shape.fillColor = [UIColor clearColor].CGColor;
    [self setMask:shape];
}

- (void)configColors {
    _firstGradientLayer.colors = @[(id)self.tintColor.CGColor, (id)[self.tintColor colorWithAlphaComponent:0.75].CGColor];

    _secondGradientLayer.colors = @[(id)[self.tintColor colorWithAlphaComponent:0.75].CGColor, (id)[self.tintColor colorWithAlphaComponent:0.5].CGColor];

    _thirdGradientLayer.colors = @[(id)[self.tintColor colorWithAlphaComponent:0.5].CGColor, (id)[self.tintColor colorWithAlphaComponent:0.25].CGColor];

    _forthGradientLayer.colors = @[(id)[self.tintColor colorWithAlphaComponent:0.25].CGColor, (id)[self.tintColor colorWithAlphaComponent:0].CGColor];
}

@end

这种实现方式的缺点是,当圆环颜色部分变宽的时候会有明显的颜色分界

实现方式二:绘制中心渐变的图片并显示的 layer,配合 CAShapLayer

//
// The MIT License (MIT)
// 
// Copyright (C) 2012 Pavel Ivashkov
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
// and associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//
//
//  AngleGradientLayer.h
//  paiv
//
//  Created by Pavel Ivashkov on 2012-02-12.
//

#import <QuartzCore/QuartzCore.h>


@interface AngleGradientLayer : CALayer

/* The array of CGColorRef objects defining the color of each gradient
 * stop. Defaults to nil. */

@property(copy) NSArray *colors;

/* An optional array of NSNumber objects defining the location of each
 * gradient stop as a value in the range [0,1]. The values must be
 * monotonically increasing. If a nil array is given, the stops are
 * assumed to spread uniformly across the [0,1] range. When rendered,
 * the colors are mapped to the output colorspace before being
 * interpolated. Defaults to nil. */

@property(copy) NSArray *locations;


/* The core method generating gradient image.
 */
+ (CGImageRef)newImageGradientInRect:(CGRect)rect colors:(NSArray *)colors locations:(NSArray *)locations;

@end
//
// The MIT License (MIT)
// 
// Copyright (C) 2012 Pavel Ivashkov
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//
//
//  AngleGradientLayer.m
//  paiv
//
//  Created by Pavel Ivashkov on 2012-02-12.
//

#import "AngleGradientLayer.h"

#if __has_feature(objc_arc)
#define BRIDGE_CAST(T) (__bridge T)
#else
#define BRIDGE_CAST(T) (T)
#endif

#define byte unsigned char
#define F2CC(x) ((byte)(255 * x))
#define RGBAF(r,g,b,a) (F2CC(r) << 24 | F2CC(g) << 16 | F2CC(b) << 8 | F2CC(a))
#define RGBA(r,g,b,a) ((byte)r << 24 | (byte)g << 16 | (byte)b << 8 | (byte)a)
#define RGBA_R(c) ((uint)c >> 24 & 255)
#define RGBA_G(c) ((uint)c >> 16 & 255)
#define RGBA_B(c) ((uint)c >> 8 & 255)
#define RGBA_A(c) ((uint)c >> 0 & 255)

@interface AngleGradientLayer()

- (CGImageRef)newImageGradientInRect:(CGRect)rect;

@end


static void angleGradient(byte* data, int w, int h, int* colors, int colorCount, float* locations, int locationCount);


@implementation AngleGradientLayer

- (id)init
{
    if (!(self = [super init]))
        return nil;

    self.needsDisplayOnBoundsChange = YES;

    return self;
}

#if !__has_feature(objc_arc)
- (void)dealloc
{
    [_colors release];
    [_locations release];
    [super dealloc];
}
#endif

- (void)drawInContext:(CGContextRef)ctx
{
    CGContextSetFillColorWithColor(ctx, self.backgroundColor);
    CGRect rect = CGContextGetClipBoundingBox(ctx);
    CGContextFillRect(ctx, rect);

    CGImageRef img = [self newImageGradientInRect:rect];
    CGContextDrawImage(ctx, rect, img);
    CGImageRelease(img);
}

- (CGImageRef)newImageGradientInRect:(CGRect)rect
{
    return [[self class] newImageGradientInRect:rect colors:self.colors locations:self.locations];
}

+ (CGImageRef)newImageGradientInRect:(CGRect)rect colors:(NSArray *)colors locations:(NSArray *)locations
{
    int w = CGRectGetWidth(rect);
    int h = CGRectGetHeight(rect);
    int bitsPerComponent = 8;
    int bpp = 4 * bitsPerComponent / 8;
    int byteCount = w * h * bpp;

    int colorCount = (int)colors.count;
    int locationCount = (int)locations.count;
    int* cols = NULL;
    float* locs = NULL;

    if (colorCount > 0) {
        cols = calloc(colorCount, bpp);
        int *p = cols;
        for (id cg in colors) {
            CGColorRef c = BRIDGE_CAST(CGColorRef)cg;
            float r, g, b, a;

            size_t n = CGColorGetNumberOfComponents(c);
            const CGFloat *comps = CGColorGetComponents(c);
            if (comps == NULL) {
                *p++ = 0;
                continue;
            }
            r = comps[0];
            if (n >= 4) {
                g = comps[1];
                b = comps[2];
                a = comps[3];
            }
            else {
                g = b = r;
                a = comps[1];
            }
            *p++ = RGBAF(r, g, b, a);
        }
    }
    if (locationCount > 0 && locationCount == colorCount) {
        locs = calloc(locationCount, sizeof(locs[0]));
        float *p = locs;
        for (NSNumber *n in locations) {
            *p++ = [n floatValue];
        }
    }

    byte* data = malloc(byteCount);
    angleGradient(data, w, h, cols, colorCount, locs, locationCount);

    if (cols) free(cols);
    if (locs) free(locs);

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Little;
    // 创建图片上下文,data 中包含了所有点的颜色。颜色为 16 进制表示
    CGContextRef ctx = CGBitmapContextCreate(data, w, h, bitsPerComponent, w * bpp, colorSpace, bitmapInfo);
    CGColorSpaceRelease(colorSpace);
    CGImageRef img = CGBitmapContextCreateImage(ctx);
    CGContextRelease(ctx);
    free(data);
    return img;
}

@end

static inline byte blerp(byte a, byte b, float w)
{
    return a + w * (b - a);
}
static inline int lerp(int a, int b, float w)
{
    return RGBA(blerp(RGBA_R(a), RGBA_R(b), w),
                blerp(RGBA_G(a), RGBA_G(b), w),
                blerp(RGBA_B(a), RGBA_B(b), w),
                blerp(RGBA_A(a), RGBA_A(b), w));
}
static inline int multiplyByAlpha(int c)
{
    float a = RGBA_A(c) / 255.0;
    return RGBA((byte)(RGBA_R(c) * a),
                (byte)(RGBA_G(c) * a),
                (byte)(RGBA_B(c) * a),
                RGBA_A(c));
}

void angleGradient(byte* data, int w, int h, int* colors, int colorCount, float* locations, int locationCount)
{
    if (colorCount < 1) return;
    if (locationCount > 0 && locationCount != colorCount) return;

    int* p = (int*)data;
    float centerX = (float)w / 2;
    float centerY = (float)h / 2;

    for (int y = 0; y < h; y++)
    for (int x = 0; x < w; x++) {
        float dirX = x - centerX;
        float dirY = y - centerY;
        float angle = atan2f(dirY, dirX);
        if (dirY < 0) angle += 2 * M_PI;
        angle /= 2 * M_PI;

        int index = 0, nextIndex = 0;
        float t = 0;

        if (locationCount > 0) {
            for (index = locationCount - 1; index >= 0; index--) {
                if (angle >= locations[index]) {
                    break;
                }
            }
            if (index >= locationCount) index = locationCount - 1;
            nextIndex = index + 1;
            if (nextIndex >= locationCount) nextIndex = locationCount - 1;
            float ld = locations[nextIndex] - locations[index];
            t = ld <= 0 ? 0 : (angle - locations[index]) / ld;
        }
        else {
            t = angle * (colorCount - 1);
            index = t;
            t -= index;
            nextIndex = index + 1;
            if (nextIndex >= colorCount) nextIndex = colorCount - 1;
        }

        int lc = colors[index];
        int rc = colors[nextIndex];
        int color = lerp(lc, rc, t);
        color = multiplyByAlpha(color);
        *p++ = color;
    }
}

源文件地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值