CodeV

6.3-构建渐变

每个渐变都包含两个值集合:

  • 一系列有序的颜色
  • 发生颜色变化的位置

例如,您可以定义一个渐变,沿渐变的方向从红色到绿色到蓝色渐变,(位置点)从0.0到0.5到1.0。 渐变在这些参考点之间的插入任何值。沿着渐变的三分之一,在0.33位置,颜色是从红色到绿色的方式的大约66%。 或者,例如,想象一个简单的黑到白渐变。 中灰色出现在绘制渐变的开始和结束之间的中间位置。

您可以提供任何类型的颜色和位置序列,只要颜色使用RGB或灰度颜色空间。 (您不能使用图案颜色绘制渐变。)位置在0.0和1.0之间。 如果提供的值超出此范围,则创建函数在运行时返回NULL。

最常用的渐变是白色到黑色,白色到透明和黑色到透明。 因为你应用这些仅需要不同的alpha级别,我觉得定义以下宏会很方便:

1
2
#define WHITE_LEVEL(_amt_, _alpha_) \
[UIColor colorWithWhite:(_amt_) alpha:(_alpha_)]

此宏在您指定的白色和Alpha级别返回灰度颜色。 白色级别可以从0(黑色)到1(白色),alpha级别从0(透明)到1(不透明)。

许多开发人员使用颜色之间的默认插值来使其渐变,如例6-1所示。 此示例创建一个从透明到黑的渐变,并从其下方的绿色形状绘制点70%到100%(来渐变)。 您会在图6-6的左上角看到结果。 与图6-6中的其他渐变图形对比。 正如你会发现,图像是使用渐变easing构建的。

例6-1绘制线性渐变

1
2
3
4
5
6
7
8
9
10
11
12
13
Gradient *gradient = [Gradient
gradientFrom:WHITE_LEVEL(0, 0) to:WHITE_LEVEL(0, 1)];

// Calculate the points
CGPoint p1 = RectGetPointAtPercents(path.bounds, 0.7, 0.5);
CGPoint p2 = RectGetPointAtPercents(path.bounds, 1.0, 0.5);

// Draw a green background
[path fill:greenColor];

// Draw the gradient across the green background
[path addClip];
[gradient drawFrom:p1 toPoint:p2];

图6-6

图6-6调整渐变影响绘制输出。 左上图显示了标准渐变插值。 右上图显示了ease-in的插值。 左下图像使用ease-in-out,右下图像使用ease-out。 每个渐变绘制在实心绿色圆角矩形背景上。

Easing

渐变函数改变渐变色调改变的速率。 根据您选择的函数,它们提供更缓和的渐变进入和渐变退出。 我最喜欢的是分别在图6-6的右上和左下显示的ease-in和ease-in-out渐变。 正如你可以看到,这两种方法避免突然的结束转换。 这些苛刻的过渡是由perceptual banding,也称为illusory mach bands创造的。

Mach bands是物理学家Ernst Mach首先注意到的光错觉。 由于我们的大脑中的自然模式处理,它们出现时,沿着边界出现略微不同的灰色阴影。 它们发生在计算机图形中,因为绘制停止在算法告诉它的停止的位置。

在图6-6中,您可以在左上角和右下角的镜头中的渐变绘图区域的边缘看到这些效果。 通过使用ease-in-out绘图,您可以拉伸基础颜色和渐变叠加之间的过渡,避免(出现)bands。

图6-7显示了图6-6中渐变的缓动(easing)函数。 你看到一组函数:linear(左上),ease-in(右上),ease-in-out(左下)和ease-out(右下)。 缓动会影响函数的开始(“in”)或结束(“out”),以建立更多的渐进变化。 这些函数与许多绘图和动画算法一起使用。

图6-7

图6-7不同于值随时间变化相等的线性插值,缓动函数改变开始(ease-in)和/或结束(ease-out)的变化率。 它们提供更加渐进的过渡,更好地反映光和运动中的自然效果。

