UIColor *strokeColor = [UIColor blueColor]; [strokeColor set];
動效設計一直是iOS平臺的優點,良好的動效設計能夠很好地提高用戶體驗。而動畫則是動效的基礎支撐。本動畫將從易到難逐步分析,從CABasicAnimation,UIBezierPath,CAShapeLayer三個方面完整的闡述iOS動畫的實現。最終的效果以下:git
1、概念
這個部分你須要瞭解如下概念: CALayer、CAAnimation、CAAnimationGroupgithub
CALayer是個與UIView很相似的概念,一樣有backgroundColor、frame等類似的屬性,咱們能夠將UIView看作一種特殊的CALayer。但實際上UIView是對CALayer封裝,在CALayer的基礎上再添加交互功能。UIView的顯示必須依賴於CALayer。咱們一樣能夠跟新建view同樣新建一個layer,而後添加到某個已有的layer上,一樣能夠對layer調整大小、位置、透明度等。通常來講,layer能夠有兩種用途:一是對view相關屬性的設置,包括圓角、陰影、邊框等參數,更詳細的參數請點擊這裏;二是實現對view的動畫操控。所以對一個view進行動畫,本質上是對該view的.layer進行動畫操縱。緩存
CAAnimation能夠分爲如下幾類:框架
CABasicAnimation基礎動畫,經過設定起始點,終點,時間,動畫會沿着你這設定點進行移動。能夠看作特殊的CAKeyFrameAnimation
CAKeyframeAnimation關鍵幀動畫,可定製度比CABasicAnimation高,也是本系列的接下來的內容
CAAnimationGroup組動畫,支持多個CABasicAnimation或者CAKeyframeAnimation動畫同時執行動畫
使用方法animationWithKeyPath:對 CABasicAnimation進行實例化,並指定Layer的屬性做爲關鍵路徑進行註冊。spa
//圍繞y軸旋轉CABasicAnimation *transformAnima = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];
設定動畫的屬性和說明屬性說明設計
transformAnima.fromValue = @(M_PI_2); transformAnima.toValue = @(M_PI); transformAnima.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; transformAnima.autoreverses = YES; transformAnima.repeatCount = HUGE_VALF; transformAnima.beginTime = CACurrentMediaTime() + 2;
防止動畫結束後回到初始狀態只需設置removedOnCompletion、fillMode兩個屬性就能夠了。3d
解釋:爲何動畫結束後返回原狀態?首先咱們須要搞明白一點的是,layer動畫運行的過程是怎樣的?其實在咱們給一個視圖添加layer動畫時,真正移動並非咱們的視圖自己,而是 presentation layer 的一個緩存。動畫開始時 presentation layer開始移動,原始layer隱藏,動畫結束時,presentation layer從屏幕上移除,原始layer顯示。這就解釋了爲何咱們的視圖在動畫結束後又回到了原來的狀態,由於它根本就沒動過。
這個一樣也能夠解釋爲何在動畫移動過程當中,咱們爲什麼不能對其進行任何操做。
因此在咱們完成layer動畫以後,最好將咱們的layer屬性設置爲咱們最終狀態的屬性,而後將presentation layer 移除掉。
添加動畫代理
[self.imageView.layer addAnimation:transformAnima forKey:@"A"];
fillMode屬性的理解該屬性定義了你的動畫在開始和結束時的動做。默認值是 kCAFillModeRemoved。code
kCAFillModeRemoved 這個是默認值,也就是說當動畫開始前和動畫結束後,動畫對layer都沒有影響,動畫結束後,layer會恢復到以前的狀態
kCAFillModeForwards 當動畫結束後,layer會一直保持着動畫最後的狀態
kCAFillModeBackwards 這個和kCAFillModeForwards是相對的,就是在動畫開始前,你只要將動畫加入了一個layer,layer便當即進入動畫的初始狀態。由於有可能出現fromValue不是目前layer的初始狀態的狀況,若是fromValue就是layer當前的狀態,則這個參數就沒太大意義。
kCAFillModeBoth 理解了上面兩個,這個就很好理解了,這個其實就是上面兩個的合成.動畫加入後開始以前,layer便處於動畫初始狀態,動畫結束後layer保持動畫最後的狀態.
也便是屬性timingFunction值的設定,有種方式來獲取屬性值
(1)使用方法functionWithName:
這種方式很簡單,這裏只是簡單說明一下取值的含義:
kCAMediaTimingFunctionLinear 傳這個值,在整個動畫時間內動畫都是以一個相同的速度來改變。也就是勻速運動。
kCAMediaTimingFunctionEaseIn 使用該值,動畫開始時會較慢,以後動畫會加速。
kCAMediaTimingFunctionEaseOut 使用該值,動畫在開始時會較快,以後動畫速度減慢。
kCAMediaTimingFunctionEaseInEaseOut 使用該值,動畫在開始和結束時速度較慢,中間時間段內速度較快。
CABasicAnimation *positionAnima = [CABasicAnimation animationWithKeyPath:@"position.y"]; positionAnima.fromValue = @(self.imageView.center.y); positionAnima.toValue = @(self.imageView.center.y-30); positionAnima.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn]; CABasicAnimation *transformAnima = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"]; transformAnima.fromValue = @(0); transformAnima.toValue = @(M_PI); transformAnima.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; CAAnimationGroup *animaGroup = [CAAnimationGroup animation]; animaGroup.duration = 2.0f; animaGroup.fillMode = kCAFillModeForwards; animaGroup.removedOnCompletion = NO; animaGroup.animations = @[positionAnima,transformAnima];[self.imageView.layer addAnimation:animaGroup forKey:@"Animation"];
動畫開始和結束時的事件爲了獲取動畫的開始和結束事件,須要實現協議
代理方法實現
其實比較重要的是有多個動畫的時候如何在代理方法中區分不一樣的動畫兩種方式
方式一:
若是咱們添加動畫的視圖是全局變量,可以使用該方法。添加動畫時,咱們使用了
因此,可根據key來區分不一樣的動畫
Note:把動畫存儲爲一個屬性而後再回調中比較,用來斷定是哪一個動畫是不可行的。應爲委託傳入的動畫參數是原始值的一個深拷貝,不是同一個值
方式二
添加動畫的視圖是局部變量時,可以使用該方法添加動畫給動畫設置key-value對
因此,能夠根據key中不一樣的值來進行區分不一樣的動畫
使用UIBezierPath能夠建立基於矢量的路徑,此類是Core Graphics框架關於路徑的封裝。使用此類能夠定義簡單的形狀,如橢圓、矩形或者有多個直線和曲線段組成的形狀等。
UIBezierPath是CGPathRef數據類型的封裝。若是是基於矢量形狀的路徑,都用直線和曲線去建立。咱們使用直線段去建立矩形和多邊形,使用曲線去建立圓弧(arc)、圓或者其餘複雜的曲線形狀。
+ (instancetype)bezierPath;
這個使用比較多,由於這個工廠方法建立的對象,咱們能夠根據咱們的須要任意定製樣式,能夠畫任何咱們想畫的圖形。
這個工廠方法根據一個矩形畫貝塞爾曲線。
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius;
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect byRoundingCorners:(UIRectCorner)corners cornerRadiu:(CGSize)cornerRadiu;
第一個工廠方法是畫矩形,可是這個矩形是能夠畫圓角的。第一個參數是矩形,第二個參數是圓角大小。
第二個工廠方法功能是同樣的,可是能夠指定某一個角畫成圓角。像這種咱們就能夠很容易地給UIView擴展添加圓角的方法了。
這個工廠方法用於畫弧,參數說明以下:
center: 弧線中心點的座標
radius: 弧線所在圓的半徑
startAngle: 弧線開始的角度值
endAngle: 弧線結束的角度值
clockwise: 是否順時針畫弧線
咱們設置畫筆顏色經過set方法:
若是咱們須要設置填充顏色,好比這裏設置爲綠色,那麼咱們須要在設置畫筆顏色以前先設置填充顏色,不然畫筆顏色就被填充顏色替代了。也就是說,若是要讓填充顏色與畫筆顏色不同,那麼咱們的順序必須是先設置填充顏色再設置畫筆顏色。以下,這二者順序不能改變。由於咱們設置填充顏色也是跟設置畫筆顏色同樣調用UIColor的-set方法。
CAShapeLayer是在其座標系統內繪製貝塞爾曲線(UIBezierPath)的。所以,使用CAShapeLayer須要與UIBezierPath一塊兒使用。
它有一個path屬性,而UIBezierPath就是對CGPathRef類型的封裝,所以這二者配合起來使用才能夠的哦!
CAShapeLayer與UIBezierPath的關係:
CAShapeLayer中shape表明形狀的意思,因此須要形狀才能生效
貝塞爾曲線能夠建立基於矢量的路徑,而UIBezierPath類是對CGPathRef的封裝
貝塞爾曲線給CAShapeLayer提供路徑,CAShapeLayer在提供的路徑中進行渲染。路徑會閉環,因此繪製出了Shape
用於CAShapeLayer的貝塞爾曲線做爲path,其path是一個首尾相接的閉環的曲線,即便該貝塞爾曲線不是一個閉環的曲線
- (CAShapeLayer *)drawCircle { CAShapeLayer *circleLayer = [CAShapeLayer layer]; // 指定frame,只是爲了設置寬度和高度 circleLayer.frame = CGRectMake(0, 0, 200, 200); // 設置居中顯示 circleLayer.position = self.view.center; // 設置填充顏色 circleLayer.fillColor = [UIColor clearColor].CGColor; // 設置線寬 circleLayer.lineWidth = 2.0; // 設置線的顏色 circleLayer.strokeColor = [UIColor redColor].CGColor; // 使用UIBezierPath建立路徑 CGRect frame = CGRectMake(0, 0, 200, 200); UIBezierPath *circlePath = [UIBezierPath bezierPathWithOvalInRect:frame]; // 設置CAShapeLayer與UIBezierPath關聯 circleLayer.path = circlePath.CGPath; // 將CAShaperLayer放到某個層上顯示 [self.view.layer addSublayer:circleLayer]; return circleLayer; }