你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> PreLoader的實現講解

PreLoader的實現講解

編輯:IOS開發基礎

PreLoaderDisplayWhite.gif

本文為投稿文章,作者:Zhiyi(Github)

PreLoader是由Volodymyr Kurbatov設計的一個很有意思的HUD,通過運動污點和固定污點之間的粘黏動畫吸引用戶的眼球跟蹤,能有效分散等待注意力。

這篇文章簡單剖析本人使用OC實現PreLoader的原理思路和做法。

噴出來的油污

根據這個Loading動畫的粘黏特征,我把它裡面這些有顏色的物體比作油污,觀察這個動畫發現,可將它分成兩個整體,左右兩邊兩個固定的油污,還有移動中的三個小油污點,左右兩個固定的油污輪流向對方噴射油污,雙方都會因為吸收油污而變大,噴射油污而變小。

首先我們從左右循環移動的污點著手,因為路徑不是平滑一步到位,我這裡選擇使用CAKeyframeAnimation關鍵幀動畫,先做出在左右固定點間來回運動的污點。

//moving Spot
for (int i = 0; i < 3; i++) {
    Spot *movingSpot = [[Spot alloc] initWithFrame:CGRectMake(originX - UNIT_RADIUS, self.bounds.size.height / 2 - UNIT_RADIUS , 2 * UNIT_RADIUS, 2 * UNIT_RADIUS) color:spotColor];
    //1
    CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:@"position.x"];
    anim.values = @[@(originX), @(originX), @(finalX), @(finalX), @(originX), @(originX)];
    anim.keyTimes = @[@(0.0), @(0.25), @(0.35), @(0.75), @(0.85), @(1.0)];//sleep 0.4 ratio
    anim.duration = PROCESS_DURING;
    anim.repeatCount = HUGE_VALF;
    anim.beginTime = CACurrentMediaTime() + i * SPOT_DELAY_RATIO * PROCESS_DURING;
    [movingSpot.layer addAnimation:anim forKey:@"movingAnim"];
    [self addSubview:movingSpot];
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    [CATransaction commit];
}

我把多余的代碼刪除掉了,主要我們來看//1處,先拿一個污點來做參考,我們通過CAKeyFrameAnimation控制污點layer的position.x變化,形成動畫。現在我們只需要兩個控制點(originX, finalX),路徑可以解析為:“休息,左邊出發點,到右邊結束點,休息,然後回到左邊出發點”,這個動作鏈為一個循環。看看我們的keyTimes和values為什麼是這些數字?這裡我們只需要保證兩點:

  • 污點在左固定點 和 右固定點休息的時間相同

  • 污點從左往右 和 從右往左移動的時間相同

這裡我規定了污點休息時間為0.4個單位,因此移動時間就是(1-2*0.4)/2 = 0.1個單位。這裡有兩個問題:

  • 為什麼從0.25個單位開始移動而不是0.0呢?可能我只是想為後面保留靈活性吧,這個其實沒多大關系。

  • 為什麼values前面有兩個重復的originX,後面又有兩個重復的finalX?這個是必須的,雖然CAKeyframeAnim的value不需要從0.0開始1.0結束也可以實現相同的動畫效果,但是如果沒有了這兩個極點,通過presentationLayer取動畫實時位置時會出現超出邊界不准確的負數,後面會再提到這個問題。

有些朋友可能會沒有弄明白這個Animation Path對應的污點休息時間在哪裡,這裡再強化下,0.35-0.75在右邊休息,0.85-0.25在左邊休息,盯著我代碼看,會看懂的。

ok,我們已經理解了一個污點的動畫路徑,那麼我們用一個for循環,很簡單就可以做出三個,一個跟一個出發的污點動畫路徑,保證了前面的基礎以後,這裡只需要保證三個路徑的動畫一個循環的持續時間duration相同,然後我們使用一個延遲系數SPOT_DELAY_RATIO = 0.08f,控制3個動畫分別的beginTime,實現了一個跟一個的效果。

movingSport.gif

吸收油污,噴射油污

接下來我們做兩個固定污點的變大變小動畫,當然不可以用碰撞檢測來做,那樣不好控制而且受大小影響,會有很多不必要的代碼。思路是在特定的時間做特定大小變化,既然我們已經定義了污點移動和休息的關鍵幀keyframe,為什麼不繼續用上他們呢?看固定油污的動畫代碼:

