匹配虛線填充動畫分析實現(xiàn)

設計師給了一個交互動畫需要實現(xiàn),是項目房間模塊匹配中的一個交互動效:圓球隨著實線轉(zhuǎn),實線填充虛線,圓球轉(zhuǎn)到頂端開始減速,越過頂端開始加速,如圖:

一開始會想如何去讓一個圓球繞著圓心去轉(zhuǎn),難道是需要套什么數(shù)學公式么?思考了下,iOS 動畫庫中并沒有這種操作,然后思考了一下上面的部件層級。

部件層級

  1. 一個外環(huán)圓,很好實現(xiàn)
  2. 外環(huán)圓包著內(nèi)環(huán)虛線
  3. 一個做圓環(huán)動效的實線
  4. 一個固定的圓球
  5. 一個隨圓環(huán)滾動的圓球

過程拆分

外環(huán)圓和內(nèi)環(huán)虛線都非常好實現(xiàn),第一反應就是 CAShapeLayer 去畫各種圖形,并且 ShapeLayer 在性能上是優(yōu)化的。

查了很久沒看見有圓球繞著錨點做圓周運動的 API,于是我拿 Sketch 嘗試分解了一下,如圖所示:

如圖所示,換了個思路,雖然沒有圓球繞著錨點做圓周運動的 API,但是可以把它放在父 layer 上,然父 layer 圍繞自己的圓心自轉(zhuǎn),那么這個圓球也就繞著圓心運動了。

實現(xiàn)

首先從最簡單的開始

外圈大圓和內(nèi)圈虛線圓環(huán)

確定它們的貝塞爾曲線 path 直接繪制:

CGFloat bigLayerWidth = self.bounds.size.width;
CGFloat bigLayerHeight = self.bounds.size.height;

CGPoint position = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2);

UIBezierPath *bigLayerPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, bigLayerWidth, bigLayerHeight)];


_bigLayer = [CAShapeLayer layer];
_bigLayer.frame = CGRectMake(0, 0, bigLayerWidth, bigLayerHeight);
_bigLayer.path = bigLayerPath.CGPath;
_bigLayer.lineWidth = 1.f;
_bigLayer.strokeColor = Color(whiteColor).CGColor;
_bigLayer.fillColor = Color(clearColor).CGColor;
_bigLayer.position = position;
[self.layer addSublayer:_bigLayer];

CGFloat contentLayerWidth = bigLayerWidth - 32;
CGFloat contentLayerHeight = bigLayerHeight - 32;
CGRect contentLayerRect = CGRectMake(0, 0, contentLayerWidth, contentLayerHeight);

UIBezierPath *centralCirclePath = [UIBezierPath bezierPathWithOvalInRect:contentLayerRect];

_dashCircleLayer = [CAShapeLayer layer];
_dashCircleLayer.bounds = contentLayerRect;
_dashCircleLayer.path = centralCirclePath.CGPath;
_dashCircleLayer.fillColor = Color(clearColor).CGColor;
_dashCircleLayer.strokeColor =Color(whiteColor).CGColor;
_dashCircleLayer.lineDashPattern = @[@1, @1];
_dashCircleLayer.lineWidth = 0.5f;
_dashCircleLayer.position = position;
[self.layer addSublayer:_dashCircleLayer];

此時的效果如圖所示:

內(nèi)圈動畫層的繪制

要畫的有三個部分:

  • 需要旋轉(zhuǎn)的圓環(huán)
  • 需要旋轉(zhuǎn)的圓球父 layer
  • 需要旋轉(zhuǎn)的圓球
// 要做動畫的圓環(huán)
_dynamicCircleLayer = [CAShapeLayer layer];
_dynamicCircleLayer.bounds = contentLayerRect;
_dynamicCircleLayer.path = centralCirclePath.CGPath;
_dynamicCircleLayer.fillColor = Color(clearColor).CGColor;
_dynamicCircleLayer.lineWidth = 1.0f;
_dynamicCircleLayer.strokeColor = Color(whiteColor).CGColor;
_dynamicCircleLayer.strokeStart = 0;
_dynamicCircleLayer.strokeEnd = 1;
_dynamicCircleLayer.affineTransform = CGAffineTransformMakeRotation(M_PI + M_PI_2);
_dynamicCircleLayer.position = position;
[self.layer addSublayer:_dynamicCircleLayer];

// 要做動畫的父 layer
UIBezierPath *rectPath = [UIBezierPath bezierPathWithRect:contentLayerRect];
_dynamicContentLayer = [CAShapeLayer layer];
_dynamicContentLayer.bounds = contentLayerRect;
_dynamicContentLayer.path = rectPath.CGPath;
_dynamicContentLayer.fillColor = Color(clearColor).CGColor;
_dynamicContentLayer.strokeColor= Color(redColor).CGColor;
_dynamicContentLayer.position = position;
[self.layer addSublayer:_dynamicContentLayer];

// 父 layer 上的圓球
CGFloat ballLayerRadius = 4;

_dynamicBallLayer = [CAShapeLayer layer];
_dynamicBallLayer.frame = CGRectMake(0, 0, 2 * ballLayerRadius, 2 * ballLayerRadius);
_dynamicBallLayer.cornerRadius = ballLayerRadius;
_dynamicBallLayer.backgroundColor = Color(whiteColor).CGColor;
_dynamicBallLayer.position = CGPointMake(contentLayerWidth / 2.0, 0);
[_dynamicContentLayer addSublayer:_dynamicBallLayer];

此時效果如圖所示:

不動圓球的繪制

_staticContentLayer = [CAShapeLayer layer];
_staticContentLayer.bounds = contentLayerRect;
_staticContentLayer.fillColor = Color(clearColor).CGColor;
_staticContentLayer.strokeColor= Color(whiteColor).CGColor;
_staticContentLayer.position = position;

[self.layer addSublayer:_staticContentLayer];

_staticBallLayer = [CAShapeLayer layer];
_staticBallLayer.frame = CGRectMake(0, 0, 2 * ballLayerRadius, 2 * ballLayerRadius);
_staticBallLayer.cornerRadius = ballLayerRadius;
_staticBallLayer.backgroundColor = Color(whiteColor).CGColor;
_staticBallLayer.position = CGPointMake(contentLayerWidth / 2.0, 0);
[_staticContentLayer addSublayer:_staticBallLayer];

隱藏圓球父試圖開始動畫

重力加速度動畫,如果使用系統(tǒng)的 dynamic 感應系統(tǒng)就太麻煩了,我選擇用動畫選項淡入淡出模擬:

- (void)makeLayersAnimated {

 // 圓環(huán)動畫
 CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
 pathAnimation.duration = 3.0f;
 // 模擬重力加速度動畫
 pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
 pathAnimation.fromValue = [NSNumber numberWithFloat:0.00f];
 pathAnimation.toValue = [NSNumber numberWithFloat:1.0f];
 pathAnimation.fillMode = kCAFillModeForwards;
 pathAnimation.removedOnCompletion = NO;
 pathAnimation.repeatCount = INFINITY;
 [_dynamicCircleLayer addAnimation:pathAnimation forKey:@"strokeEndAnimation"];

 // 圓球動畫
 CABasicAnimation *animation = [CABasicAnimation animation];
 animation.keyPath = @"transform.rotation";
 animation.duration = 3.0f;
 animation.repeatCount = INFINITY;
 animation.byValue = @(M_PI * 2);
 animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];;
 [_dynamicContentLayer addAnimation:animation forKey:animation.keyPath];
}

最后在父控制器的 View 上添加這個 matching view:

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

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