清单6-3定义了一个Gradient类方法,它根据您提供的函数构建渐变。 您传递一个接受输入百分比(时间轴)的块,并返回一个值(数量轴)以应用于开始和结束颜色值。 该方法内插颜色并将值添加到渐变。

三个标准缓动(easing)函数使用两个参数:经过时间和指数。 你传递的指数决定了所产生的easing的类型。 对于标准立方缓和(cubic easing),您传递3作为第二个参数,对于二次缓和(quadratic easing),传递2作为第二个参数。传递1产生一个没有缓和(easing)的线性函数。

您可以在插值块中应用任何您喜欢的函数。 以下代码段使用in-out立方体缓动构建渐变:

1
2
3
Gradient *gradient = [Gradient gradientUsingInterpolationBlock:
^CGFloat (CGFloat percent) {return EaseInOut(percent, 3);}
between: WHITE_LEVEL(0, 0) and: WHITE_LEVEL(0, 1)];

清单6-3应用函数创建自定义渐变

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
typedef CGFloat (^InterpolationBlock)(CGFloat percent);

// Build a custom gradient using the supplied block to
// interpolate between the start and end colors
+ (instancetype) gradientUsingInterpolationBlock:
(InterpolationBlock) block
between: (UIColor *) c1 and: (UIColor *) c2;
{
if (!block)
{
NSLog(@"Error: No interpolation block");
return nil;
}

NSMutableArray *colors = [NSMutableArray array];
NSMutableArray *locations = [NSMutableArray array];
int numberOfSamples = 24;
for (int i = 0; i <= numberOfSamples; i++)
{
CGFloat amt = (CGFloat) i / (CGFloat) numberOfSamples;
CGFloat percentage = fmin(fmax(0.0, block(amt)), 1.0);
[colors addObject:
InterpolateBetweenColors(c1, c2, percentage)];
[locations addObject:@(amt)];
}

return [Gradient gradientWithColors:colors
locations:locations];
}

// Return an interpolated color
UIColor *InterpolateBetweenColors(
UIColor *c1, UIColor *c2, CGFloat amt) {
{
CGFloat r1, g1, b1, a1;
CGFloat r2, g2, b2, a2;

if (CGColorGetNumberOfComponents(c1.CGColor) == 4)
[c1 getRed:&r1 green:&g1 blue:&b1 alpha:&a1];
else
{
[c1 getWhite:&r1 alpha:&a1];
g1 = r1; b1 = r1;
}

if (CGColorGetNumberOfComponents(c2.CGColor) == 4)
[c2 getRed:&r2 green:&g2 blue:&b2 alpha:&a2];
else
{
[c2 getWhite:&r2 alpha:&a2];
g2 = r2; b2 = r2;
}

CGFloat r = (r2 * amt) + (r1 * (1.0 - amt));
CGFloat g = (g2 * amt) + (g1 * (1.0 - amt));
CGFloat b = (b2 * amt) + (b1 * (1.0 - amt));
CGFloat a = (a2 * amt) + (a1 * (1.0 - amt));
return [UIColor colorWithRed:r green:g blue:b alpha:a];
}

#pragma mark – Easing Functions

// Ease only the beginning
CGFloat EaseIn(CGFloat currentTime, int factor)
{
return powf(currentTime, factor);
}

// Ease only the end
CGFloat EaseOut(CGFloat currentTime, int factor)
{
return 1 - powf((1 - currentTime), factor);
}

// Ease both beginning and end
CGFloat EaseInOut(CGFloat currentTime, int factor)
{
currentTime = currentTime * 2.0;
if (currentTime < 1)
return (0.5 * pow(currentTime, factor));
currentTime -= 2.0;
if (factor % 2)
return 0.5 * (pow(currentTime, factor) + 2.0);
return 0.5 * (2.0 - pow(currentTime, factor)); }

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