//Fixed Spot
Spot *leftFixedSpot = [[Spot alloc] initWithFrame:CGRectMake(originX - UNIT_RADIUS, self.bounds.size.height / 2 - UNIT_RADIUS, 2 * UNIT_RADIUS, 2 * UNIT_RADIUS) color:spotColor];
Spot *rightFixedSpot = [[Spot alloc] initWithFrame:CGRectMake(self.bounds.size.width - margin - UNIT_RADIUS, self.bounds.size.height / 2 - UNIT_RADIUS, 2 * UNIT_RADIUS, 2 * UNIT_RADIUS) color:spotColor];
NSValue *firstVal = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0f, 1.0f, 0)];
NSValue *secondVal = [NSValue valueWithCATransform3D:CATransform3DMakeScale(2.0f, 2.0f, 0)];
NSValue *thirdVal = [NSValue valueWithCATransform3D:CATransform3DMakeScale(3.0f, 3.0f, 0)];
NSValue *fourthVal = [NSValue valueWithCATransform3D:CATransform3DMakeScale(4.0f, 4.0f, 0)];
//發射點,先調至最大
leftFixedSpot.layer.transform = CATransform3DMakeScale(4.0f, 4.0f, 0);
//left
CAKeyframeAnimation *leftFixedSpotAnim = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
leftFixedSpotAnim.values = @[thirdVal,  thirdVal, fourthVal,   fourthVal, thirdVal,   thirdVal, secondVal,   secondVal, firstVal,
firstVal, secondVal,   secondVal, thirdVal,   thirdVal];
leftFixedSpotAnim.keyTimes = @[@(0.0),  @(0.01), @(0.01),    @(0.25), @(0.25),     @(0.33), @(0.33),     @(0.41), @(0.41),//sleep
@(0.85), @(0.85),    @(0.93), @(0.93),     @(1.00)];//SPOT_DELAY_RATIO = 0.08
leftFixedSpotAnim.duration = PROCESS_DURING;
leftFixedSpotAnim.repeatCount = HUGE_VALF;
[leftFixedSpot.layer addAnimation:leftFixedSpotAnim forKey:@"fixedSpotScaleAnim"];
//right
CAKeyframeAnimation *rightFixedSpotAnim = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
rightFixedSpotAnim.values = @[firstVal, firstVal, secondVal,     secondVal, thirdVal,     thirdVal, fourthVal,
fourthVal, thirdVal,     thirdVal, secondVal,     secondVal,  firstVal, firstVal];
rightFixedSpotAnim.keyTimes = @[@(0.0),  @(0.25), @(0.25),     @(0.33), @(0.33),     @(0.41), @(0.41),//sleep
@(0.75), @(0.75),     @(0.83), @(0.83),     @(0.91), @(0.91),  @(1.0)];//SPOT_DELAY_RATIO = 0.08
rightFixedSpotAnim.duration = PROCESS_DURING;
rightFixedSpotAnim.repeatCount = HUGE_VALF;
//0.1 ratio needed that the spot from left to right
rightFixedSpotAnim.beginTime = CACurrentMediaTime() + PROCESS_DURING * 0.1;
[rightFixedSpot.layer addAnimation:rightFixedSpotAnim forKey:@"fixedSpotScaleAnim"];
[self addSubview:leftFixedSpot];
[self addSubview:rightFixedSpot];

我們先定義好4種Scale對應的污點value(firstVal, secondVal, thirdVal, fourthVal), 使用keyframeAnimation控制這4種value的變化,實現左右固定污點在指定時間點的變大變小。

這裡需要注意什麼:

  • 跟移動的污點保持一致,也是從0.25開始活動

  • 還記得移動污點一個跟一個出發的延遲系數0.08f嗎?它就是固定污點變大變小的間隔時間單位

  • 因為移動污點從一邊移動到另一邊用的時間單位為0.1,所以右固定點的變大動畫開始時間要比左固定點晚0.1,也就是rightFixedSpotAnim.beginTime = CACurrentMediaTime() + PROCESS_DURING * 0.1;

  • 移動污點休息時間在固定點關鍵幀上用不著,因為固定點在最後一個移動污點離開後變成最小,而在第一個移動污點來臨時就要開始變大

  • 因為右固定點的動畫相對於左固定點延遲了0.1開始,所以仍然是從0.25關鍵幀開始活動(變大)

這裡舉例講解下這一part關鍵幀的計算依據:

看左固定點的代碼//left,從0.25開始活動,每過一個延遲系數0.08,會有一個污點觸發(噴出),做一次變小動畫,當第三次變小動畫做完,我們並不知道中間休息時間有多長,這時就要用到0.85這個關鍵幀時間(從移動污點代碼可看出,第一個污點在0.85時回到左固定點),於是從0.85開始做變大動畫,也是延遲系數0.08,最後會操作1.0,沒關系影響不大,最後一個變大動畫在0.01做就好了!

那右固定點的0.75關鍵幀是怎樣算的呢?0.41是最後一次變大(最後一個污點被吸收), 那麼距離第一個污點被噴出的時間間隔就是0.41+移動污點休息時間0.4-兩個延遲系數0.08*2,因為,最後一個污點被吸收時,第一個來的污點已經休息了兩個延遲系數時間了。

