iOS Animate Dashed Rectangle Border

2019-02-06 14:29发布

问题:

I have a UIImageView displaying an image. I want to "highlight" a portion of the image by drawing a rounded rectangle outline. I would like to have the outline drawn with a thick, dashed line that "animates" by continually varying where the "beginning" of the line starts.

I thought about drawing a circle that had the look I want and then simply animating it, but I really need a rectangular solution, so that's out.

Background:

I'm drawing the rounded rectangle border by calculating 8 points and drawing 4 straight lines and 4 curves. (Maybe this can be easier, but it's not the broken part!)

My thinking is that I'll use an "offset" variable that starts at the top-left of the rounded rectangle, where the top-left curve meets the top straight piece. Then, I will increment this "offset" across the top of the rounded rectangle until it reaches the top-right curve, whereupon I will "reset" the "offset" variable to its original value.

This is working pretty much as I'd like, until the "reset" occurs. At this point, the animation is jerky (kind of expected), but it also appears to travel in reverse for a small portion of the time, before resuming "forward" motion. Finally, at the beginning/end of my dashed line, I get an extra long segment on the dashed line. I know they can't all be equal-length (can they? how to calculate?), but how can I make 2 shorter segments rather than 1 longer segment?

Anybody have an idea of what I can do to get a smooth "marching ants" kind of look? Any other ideas on a good way to (using animation) call the user's eye to a particular area of the screen? (It needs to surround a particular area without obscuring it.)

Current code:

- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextClearRect(context, rect);

    // Rounded corner will be 10% of average side length (i.e., (w + h) / 2)
    float averageSide = ([self HighlightRect].size.width + [self HighlightRect].size.height) / 2.0;
    float roundSize = averageSide * 0.10;

    // offset is a static, class variable
    offset += roundSize / 4.0;
    if ([WhereIAmView offset] < roundSize) {
        offset = roundSize;
    }
    if ([WhereIAmView offset] > ([self HighlightRect].size.width - roundSize)) {
        offset = roundSize;
    }

    // Set the "main" color of the rounded rectangle
    UIColor *lineColor = [UIColor colorWithRed:027.0/255.0 green:050.0/255.0 blue:224.0/255.0 alpha:1.0];
    CGContextSetStrokeColorWithColor(context, [lineColor CGColor]);
    CGContextSetLineWidth(context, 16.0);
    CGFloat pattern[] = {25.0, 5.0};
    CGContextSetLineDash(context, offset, pattern, 2);

    CGRect rRect = [self HighlightRect];
    // The top left corner
    CGPoint topLeft = CGPointMake(rRect.origin.x, rRect.origin.y);
    // The top right corner
    CGPoint topRight = CGPointMake(rRect.origin.x + rRect.size.width, rRect.origin.y);
    // The bottom right corner
    CGPoint bottomRight = CGPointMake(rRect.origin.x + rRect.size.width, rRect.origin.y + rRect.size.height);
    // The bottom left corner
    CGPoint bottomLeft = CGPointMake(rRect.origin.x, rRect.origin.y +     rRect.size.height);

    // The two points across the top of the rounded rectangle (left to right)
    CGPoint point1 = CGPointMake(rRect.origin.x + roundSize, rRect.origin.y);
    CGPoint point2 = CGPointMake(rRect.origin.x + rRect.size.width - roundSize, rRect.origin.y);
    // The two points along the right of the rounded rectangle (top to bottom)
    CGPoint point3 = CGPointMake(rRect.origin.x + rRect.size.width, rRect.origin.y + roundSize);
    CGPoint point4 = CGPointMake(rRect.origin.x + rRect.size.width, rRect.origin.y + rRect.size.height - roundSize);
    // The two points along the bottom of the rounded rectangle (right to left)
    CGPoint point5 = CGPointMake(rRect.origin.x + rRect.size.width - roundSize, rRect.origin.y + rRect.size.height);
    CGPoint point6 = CGPointMake(rRect.origin.x + roundSize, rRect.origin.y + rRect.size.height);
    // The two points along the left of the rounded rectangle (bottom to top)
    CGPoint point7 = CGPointMake(rRect.origin.x, rRect.origin.y + rRect.size.height - roundSize);
    CGPoint point8 = CGPointMake(rRect.origin.x, rRect.origin.y + roundSize);

    // Move to point 1
    CGContextMoveToPoint(context, point1.x, point1.y);
    // Add line to point 2 (this is the straight portion across the top)
    CGContextAddLineToPoint(context, point2.x, point2.y);
    // Add curve to point 3 (this is the rounded portion in top right)
    CGContextAddArcToPoint(context, topRight.x, topRight.y, point3.x, point3.y, roundSize);
    // Add line to point 4 (this is the straight portion across the right)
    CGContextAddLineToPoint(context, point4.x, point4.y);
    // Add curve to point 5 (this is the rounded portion in bottom right)
    CGContextAddArcToPoint(context, bottomRight.x, bottomRight.y, point5.x, point5.y, roundSize);
    // Add line to point 6 (this is the straight portion across the bottom)
    CGContextAddLineToPoint(context, point6.x, point6.y);
    // Add curve to point 7 (this is the rounded portion in bottom left)
    CGContextAddArcToPoint(context, bottomLeft.x, bottomLeft.y, point7.x, point7.y, roundSize);
    // Add line to point 8 (this is the straight portion across the left)
    CGContextAddLineToPoint(context, point8.x, point8.y);
    // Add curve to point 1 (this is the rounded portion in top left)
    CGContextAddArcToPoint(context, topLeft.x, topLeft.y, point1.x, point1.y, roundSize);

    // Stroke the path
    CGContextStrokePath(context);

}

bump bump bump

回答1:

Try Using CAShapeLayer with CGPath of your shape.

Rounded rectangle path can be constructed using Uibezierpath convenience method.

You can set a line pattern for the shape layer. Animating the line property of the shape layer would give the "marching ants like effect".

shapeLayer = [CAShapeLayer layer];
CGRect shapeRect = CGRectMake(0.0f, 0.0f, 200.0f, 100.0f);
[shapeLayer setBounds:shapeRect];
[shapeLayer setPosition:CGPointMake(160.0f, 140.0f)];
[shapeLayer setFillColor:[[UIColor clearColor] CGColor]];
[shapeLayer setStrokeColor:[[UIColor blackColor] CGColor]];
[shapeLayer setLineWidth:1.0f];
[shapeLayer setLineJoin:kCALineJoinRound];
[shapeLayer setLineDashPattern:
 [NSArray arrayWithObjects:[NSNumber numberWithInt:10], 
  [NSNumber numberWithInt:5], 
  nil]];
  UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:shapeRect cornerRadius:15.0];
[shapeLayer setPath:path.CGPath];
[[imageview layer] addSublayer:shapeLayer];

The animation function can be,

- (void)toggleMarching
  {
if ([shapeLayer animationForKey:@"linePhase"])
    [shapeLayer removeAnimationForKey:@"linePhase"];
else {
    CABasicAnimation *dashAnimation;
    dashAnimation = [CABasicAnimation 
                     animationWithKeyPath:@"lineDashPhase"];

    [dashAnimation setFromValue:[NSNumber numberWithFloat:0.0f]];
    [dashAnimation setToValue:[NSNumber numberWithFloat:15.0f]];
    [dashAnimation setDuration:0.75f];
    [dashAnimation setRepeatCount:10000];

    [shapeLayer addAnimation:dashAnimation forKey:@"linePhase"];

}
  }