呼哧,終於今天到了最後一篇啦,也是醉了,弄了兩三個月。從最開始計劃只寫三篇就好了,結果自己沒把握好,一點點加成了今天這個樣子。因為增加的內容太多,也差點變成太監文,不過好在沒有放棄自己。所以各位行行好,要是看上去覺得還不錯,就點個贊,打賞小的點兒。這玩意兒寫的我是頭髮亂髮,兩眼通紅。哇哇哇哇~

接下來要寫啥,確實還沒想好。現在的感覺就是胸口的一塊大石頭沒有了,要去盡情的嗨皮!!!!

之前在一個網站上看到了一個HTML5/SVG實現的過山車動畫,點這裏看網頁版。 覺得很棒,想想咱們iOS也完全可以實現,正好還可以全面回顧一下之前分享過的關於iOS中間動畫系列會使用到的各個內容。不過今天的內容稍微有點多,我呢盡量只說最重要的部分,這裏面所有的內容都是通過代碼繪製出來的。

實現后的效果圖:(這也是為了簡書抓圖用的,不知道為啥現在如果是gif,簡書不會當成文章的縮略圖。好心煩~)


Paste_Image.png

完成后的動態圖:


過山車.gif

1. 思路和所用到的內容

1.1 思維導圖


過山車思維導圖.png

1.2 所用到的知識

在這裏,我們使用到了:

  • CALayer、CAShapeLayer、CAGradientLayer三種layer。
  • UIBeizerPath的使用,包括二次貝塞爾曲線、三次貝塞爾曲線的應用,使用BeizerPath繪畫圓。
  • CAKeyframeAnimation的應用。
    所有上面的內容之前的文章裏面都有仔細的寫過怎麼使用噠,要是不清楚的小夥伴們可以翻翻之前的文章。幾乎絕大部分的內容都在iOS動畫系列這個裡面。

1.3 最耗費時間的地方

特別想拿出來說說這個最耗費時間的東東。想都不用想,當然是火車軌道比較麻煩啦。但是這個對我來說還不是花費時間最長的,花費時間最長的居然是那兩座雪山。為了繪畫那兩座雪山,還有山上面的積雪簡直是費老鼻子勁了。所以火車軌道、雪山俺會單獨拿出兩小節來說說這個令人頭疼的玩意。

2. 輔助元素的創建(背景顏色、草坪、大地、小樹、雲彩)

輔助元素完成后的效果圖:


Paste_Image.png

2.1 漸變的天空背景

使用CAGradientLayer進行設置,就是一個最基本的應用,讓成45度角進行變換。
受篇幅限制,代碼我就不貼了,在源代碼裏面自己看吧。註釋寫的還算比較詳細啦,自我感覺。哈哈~
CAGradientLayer的基礎部分可以看看這個文章,第九篇:iOS動畫系列之九:實現點贊的動畫及播放起伏指示器

2.2 草坪

主要是使用兩個二次貝塞爾曲線實現的。

    [leftLawnPath moveToPoint:leftStartPoint];
    [leftLawnPath addLineToPoint:CGPointMake(0, k_SIZE.height - 100)];

    //    畫一個二次貝塞爾曲線
    [leftLawnPath addQuadCurveToPoint:CGPointMake(k_SIZE.width / 3.0, k_LAND_BEGIN_HEIGHT) controlPoint:CGPointMake(k_SIZE.width / 5.0, k_SIZE.height - 100)];

    leftLawn.path = leftLawnPath.CGPath;
    leftLawn.fillColor = [UIColor colorWithDisplayP3Red:82.0 / 255.0 green:177.0 / 255.0 blue:52.0 / 255.0 alpha:1.0].CGColor;
    [self.view.layer addSublayer:leftLawn];

    CAShapeLayer *rightLawn = [[CAShapeLayer alloc] init];
    UIBezierPath *rightLawnPath = [[UIBezierPath alloc] init];

二次貝塞爾曲線