scaleTransform.gif

緩動脹大

之前scale的KeyframeAnim變化都是突然的,一個scale跳去另外一個scale,看看局部代碼會議下:

firstVal, secondVal,   secondVal, thirdVal
@(0.25), @(0.25),      @(0.33), @(0.33)

可以看出一個Val變化到另一個Val是發生在同一個關鍵幀,沒有做過度效果。這裡為了修改方便,我們只需要引入一個比較短的動畫時間間隔CGFloat ti = SPOT_MAGNIFY_ANIM_DURATION_RATIO = 0.03f即可:

CGFloat ti = SPOT_MAGNIFY_ANIM_DURATION_RATIO;
leftFixedSpotAnim.keyTimes = @[@(0.0),  @(0.01), @(0.01+ti),    @(0.25), @(0.25+ti),     @(0.33), @(0.33+ti),     @(0.41), @(0.41+ti),//sleep
@(0.85), @(0.85+ti),    @(0.93), @(0.93+ti),     @(1.00)];//SPOT_DEL

expansiongif.gif

粘黏動畫

這裡我使用presentationLayer獲取動畫layer的實時frame信息,然後准備幾個工具函數:

  • centerDistanceWithPoint:another:計算圓心距CD

  • faceDistanceWithCircleLayer:another:計算表面距離FD

  • circleIncirclingWithBigOne:smallOne:判斷兩圓是否包含關系

粘連動畫.jpg

我使用了CAShapeLayer和UIBezierPath來做這個粘連效果,通過控制CAShapeLayer的顏色控制粘連的顯示和消失,而顯示/消失的依據就是兩圓的表面距離FD。這裡再次強調一遍KeyframeAnim的keyTimes一定要從0.0開始,1.0結束,否則獲取layer實時frame時會有錯誤數據干擾。

那麼“是否包含”用來干什麼呢?我們有3個污點,一個粘連效果ShapeLayer,當第三個污點到來時根據FD計算出粘連Path,准備愉快地表現自己的時候,這個Path又會被第一個污點的FD干擾,計算出一個不正確的Path覆蓋,所以我們讓移動污點跟固定污點內切以後,就不對粘連Path產生影響。順便一提,這一切的動畫邏輯計算,都在CADisplayLink裡完成。

路徑Path:

我們采用兩條曲線銜接兩個圓的這種污點結合方式作為動畫路徑(見上圖),這種方式能很好地模擬呈現液體的吸收結合效果,曲線經過固定污點的頂點Fu和移動污點的頂點Mu,再由FuMu線段中垂線上的一個controlPoint決定了一條曲線。UIBezierPath的API:addQuadCurveToPoint:controlPoint:。整個路徑由曲線FuMu,曲線FdMd,和圓弧MuMd組成,最後由線段FuFd封閉。吸收效果截圖:

吸收截圖web.jpg

回彈粘黏效果

做回彈效果前,我們先得讓移動污點會跑到固定污點的後面,引入originRearX(出發點的後方)和finalRearX(終點的後方),修改移動污點的keyframeAnim:

anim.values = @[@(originX),    @(originX), @(finalX),    @(finalRearX), @(finalX),
@(finalX), @(originX),    @(originRearX), @(originX), @(originX)];
anim.keyTimes = @[@(0.0), @(0.25), @(0.35),    @(0.38), @(0.41),
@(0.75), @(0.85),    @(0.88), @(0.91), @(1.0)];//sleep 0.4 ratio

代碼中可以看出,我定義了回彈時間為0.03 * 2,ok這裡沒有什麼問題。 

回彈動畫原理圖.jpg

至於回彈的效果Path,因為兩個污點的圓心距比較小,如果仍然沿用上一種路徑方案效果會不太好,我們使用上圖這種Path而不再使用模擬液體吸收的曲線方式。參考上圖,我們通過兩條線段,和一條與移動污點圓周重合的圓弧來組合粘黏效果Path,再由線段FuFd來閉合它,線段經過左固定污點,與溢出的移動污點相切。

還記得“是否包含”嗎,移動污點從固定污點後面溢出來時也就不符合包含關系了,會影響到正面粘黏效果的作畫,於是這裡的回彈粘黏效果跟普通粘黏效果分開兩個獨立的CAShapeLayer來做的,避免干擾。回彈效果截圖: 

回彈截圖web2.png

總體思路就是這樣了,主要的耗時工作就是計算path和協調幾個CAShapeLayer的顯示消失和互相影響,源代碼或者效果可以在Github:https://github.com/liuzhiyi1992/PreLoader中查看,謝謝。

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved