A GUIDE TO IOS ANIMATION 2.0筆記(一) - 玩轉(zhuǎn)貝塞爾曲線

玩轉(zhuǎn)貝塞爾曲線

  • 歷史:由法國(guó)雷諾汽車的工程師皮諾爾·貝塞爾發(fā)明,應(yīng)用于雷諾汽車設(shè)計(jì)
  • 原理鋪墊:給定 n+1個(gè)數(shù)據(jù)點(diǎn),生產(chǎn)一條曲線,使得該曲線與這些點(diǎn)所連接的折線相近。

第一個(gè)demo

粘性小球.gif
  • 代碼

  • 思路:一個(gè)小球用四條貝塞爾曲線平分拼成,鏈接完成之后向內(nèi)填充顏色。然后,再單獨(dú)控制每條貝塞爾曲線的形狀,實(shí)時(shí)調(diào)用layer[self setNeedsDisplay]以重繪- (void)drawInContext:(CGContextRef)ctx方法。其中每條弧線都有兩個(gè)控制點(diǎn)(學(xué)過(guò)的應(yīng)該有印象)。

  • 為了方便傳達(dá)理念,已以下形式展示這一思路。

未命名2.gif
Screen-Shot-2015-06-13-at-15-45-13.png

小球是由弧AB、弧BC、弧CD、弧DA 四段組成,其中每段弧都綁定兩個(gè)控制點(diǎn):弧AB 綁定的是 C1 、 C2;弧BC 綁定的是 C3 、 C4...

問(wèn)題:這些點(diǎn)應(yīng)該以什么樣的規(guī)律運(yùn)動(dòng)?

  • 為了方便計(jì)算各個(gè)點(diǎn)的坐標(biāo),引入外接矩形(圖中虛線)

首先計(jì)算出這個(gè)外接矩形的位置:根據(jù)中心點(diǎn)的 (x,y) 分別減去矩形(寬,高)1/2 獲得。

//outsideRectSize 是外接矩形邊長(zhǎng)
// 外接矩形 x
CGFloat origin_x = self.position.x - outsideRectSize/2 + (pro
gress - 0.5) * (self.frame.size.width - outsideRectSize);
// 外接矩形 y
CGFloat origin_y = self.position.y - outsideRectSize/2;
// 設(shè)置外接矩形的frame
self.outsideRect = CGRectMake(origin_x, origin_y, outsideRect
Size, outsideRectSize);
  • 個(gè)人理解:代碼里的progress代表著上面視圖中滑塊0~1的值。計(jì)算外接矩形 X值時(shí),...+ (pro gress - 0.5) * (self.frame.size.width - outsideRectSize)這個(gè)部分代碼,代表著矩形在向左右移動(dòng)的過(guò)程中,向左移B點(diǎn),向右移D點(diǎn),這兩點(diǎn)在移動(dòng)過(guò)程中的緩沖區(qū)。

其次還需要判斷當(dāng)前是向左移還是右移,左移的時(shí)候B動(dòng)D不動(dòng);右移的時(shí)候D動(dòng)B不動(dòng)。(動(dòng):指的是需不需要有緩沖區(qū))

//只要外接矩形在左側(cè),則改變B點(diǎn);在右邊,改變D點(diǎn)
  if (progress <= 0.5) {
       self.movePoint = POINT_B;//用枚舉代表 B, D 點(diǎn)
       NSLog(@"B點(diǎn)動(dòng)");
   }else{
       self.movePoint = POINT_D;
       NSLog(@"D點(diǎn)動(dòng)");
   }

有了矩形的位置,接下來(lái)計(jì)算關(guān)鍵點(diǎn)的坐標(biāo)