-(void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;

二次貝塞爾曲線.png

2.3 雲彩動畫的實現

雲彩的漂浮就是通過CAKeyframeAnimation,讓其沿着繪畫的直線曲線進行運動。

    CALayer *cloud = [[CALayer alloc]init];
    cloud.contents = (__bridge id _Nullable)([UIImage imageNamed:@"cloud"].CGImage);
    cloud.frame = CGRectMake(0, 0, 63, 20);
    [self.view.layer addSublayer:cloud];

    UIBezierPath *cloudPath = [[UIBezierPath alloc] init];
    [cloudPath moveToPoint:CGPointMake(k_SIZE.width + 63, 50)];
    [cloudPath addLineToPoint:CGPointMake(-63, 50)];

    CAKeyframeAnimation *ani = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    ani.path = cloudPath.CGPath;
    ani.duration = 30;
    ani.autoreverses = NO;
    ani.repeatCount = CGFLOAT_MAX;
    ani.calculationMode = kCAAnimationPaced;

    [cloud addAnimation:ani forKey:@"position"];

2.4 大地、小樹的實現

就是分別創建了大地和小樹的CALayer,為了使用不同的方法,大地我們通過backgroundColor填充了圖片。小樹的Layer,我們通過設置contents進行了圖片填充。

//大地的背景填充
    _landLayer.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ground"]].CGColor;
//小樹的背景設置
    treeLayer.contents = (__bridge id _Nullable)(tree.CGImage);

為了能夠層次不齊的放置小樹,所以用了幾個循環,在不同的y軸位置,添加了若干個小樹。

3. 雪山的實現

雪山的實現其實並不是特別難,主要是很繁瑣。自己還忘記了一個初中的小公式,在這個地方耽誤了點時間。

3.1 雪山的思路

以一座雪山為例子,乍一看,以為雪山分成了兩部分:雪山下半部分+山頂的雪。很快的,自己就放棄了這個思路。這樣的話,中間的曲線部分畫起來簡直就要了人命了。所以就換了一個思路:先畫一個全部被雪覆蓋滿的山體,然後在這個之上再畫一個有棕色土地的部分。

完成后是這個樣子的:


雪山.png

3.2 雪山繪畫的步驟

STEP ONE:


覆蓋滿白雪的雪山.png

STEP TWO:


給雪山添加棕色山體.png

STEP THREE:


第二坐被白雪覆蓋的雪山.png

STEP FOUR:


雪山.png

3.3 需要注意的點

在畫山的過程中,最複雜的是找到山上左右兩側山坡上邊緣的那個點的CGPoint。
以第一座山左邊上坡上開始有雪的那個點來說。其實要計算的是從山腳到山頂兩點之間的連線上任意一點的坐標。知道了X軸坐標,要計算Y軸坐標。
這個就是咱們初中學到的計算公式,y = kx + b。 k是斜率,b是截距。起點、終點已經知道了,可以很容易的計算出斜率k。根據k,再計算出b。這樣給出這條線段上任意一點x軸坐標,就能輕易的算出y軸坐標了。xy都知道了,CGPoint不就知道了嘛。

- (CGPoint)calculateWithXValue:(CGFloat)xvalue startPoint:(CGPoint)startPoint endpoint:(CGPoint)endpoint{
    //    求出兩點連線的斜率
    CGFloat k = (endpoint.y - startPoint.y) / (endpoint.x - startPoint.x);
    CGFloat b = startPoint.y - startPoint.x * k;
    CGFloat yvalue = k * xvalue + b;
    return CGPointMake(xvalue, yvalue);
}

3.4 以左邊山為例

    //    左邊第一座山頂,其實就是一個白色的三角形
    CAShapeLayer *leftSnowberg = [[CAShapeLayer alloc] init];
    UIBezierPath *leftSnowbergPath = [[UIBezierPath alloc] init];

    //    把bezierpath的起點移動到雪山左下角
    [leftSnowbergPath moveToPoint:CGPointMake(0, k_SIZE.height - 120)];

    //    畫一條線到山頂
    [leftSnowbergPath addLineToPoint:CGPointMake(100, 100)];

    //    畫一條線到右下角->左下角->閉合
    [leftSnowbergPath addLineToPoint:CGPointMake(k_SIZE.width / 2, k_LAND_BEGIN_HEIGHT)];
    [leftSnowbergPath addLineToPoint:CGPointMake(0, k_LAND_BEGIN_HEIGHT)];
    [leftSnowbergPath closePath];

    leftSnowberg.path = leftSnowbergPath.CGPath;
    leftSnowberg.fillColor = [UIColor whiteColor].CGColor;
    [self.view.layer addSublayer:leftSnowberg];


    //    開始畫山體沒有被雪覆蓋的部分
    CAShapeLayer *leftSnowbergBody = [[CAShapeLayer alloc] init];
    UIBezierPath *leftSnowbergBodyPath = [[UIBezierPath alloc] init];

    //    把bezierpath的起點移動到雪山左下角相同的位置
    CGPoint startPoint = CGPointMake(0, k_SIZE.height - 120);
    CGPoint endPoint = CGPointMake(100, 100);
    CGPoint firstPathPoint = [self calculateWithXValue:20 startPoint:startPoint endpoint:endPoint];
    [leftSnowbergBodyPath moveToPoint:startPoint];

    [leftSnowbergBodyPath addLineToPoint:firstPathPoint];
    [leftSnowbergBodyPath addLineToPoint:CGPointMake(60, firstPathPoint.y)];
    [leftSnowbergBodyPath addLineToPoint:CGPointMake(100, firstPathPoint.y + 30)];
    [leftSnowbergBodyPath addLineToPoint:CGPointMake(140, firstPathPoint.y)];
    [leftSnowbergBodyPath addLineToPoint:CGPointMake(180, firstPathPoint.y - 20)];

    CGPoint secondPathPoint = [self calculateWithXValue:(k_SIZE.width / 2 - 125) startPoint:endPoint endpoint:CGPointMake(k_SIZE.width / 2, k_LAND_BEGIN_HEIGHT)];
    [leftSnowbergBodyPath addLineToPoint:CGPointMake(secondPathPoint.x - 30, firstPathPoint.y)];

    [leftSnowbergBodyPath addLineToPoint:secondPathPoint];

    [leftSnowbergBodyPath addLineToPoint:CGPointMake(k_SIZE.width / 2, k_LAND_BEGIN_HEIGHT)];
    [leftSnowbergBodyPath addLineToPoint:CGPointMake(0, k_LAND_BEGIN_HEIGHT)];
    [leftSnowbergBodyPath closePath];

    leftSnowbergBody.path = leftSnowbergBodyPath.CGPath;
    UIColor *snowColor = [UIColor colorWithDisplayP3Red:139.0 /255.0 green:92.0 /255.0 blue:0.0 /255.0 alpha:1.0];
    leftSnowbergBody.fillColor = snowColor.CGColor;
    [self.view.layer addSublayer:leftSnowbergBody];

4. 軌道的實現


Paste_Image.png

軌道這部分主要就是花了幾個二次貝塞爾曲線,三次貝塞爾曲線。那我們用最複雜的綠色這個帶圓圈的軌道來分享一下。它是由三部分組成的,考慮到在最後我們會讓過山車從右邊進入,跑到左邊去,我們就從最右側開始畫起。
最右側有一個二次貝塞爾曲線,中間畫了一個圓圈,左邊是一個三次貝塞爾曲線。畫完了之後,使用圖片進行填充就完成了90%的工作。
為了讓軌道看起來更好看一些,對軌道的邊緣進行鏤空,內部填充色變成透明。

4.1 繪畫的步驟

1,先畫最右邊的弧線,一個二次貝塞爾曲線。


Paste_Image.png

2,畫一個圓圈。注意控制圓的半徑以及圓心的位置。


Paste_Image.png

3,畫最左邊的那條曲線,一個三次貝塞爾曲線。其實就是有兩個控制點的曲線。


Paste_Image.png

4,將曲線進行閉合。


Paste_Image.png

5,把曲線的背景顏色填充為準備好的小格子。


Paste_Image.png

6,為了讓軌道看起來更加逼真,讓曲線的邊緣變成虛線。


Paste_Image.png

4.2 三次貝塞爾曲線

- (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2;

起點用moveToPoint的方法進行設定,endPoint:貝塞爾曲線的終點;controlPoint1:控制點1;controlPoint2:控制點2。

曲線是由起點趨向控制點1,之後趨向控制點2,最後到達終點的曲線。


Paste_Image.png

4.3 代碼實現

綠色軌道繪製部分的代碼:

    //    綠色鐵軌的火車從右側進入,所以從右側開始繪畫。需要畫三條曲線,右邊一條+中間的圓圈+左邊一條
    UIBezierPath *path = [[UIBezierPath alloc] init];
    [path moveToPoint:CGPointMake(k_SIZE.width + 10, k_LAND_BEGIN_HEIGHT)];
    [path addLineToPoint:CGPointMake(k_SIZE.width + 10, k_SIZE.height - 70)];
    [path addQuadCurveToPoint:CGPointMake(k_SIZE.width / 1.5, k_SIZE.height - 70) controlPoint:CGPointMake(k_SIZE.width - 150, 200)];

    //    畫圓圈
    [path addArcWithCenter:CGPointMake(k_SIZE.width / 1.6, k_SIZE.height - 140) radius:70 startAngle:M_PI_2 endAngle:2.5 * M_PI clockwise:YES];

    [path addCurveToPoint:CGPointMake(0, k_SIZE.height - 100) controlPoint1:CGPointMake(k_SIZE.width / 1.8 - 60, k_SIZE.height - 60) controlPoint2:CGPointMake(150, k_SIZE.height / 2.3)];

    [path addLineToPoint:CGPointMake(- 10, k_LAND_BEGIN_HEIGHT)];

    _greenTrack.path = path.CGPath;
    _greenTrack.fillColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"green"]].CGColor;

