CodeV

6.1-渐变

渐变进程总是涉及至少两种颜色。颜色与起始点和结束点相关联,范围在0和1之间。除此之外,渐变可以像您想要的那样简单或复杂。图6-1演示了此范围。图6-1中的顶部图像显示了最简单的可能梯度。它从白色(0)变为黑色(1)。底部图像显示了从24个单独色调构建的梯度,参考颜色沉积在等距点处。这个复杂的梯度是从红色到橙色到黄色到绿色等等。

图6-1

图6-1一个简单的白到黑色渐变和受彩色盘(color wheel)启发的渐变。

如果你以前有使用过渐变,你会知道你可以绘制线性和径向的输出。如果没有,图6-2介绍了这两种样式。在左边,通过从白色(在底部)移动到黑色(在顶部)创建线性渐变。线性渐变沿着您指定的轴绘制颜色。

相反,径向渐变随着它们从开始到结束的演进而改变它们的绘图的宽度。在右边,径向渐变从白色(中心)开始,并在边缘附近延伸到黑色。在该示例中,半径在图像的中间从0开始并且在右边缘的范围结束。随着半径增长,颜色变暗,产生你在这里看到的“球体”外观。

图6-2

图6-2线性(左)和径向(右)渐变绘制。

您可能无法意识到图6-2中的两个图像都使用相同的源渐变,如图6-1顶部所示。渐变没有形状,位置或任何几何属性。他们只是描述颜色的进度。绘制渐变的方式完全取决于你和你使用的Core Graphics函数调用。

包装CGGradientRef类

CGGradientRef是Core Foundation类型,它在0.0到1.0的范围内存储任意数量的颜色和起点。您可以通过将两个数组传递给它来构建渐变 - 颜色及其(颜色对应的)位置,如下例所示:

1
2
3
4
5
CGGradientRef CGGradientCreateWithColors(
CGColorSpaceRef space,
CFArrayRef colors,
const CGFloat locations[]
};

在进一步查看Core Graphics实现之前,我想休息一下,介绍一个Objective-C的解决方法,当你使用这个类时真的会很有帮助。

总的来说,我发现通过Objective-C包装器使用渐变更容易,而不用担心内存管理和混合使用C和Core Foundation样式的元素,比如这里使用的两个数组。由于没有UIKit提供的渐变包装器和没有等价桥接的方法,我构建了一个Objective-C包装器。这是解决方法发挥作用的地方。

我受到一个小小的属性技巧的帮助,使ARC能够管理Core Foundation引用,就像它是一个正常的Cocoa Touch对象。http://llvm.org 网站描述此功能的方法如下:

GCC introduces __attribute__((NSObject)) on structure pointers to mean “this is an object.” This is useful because many low level data structures are declared as opaque structure pointers, e.g. CFStringRef, CFArrayRef, etc.

你可以使用这个技巧建立一个派生类型。下面是我用于Quartz渐变而定义的类型:

1

typedef __attribute__((NSObject)) CGGradientRef GradientObject;

此声明使您能够在使用ARC内存管理的同时,不使用无缝桥接建立基于Core Foundation的类属性类型。这是很重要的,因为,通常情况下,Quartz类不是无缝桥接到UIKit的。您可以使用派生类型来构建使用ARC风格的强引用的属性:

1
2

@property (nonatomic, strong) GradientObject storedGradient;

当清单6-1中创建的Gradient实例被释放时,底层CGGradientRef也会被释放。您不必构建特殊的dealloc方法来处理Core Foundation对象。你得到的是一个类,这个类是使用Objective-C接口处理核心图形渐变。您使用UIColor(类型的)颜色和NSNumber(类型的)位置的NSArrays

注意

正如你在这里看到的,这个属性方法需要显式的类型定义。避免普遍使用其他语言功能,如__typeof。 有关更多详细信息和注意事项,请参阅LLVM文档。使用这种方法我觉得很舒服并且也推荐(使用)这种方法,因为苹果工程师介绍给了我。

清单6-1创建Objective-C渐变类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@interface Gradient ()
@property (nonatomic, strong) GradientObject storedGradient;
@end

@implementation Gradient
- (CGGradientRef) gradient
{
// Expose the internal GradientObject property
// as a CGGradientRef to the outside world on demand
return _storedGradient;
}

// Primary entry point for the class. Construct a gradient
// with the supplied colors and locations
+ (instancetype) gradientWithColors: (NSArray *) colorsArray
locations: (NSArray *) locationArray
{
// Establish color space
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
if (space == NULL)
{
NSLog(@"Error: Unable to create RGB color space");
return nil;
}

// Convert NSNumber *locations array to CGFloat *
CGFloat locations[locationArray.count];
for (int i = 0; i < locationArray.count; i++)
locations[i] = fminf(fmaxf([locationArray[i] floatValue], 0), 1);

// Convert colors array to (id) CGColorRef
NSMutableArray *colorRefArray = [NSMutableArray array];
for (UIColor *color in colorsArray)
[colorRefArray addObject:(id)color.CGColor];

// Build the internal gradient
CGGradientRef gradientRef = CGGradientCreateWithColors(
space, (__bridge CFArrayRef) colorRefArray, locations);
CGColorSpaceRelease(space);
if (gradientRef == NULL)
{
NSLog(@"Error: Unable to construct CGGradientRef");
return nil;
}

// Build the wrapper, store the gradient, and return
Gradient *gradient = [[self alloc] init];
gradient.storedGradient = gradientRef;
CGGradientRelease(gradientRef);
return gradient;
}

+ (instancetype) gradientFrom: (UIColor *) color1
to: (UIColor *) color2
{
return [self gradientWithColors:@[color1, color2]
locations:@[@(0.0f), @(1.0f)]];
}
@end

本文翻译自《iOS Drawing Practical UIKit Solutions》作者:Erica Sadun,翻译:Cheng Dong。如果觉得本书不错请购买支持正版:亚马逊购买传送门,本书所有源代码可在GitHub上下载。译者虽然力求做到信,达,雅,但是由于时间仓促加之译者水平十分有限,文中难免会出现不正确,不准确,词不达意,难于理解的地方,还望各位批评指正,共同进步,谢谢。转载请注明出处。