- (void)drawInContext:(CGContextRef)ctx{
   //A-C1、B-C2... 的距離,當(dāng)設(shè)置為正方形邊長(zhǎng)的1/3.6倍時(shí),畫出來(lái)的圓弧完美貼合圓形
   CGFloat offset = self.outsideRect.size.width / 3.6;
   //A.B.C.D實(shí)際需要移動(dòng)的距離.系數(shù)為滑塊偏離中點(diǎn)0.5的絕對(duì)值再乘以2.當(dāng)滑到兩端的時(shí)候,movedDistance為最大值:「外接矩形寬度的1/5」.
   CGFloat movedDistance = (self.outsideRect.size.width * 1 / 6) * fabs(self.progress-0.5)*2;
   //方便下方計(jì)算各點(diǎn)坐標(biāo),先算出外接矩形的中心點(diǎn)坐標(biāo)
   CGPoint rectCenter = CGPointMake(self.outsideRect.origin.x + self.outsideRect.size.width/2 , self.outsideRect.origin.y + self.outsideRect.size.height/2);
   CGPoint pointA = CGPointMake(rectCenter.x ,self.outsideRect.origin.y + movedDistance);
   CGPoint pointB = CGPointMake(self.movePoint == POINT_D ? rectCenter.x + self.outsideRect.size.width/2 : rectCenter.x + self.outsideRect.size.width/2 + movedDistance*2 ,rectCenter.y);
   CGPoint pointC = CGPointMake(rectCenter.x ,rectCenter.y + self.outsideRect.size.height/2 - movedDistance);
   CGPoint pointD = CGPointMake(self.movePoint == POINT_D ? self.outsideRect.origin.x - movedDistance*2 : self.outsideRect.origin.x, rectCenter.y);
   CGPoint c1 = CGPointMake(pointA.x + offset, pointA.y);
   CGPoint c2 = CGPointMake(pointB.x, self.movePoint == POINT_D ? pointB.y - offset : pointB.y - offset + movedDistance);
    CGPoint c3 = CGPointMake(pointB.x, self.movePoint == POINT_D ? pointB.y + offset : pointB.y + offset - movedDistance);
   CGPoint c4 = CGPointMake(pointC.x + offset, pointC.y);
   CGPoint c5 = CGPointMake(pointC.x - offset, pointC.y);
  CGPoint c6 = CGPointMake(pointD.x, self.movePoint == POINT_D ? pointD.y + offset - movedDistance : pointD.y + offset);
    CGPoint c7 = CGPointMake(pointD.x, self.movePoint == POINT_D ? pointD.y - offset + movedDistance : pointD.y - offset);
  CGPoint c8 = CGPointMake(pointA.x - offset, pointA.y);
  //外接虛線矩形
   UIBezierPath *rectPath = [UIBezierPath bezierPathWithRect:self.outsideRect];
   CGContextAddPath(ctx, rectPath.CGPath);
  CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor);
   CGContextSetLineWidth(ctx, 1.0);
   CGFloat dash[] = {5.0, 5.0};
   CGContextSetLineDash(ctx, 0.0, dash, 2); //1
   CGContextStrokePath(ctx); //給線條填充顏色
   //圓的邊界
   UIBezierPath* ovalPath = [UIBezierPath bezierPath];
   [ovalPath moveToPoint: pointA];
   [ovalPath addCurveToPoint:pointB controlPoint1:c1 controlPoint2:c2];
   [ovalPath addCurveToPoint:pointC controlPoint1:c3 controlPoint2:c4];
   [ovalPath addCurveToPoint:pointD controlPoint1:c5 controlPoint2:c6];
   [ovalPath addCurveToPoint:pointA controlPoint1:c7 controlPoint2:c8];
   [ovalPath closePath];
    CGContextAddPath(ctx, ovalPath.CGPath);
  CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor);
   CGContextSetFillColorWithColor(ctx, [UIColor redColor].CGColor);
   CGContextSetLineDash(ctx, 0, NULL, 0); //2
   CGContextDrawPath(ctx, kCGPathFillStroke); //同時(shí)給線條和線條包圍的內(nèi)部區(qū)域填充顏色
   //--------------  注:以下代碼均為輔助觀察  -------------------
  //標(biāo)記出每個(gè)點(diǎn)并連線,方便觀察,給所有關(guān)鍵點(diǎn)染色 -- 白色,輔助線顏色 -- 白色
   //語(yǔ)法糖:字典@{},數(shù)組@[],基本數(shù)據(jù)類型封裝成對(duì)象@234,@12.0,@YES,@(234+12.0)
   CGContextSetFillColorWithColor(ctx, [UIColor yellowColor].CGColor);
   CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor);
   NSArray *points = @[[NSValue valueWithCGPoint:pointA],> >[NSValue valueWithCGPoint:pointB],[NSValue valueWithCGPoint:pointC],[NSValue valueWithCGPoint:pointD],[NSValue valueWithCGPoint:c1],[NSValue valueWithCGPoint:c2],[NSValue valueWithCGPoint:c3],[NSValue valueWithCGPoint:c4],[NSValue valueWithCGPoint:c5],[NSValue valueWithCGPoint:c6],[NSValue valueWithCGPoint:c7],[NSValue valueWithCGPoint:c8]];