軌道邊緣鏤空的代碼:

    //    為了能夠讓弧線更好看一點,需要加入鏤空的虛線
    CAShapeLayer *trackLine = [[CAShapeLayer alloc] init];
    trackLine.lineCap = kCALineCapRound;
    trackLine.strokeColor = [UIColor whiteColor].CGColor;

    trackLine.lineDashPattern = @[@1.0,@6.0];
    trackLine.lineWidth = 2.5;
    trackLine.fillColor = [UIColor clearColor].CGColor;
    trackLine.path = path.CGPath;
    [_greenTrack addSublayer:trackLine];

———————–寫在最後————————————————-
如果看官你覺得確實這篇文章有點收穫,那就給個贊或者粉一下唄。再進一步,如果您認可小的這點兒辛苦,打賞點可樂錢,小的願意給爺笑一個^&^

OC版本的源代碼在這裏:http://git.oschina.net/atypical/rollercoaster
群眾要是呼聲比較高,那就還是老慣例,隨後再寫swift版本的。要是沒啥人有興趣,俺就拿着大家大賞的銀子去買點小片兒看看,樂呵樂呵。

———————–華麗分割線,iOS動畫系列全集鏈接————————————————-
第一篇:iOS動畫系列之一:通過實戰學習CALayer和透視的原理。做一個帶時分秒指針的時鐘動畫(上)
第二篇:iOS動畫系列之二:通過實戰學習CALayer和透視的原理。做一個帶時分秒指針的時鐘動畫。包含了OC和Swift兩種源代碼(下)
第三篇:iOS動畫系列之三:Core Animation。介紹了Core Animation的常用屬性和方法。
第四篇:CABasic Animation。iOS動畫系列之四:基礎動畫之平移篇
第五篇:CABasic Animation。iOS動畫系列之五:基礎動畫之縮放篇&旋轉篇
第六篇:iOS動畫系列之六:利用CABasic Animation完成帶動畫特效的登錄界面
第七篇:iOS動畫系列之七:實現類似Twitter的啟動動畫
第八篇:iOS動畫系列之八:使用CAShapeLayer繪畫動態流量圖
第九篇:iOS動畫系列之九:實現點贊的動畫及播放起伏指示器
第十篇:實戰系列:繪製過山車場景


分享