更新:換一種思路實現(xiàn)。iOS 沿曲線線性漸變的貝塞爾曲線(改進(jìn)版)
iOS原生的漸變只支持線性的漸變,但有的時候我們需要沿曲線進(jìn)行漸變。
先看下垂直線性漸變與沿曲線線性漸變的區(qū)別


那么先來分析一下這個問題:
- 怎樣繪制曲線?
對于貝塞爾曲線的繪制,系統(tǒng)提供了一系列的方法,同時我們已可以通過公式計算出一條貝塞爾曲線。 - 如何保證顏色漸變?
找到曲線上的點,計算出每一個點的色值。
只要解決上面的問題就可以畫出一條沿曲線線性漸變的貝塞爾曲線,曲線畫起來還是比較簡單的,但是這樣計算出每一點的色值是一件比較麻煩的事情。
1、貝塞爾曲線
這里先介紹一下貝塞爾曲線的一些東西,以二次貝塞爾曲線為例,先來動態(tài)感受一下繪制過程

一條二次貝塞爾曲線需要三個點,A:起點 ;B:控制點;C:終點

然后在AB上取點E,在BC上取點F 。使AD:AB = BE:BC

在DE上取點F,使DF:DE = AD:AB = BE:BC

F點就是貝塞爾曲線上的一個點,以此類推,取點一系列的點之后在ABC之間就產(chǎn)生了一條貝塞爾曲線

可以看出貝塞爾曲線上的每個點是有規(guī)律的,二次貝塞爾曲線的方程為
P0:起點;P1:控制點; P2:終點 ;t:百分比

用OC表達(dá)的話就是這樣的
CGFloat x = pow((1-t), 2) * _startPoint.x + 2 * (1-t) * t * _controlPoint.x + pow(t, 2) * _endPoint.x;
CGFloat y = pow((1-t), 2) * _startPoint.y + 2 * (1-t) * t * _controlPoint.y + pow(t, 2) * _endPoint.y;
2、漸變色
既然已經(jīng)一顆貝塞爾曲線的方程,那就可以操作曲線上的每一個點了,那怎么設(shè)置每一個點的顏色的。
1、取點
對于取點還有一個問題需要注意,由于貝塞爾曲線并不是勻速變化的,所有如果均勻分割 t 來進(jìn)行取點的話,取出來的點是不均勻的。不均勻的點會造成有的地方缺失點,形成空白。所以需要對 t 進(jìn)行修正,取出間隔均勻的點。

想要均勻的點,就需要線計算出曲線長度,接下來就使用辛普森積分法來計算曲線的長度。這個求的是二次貝塞爾曲線的長度,如果需更高次的曲線,可修改一下修改。
//曲線長度
- (CGFloat)lengthWithT:(CGFloat)t{
NSInteger totalStep = 1000;
NSInteger stepCounts = (NSInteger)(totalStep * t);
if(stepCounts & 1) stepCounts++;
if(stepCounts==0) return 0.0;
NSInteger halfCounts = stepCounts/2;
CGFloat sum1=0.0, sum2=0.0;
CGFloat dStep = (t * 1.0)/stepCounts;
for(NSInteger i=0; i<halfCounts; i++) {
sum1 += [self speedAtT:(2*i+1)*dStep];
}
for(NSInteger i=1; i<halfCounts; i++) {
sum2 += [self speedAtT:(2*i)*dStep ];
}
return ([self speedAtT:0]+[self speedAtT:1]+2*sum2+4*sum1)*dStep/3.0;
}
- (CGFloat)speedAtT:(CGFloat)t {
CGFloat xSpeed = [self xSpeedAtT:t];
CGFloat ySpeed = [self ySpeedAtT:t];
CGFloat speed = sqrt(pow(xSpeed, 2) + pow(ySpeed, 2));
return speed;
}
- (CGFloat)xSpeedAtT:(CGFloat)t {
return 2 * (_startPoint.x + _endPoint.x - 2 * _controlPoint.x) * t + 2 * (_controlPoint.x - _startPoint.x);;
}
- (CGFloat)ySpeedAtT:(CGFloat)t {
return 2 * (_startPoint.y + _endPoint.y - 2 * _controlPoint.y) * t + 2 * (_controlPoint.y - _startPoint.y);
}
- (CGFloat)xAtT:(CGFloat)t {
CGFloat x = pow((1-t), 2) * _startPoint.x + 2 * (1-t)* t * _controlPoint.x + pow(t, 2) * _endPoint.x;
return x;
}
- (CGFloat)yAtT:(CGFloat)t {
CGFloat y = pow((1-t), 2) * _startPoint.y + 2 * (1-t) * t * _controlPoint.y + pow(t, 2) * _endPoint.y;
return y;
}
接下里就就開始矯正間隔了
//矯正間隔
- (CGFloat)uniformSpeedAtT:(CGFloat)t {
CGFloat totalLength = [self lengthWithT:1.0];
CGFloat len = t*totalLength;
CGFloat t1=t, t2;
do {
t2 = t1 -([self lengthWithT:t1] - len)/[self speedAtT:t1];
if(fabs(t1-t2)<0.001) break;
t1=t2;
}while(true);
return t2;
}

加上顏色,就是這樣了

接下來就是要去足夠多的點來連成曲線了,這個取點的個數(shù)要根據(jù)具體情況來定,
考慮到線的邊界問題,處理起來太費(fèi)事了,要進(jìn)行更多的色值計算

我的做法是先按線段長度取點,然后再根據(jù)每個點的速度及方向進(jìn)行上下左右偏移,得到一條寬度足夠的線之后在進(jìn)行mask裁剪。


這個取點的方案還不是很完善,先放個demo吧至少效果是出來了,可以看一下具體的樣子。
下面的文章給了我很多的幫助,有需要的同學(xué)可以去看一下
勻速貝塞爾曲線運(yùn)動(續(xù))
一分鐘就懂貝塞爾曲線
Bezier - 勻速貝塞爾曲線運(yùn)動的實現(xiàn)
動態(tài)繪制貝塞爾曲線的在線演示