LOTRoundedRectAnimator.m 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. //
  2. // LOTRoundedRectAnimator.m
  3. // Lottie
  4. //
  5. // Created by brandon_withrow on 7/19/17.
  6. // Copyright © 2017 Airbnb. All rights reserved.
  7. //
  8. #import "LOTRoundedRectAnimator.h"
  9. #import "LOTPointInterpolator.h"
  10. #import "LOTNumberInterpolator.h"
  11. #import "CGGeometry+LOTAdditions.h"
  12. @implementation LOTRoundedRectAnimator {
  13. LOTPointInterpolator *_centerInterpolator;
  14. LOTPointInterpolator *_sizeInterpolator;
  15. LOTNumberInterpolator *_cornerRadiusInterpolator;
  16. BOOL _reversed;
  17. }
  18. - (instancetype _Nonnull)initWithInputNode:(LOTAnimatorNode *_Nullable)inputNode
  19. shapeRectangle:(LOTShapeRectangle *_Nonnull)shapeRectangle {
  20. self = [super initWithInputNode:inputNode keyName:shapeRectangle.keyname];
  21. if (self) {
  22. _centerInterpolator = [[LOTPointInterpolator alloc] initWithKeyframes:shapeRectangle.position.keyframes];
  23. _sizeInterpolator = [[LOTPointInterpolator alloc] initWithKeyframes:shapeRectangle.size.keyframes];
  24. _cornerRadiusInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:shapeRectangle.cornerRadius.keyframes];
  25. _reversed = shapeRectangle.reversed;
  26. }
  27. return self;
  28. }
  29. - (NSDictionary *)valueInterpolators {
  30. return @{@"Size" : _sizeInterpolator,
  31. @"Position" : _centerInterpolator,
  32. @"Roundness" : _cornerRadiusInterpolator};
  33. }
  34. - (BOOL)needsUpdateForFrame:(NSNumber *)frame {
  35. return [_centerInterpolator hasUpdateForFrame:frame] || [_sizeInterpolator hasUpdateForFrame:frame] || [_cornerRadiusInterpolator hasUpdateForFrame:frame];
  36. }
  37. - (void)addCorner:(CGPoint)cornerPoint withRadius:(CGFloat)radius toPath:(LOTBezierPath *)path clockwise:(BOOL)clockwise {
  38. CGPoint currentPoint = path.currentPoint;
  39. CGFloat ellipseControlPointPercentage = 0.55228;
  40. if (cornerPoint.y == currentPoint.y) {
  41. // Moving east/west
  42. if (cornerPoint.x < currentPoint.x) {
  43. // Moving west
  44. CGPoint corner = CGPointMake(cornerPoint.x + radius, currentPoint.y);
  45. [path LOT_addLineToPoint:corner];
  46. if (radius) {
  47. CGPoint curvePoint = clockwise ? CGPointMake(cornerPoint.x, cornerPoint.y - radius) : CGPointMake(cornerPoint.x, cornerPoint.y + radius);
  48. CGPoint cp1 = CGPointMake(corner.x - (radius * ellipseControlPointPercentage), corner.y);
  49. CGPoint cp2 = (clockwise ?
  50. CGPointMake(curvePoint.x, curvePoint.y + (radius * ellipseControlPointPercentage)) :
  51. CGPointMake(curvePoint.x, curvePoint.y - (radius * ellipseControlPointPercentage)));
  52. [path LOT_addCurveToPoint:curvePoint controlPoint1:cp1 controlPoint2:cp2];
  53. }
  54. } else {
  55. // Moving east
  56. CGPoint corner = CGPointMake(cornerPoint.x - radius, currentPoint.y);
  57. [path LOT_addLineToPoint:corner];
  58. if (radius) {
  59. CGPoint curvePoint = clockwise ? CGPointMake(cornerPoint.x, cornerPoint.y + radius) : CGPointMake(cornerPoint.x, cornerPoint.y - radius);
  60. CGPoint cp1 = CGPointMake(corner.x + (radius * ellipseControlPointPercentage), corner.y);
  61. CGPoint cp2 = (clockwise ?
  62. CGPointMake(curvePoint.x, curvePoint.y - (radius * ellipseControlPointPercentage)) :
  63. CGPointMake(curvePoint.x, curvePoint.y + (radius * ellipseControlPointPercentage)));
  64. [path LOT_addCurveToPoint:curvePoint controlPoint1:cp1 controlPoint2:cp2];
  65. }
  66. }
  67. } else {
  68. // Moving North/South
  69. if (cornerPoint.y < currentPoint.y) {
  70. // Moving North
  71. CGPoint corner = CGPointMake(currentPoint.x, cornerPoint.y + radius);
  72. [path LOT_addLineToPoint:corner];
  73. if (radius) {
  74. CGPoint curvePoint = clockwise ? CGPointMake(cornerPoint.x + radius, cornerPoint.y) : CGPointMake(cornerPoint.x - radius, cornerPoint.y);
  75. CGPoint cp1 = CGPointMake(corner.x, corner.y - (radius * ellipseControlPointPercentage));
  76. CGPoint cp2 = (clockwise ?
  77. CGPointMake(curvePoint.x - (radius * ellipseControlPointPercentage), curvePoint.y) :
  78. CGPointMake(curvePoint.x + (radius * ellipseControlPointPercentage), curvePoint.y));
  79. [path LOT_addCurveToPoint:curvePoint controlPoint1:cp1 controlPoint2:cp2];
  80. }
  81. } else {
  82. // moving south
  83. CGPoint corner = CGPointMake(currentPoint.x, cornerPoint.y - radius);
  84. [path LOT_addLineToPoint:corner];
  85. if (radius) {
  86. CGPoint curvePoint = clockwise ? CGPointMake(cornerPoint.x - radius, cornerPoint.y) : CGPointMake(cornerPoint.x + radius, cornerPoint.y);
  87. CGPoint cp1 = CGPointMake(corner.x, corner.y + (radius * ellipseControlPointPercentage));
  88. CGPoint cp2 = (clockwise ?
  89. CGPointMake(curvePoint.x + (radius * ellipseControlPointPercentage), curvePoint.y) :
  90. CGPointMake(curvePoint.x - (radius * ellipseControlPointPercentage), curvePoint.y));
  91. [path LOT_addCurveToPoint:curvePoint controlPoint1:cp1 controlPoint2:cp2];
  92. }
  93. }
  94. }
  95. }
  96. - (void)performLocalUpdate {
  97. CGFloat cornerRadius = [_cornerRadiusInterpolator floatValueForFrame:self.currentFrame];
  98. CGPoint size = [_sizeInterpolator pointValueForFrame:self.currentFrame];
  99. CGPoint position = [_centerInterpolator pointValueForFrame:self.currentFrame];
  100. CGFloat halfWidth = size.x / 2;
  101. CGFloat halfHeight = size.y / 2;
  102. CGRect rectFrame = CGRectMake(position.x - halfWidth, position.y - halfHeight, size.x, size.y);
  103. CGPoint topLeft = CGPointMake(CGRectGetMinX(rectFrame), CGRectGetMinY(rectFrame));
  104. CGPoint topRight = CGPointMake(CGRectGetMaxX(rectFrame), CGRectGetMinY(rectFrame));
  105. CGPoint bottomLeft = CGPointMake(CGRectGetMinX(rectFrame), CGRectGetMaxY(rectFrame));
  106. CGPoint bottomRight = CGPointMake(CGRectGetMaxX(rectFrame), CGRectGetMaxY(rectFrame));
  107. // UIBezierPath Draws rects from the top left corner, After Effects draws them from the top right.
  108. // Switching to manual drawing.
  109. CGFloat radius = MIN(MIN(halfWidth, halfHeight), cornerRadius);
  110. BOOL clockWise = !_reversed;
  111. LOTBezierPath *path1 = [[LOTBezierPath alloc] init];
  112. path1.cacheLengths = self.pathShouldCacheLengths;
  113. CGPoint startPoint = (clockWise ?
  114. CGPointMake(topRight.x, topRight.y + radius) :
  115. CGPointMake(topRight.x - radius, topRight.y));
  116. [path1 LOT_moveToPoint:startPoint];
  117. if (clockWise) {
  118. [self addCorner:bottomRight withRadius:radius toPath:path1 clockwise:clockWise];
  119. [self addCorner:bottomLeft withRadius:radius toPath:path1 clockwise:clockWise];
  120. [self addCorner:topLeft withRadius:radius toPath:path1 clockwise:clockWise];
  121. [self addCorner:topRight withRadius:radius toPath:path1 clockwise:clockWise];
  122. } else {
  123. [self addCorner:topLeft withRadius:radius toPath:path1 clockwise:clockWise];
  124. [self addCorner:bottomLeft withRadius:radius toPath:path1 clockwise:clockWise];
  125. [self addCorner:bottomRight withRadius:radius toPath:path1 clockwise:clockWise];
  126. [self addCorner:topRight withRadius:radius toPath:path1 clockwise:clockWise];
  127. }
  128. [path1 LOT_closePath];
  129. self.localPath = path1;
  130. }
  131. @end