設(shè)計師給了一個交互動畫需要實現(xiàn),是項目房間模塊匹配中的一個交互動效:圓球隨著實線轉(zhuǎn)绎狭,實線填充虛線灌侣,圓球轉(zhuǎn)到頂端開始減速,越過頂端開始加速卖陵,如圖:
一開始會想如何去讓一個圓球繞著圓心去轉(zhuǎn)遭顶,難道是需要套什么數(shù)學(xué)公式么?思考了下泪蔫,iOS 動畫庫中并沒有這種操作棒旗,然后思考了一下上面的部件層級。
部件層級
- 一個外環(huán)圓撩荣,很好實現(xiàn)
- 外環(huán)圓包著內(nèi)環(huán)虛線
- 一個做圓環(huán)動效的實線
- 一個固定的圓球
- 一個隨圓環(huán)滾動的圓球
過程拆分
外環(huán)圓和內(nèi)環(huán)虛線都非常好實現(xiàn)铣揉,第一反應(yīng)就是 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 感應(yīng)系統(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];