[self drawPoint:points withContext:ctx];

//連接輔助線
UIBezierPath *helperline = [UIBezierPath bezierPath];
[helperline moveToPoint:pointA];
[helperline addLineToPoint:c1];
[helperline addLineToPoint:c2];
[helperline addLineToPoint:pointB];
[helperline addLineToPoint:c3];
[helperline addLineToPoint:c4];
[helperline addLineToPoint:pointC];
[helperline addLineToPoint:c5];
[helperline addLineToPoint:c6];
[helperline addLineToPoint:pointD];
[helperline addLineToPoint:c7];
[helperline addLineToPoint:c8];
[helperline closePath];

CGContextAddPath(ctx, helperline.CGPath);
CGFloat dash2[] = {2.0, 2.0};
CGContextSetLineDash(ctx, 0.0, dash2, 2);
CGContextStrokePath(ctx); //給輔助線條填充顏色

}

//在某個(gè)point位置畫一個(gè)點(diǎn),方便觀察運(yùn)動(dòng)情況

  • (void)drawPoint:(NSArray *)points withContext:(CGContextRef)ctx{
    for (NSValue *pointValue in points) {
    CGPoint point = [pointValue CGPointValue];
    CGContextFillRect(ctx, CGRectMake(point.x - 2,point.y - 2,4,4));
    }
    }
代碼中`ctx`字面意思是上下文,你可以理解為一塊全局的畫布。也就是說(shuō),一旦在某個(gè)地方改了畫布的一些屬性,其他任何使用畫布的屬性的時(shí)候都是改了之后的。比如上面在 `//1` 中把線條樣式改成了虛線,那么在下文 `//2` 中如果不恢復(fù)成連續(xù)的直線,那么畫出來(lái)的依然是`//1`中的虛線樣式。

>* **個(gè)人理解:**
>
>* **結(jié)合上面后兩張圖片分析**:假設(shè)向左移動(dòng),發(fā)現(xiàn)外接矩形以相同的速度向左移動(dòng),隨著移動(dòng),發(fā)現(xiàn)`c7,c6`兩控制點(diǎn)在`y`軸并不會(huì)改變;`c8,c5`在`x`軸不變的情況下,`y`軸向內(nèi)移動(dòng),`c1,c4`控制點(diǎn)與其保持平行,同時(shí)`x`軸相對(duì)不變;`c2,c3`以相同的比例向內(nèi)靠近。**猜測(cè)**該向內(nèi)靠近的多少與緩沖區(qū)有關(guān)連。向右移動(dòng)同理。

相關(guān)閱讀

注意:該筆記內(nèi)容暫且待定,我覺得有必要加深一下內(nèi)功,務(wù)實(shí)基礎(chǔ)。先讀讀《iOS核心動(dòng)畫高級(jí)技巧》??。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • AnimationCircle 用四條曲線拼接出小球的形狀,逆時(shí)針開始,分別是弧AB,弧BC,弧CD,弧DA,每條...
    JoyceZhao閱讀 812評(píng)論 0 8
  • 前言 前段時(shí)間看到一個(gè)挺有趣的動(dòng)畫效果,于是就想著實(shí)現(xiàn)一下,順便練下手,這里是第一篇教程,介紹了啟動(dòng)動(dòng)畫的制作,示...
    Arthury閱讀 734評(píng)論 5 11
  • #define kBlackColor [UIColor blackColor] //.h //劃線 + (voi...
    CHADHEA閱讀 859評(píng)論 0 1
  • 在iOS中隨處都可以看到絢麗的動(dòng)畫效果,實(shí)現(xiàn)這些動(dòng)畫的過(guò)程并不復(fù)雜,今天將帶大家一窺iOS動(dòng)畫全貌。在這里你可以看...
    F麥子閱讀 5,270評(píng)論 5 13
  • 《五月》 粽葉飄香 汨羅江畔的那縷濕漉漉的詩(shī)魂 又撩動(dòng)多少文人雅士的心 一行行是祭奠 一句句是敬仰 兩千年前的愛國(guó)...
    瓶水之冰閱讀 234評(píng)論 3 2

友情鏈接更多精彩內(nèi)容