用drawrect方法绘制带动画的矩形?

时间:2014-03-23 07:42:24

标签: ios objective-c drawing

我在drawrect方法中有以下代码。我想用动画画画。

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

CGMutablePathRef pathRef = CGPathCreateMutable();


CGPathMoveToPoint(pathRef, NULL, a.x, a.y);
CGPathAddLineToPoint(pathRef, NULL, b.x, b.y);
CGPathAddLineToPoint(pathRef, NULL, c.x, c.y);
CGPathAddLineToPoint(pathRef, NULL, d.x, d.y);
CGPathCloseSubpath(pathRef);



CGContextAddPath(context, pathRef);
CGContextStrokePath(context);

CGContextSetBlendMode(context, kCGBlendModeClear);

CGContextAddPath(context, pathRef);
CGContextFillPath(context);

//我在这里展示动画,但它不起作用。有没有办法做到这一点

-(void)showAnimation{
    [UIView beginAnimations:@"movement" context:nil];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationWillStartSelector:@selector(didStart:context:)];
    [UIView setAnimationDidStopSelector:@selector(didStop:finished:context:)];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
    [UIView setAnimationDuration:3.0f];

    [UIView setAnimationRepeatAutoreverses:YES];
    a=CGPointMake(10, 100);
    b=CGPointMake(100, 100);
    c=CGPointMake(100, 30);
    d=CGPointMake(20, 30);
    [UIView commitAnimations];
   }
}

3 个答案:

答案 0 :(得分:2)

也许自定义图层子类可能是您要搜索的内容。通过这样做,您可以添加自己的自定义动画属性,例如表示绘图进度的进度变量,并在drawInContext方法中用于确定要绘制的内容。

现在,如果您通过CAAnimation为该属性设置动画,那么所有发生的事情都是CoreAnimation系统制作图层的副本,将属性稍微更改为最终值,调用drawInContext,再次更改属性一点点,再次呼叫drawInContext,依此类推。这就是动画的工作原理。

Layer子类应如下所示:

#import <QuartzCore/QuartzCore.h>

@interface AnimatedRect : CALayer
@property (nonatomic) float progress;
@end

@implementation AnimatedRect

//the animated property must be dynamic
@dynamic progress;

//initWithLayer is called when the animation starts, for making the copy
- (id)initWithLayer:(id)layer {
    self = [super initWithLayer:layer];
    if (self) {
        self.progress = layer.progress;
    }
    return self;
}

//drawInContext is called to perform drawing
-(void)drawInContext:(CGContextRef)ctx
{
    //your drawing code for the rect up to the percentage in progress
    //some math is required here, to determine which sides are drawn
    //and where the endpoint is
}

在drawInContext方法中,您必须计算最后绘制线的结束位置。例如,如果你有一个正方形,12.5%是半个第一行,50%是前两个。下面的矩形是87.5%,从右上角开始 rect at 87.5%

作为补充:如果要隐式动画,则需要实现其他方法-(id<CAAction>)actionForKey:(NSString *)event,在其中创建CAAnimation并将其返回。

关于该主题的一个很好的消息来源是this tutorial

答案 1 :(得分:0)

这不是解决这个问题的正确方法。实施drawRect:应该是最后的选择,特别是如果你所做的只是画一个矩形。它不能很好地与动画配合使用,因为你要强制重绘每一帧,这对性能非常不利。

您最好不要执行以下操作之一:

  • 将矩形作为适当大小和背景颜色的子视图,并为其框架设置动画
  • 拥有CAShapeLayer并为其路径设置动画。

答案 2 :(得分:0)

来自@TAKeanice的有用answer我已实施similar超时行为。

XYZTimeoutView

#import <UIKit/UIKit.h>

/**
 *    A view to draw timeout line.
 */
@interface XYZTimeoutView : UIImageView

/**
 *    Current progress in percent.
 */
@property(nonatomic) IBInspectable CGFloat progress;

/**
 *    Padding between outer view edge and timeout line.
 */
@property(nonatomic) IBInspectable CGFloat padding;

/**
 *    A width of timeout line.
 */
@property(nonatomic) IBInspectable CGFloat strokeWidth;

/**
 *    A duration of timeout animation in seconds.
 */
@property(nonatomic) IBInspectable CGFloat durationOfTimeoutAnimation;

/**
 *    A color of timeout line.
 */
@property(nonatomic) IBInspectable UIColor *strokeColor;

@end

#import "XYZTimeoutView.h"
#import "XYZTimeoutLayer.h"

@interface XYZTimeoutView ()

@property(nonatomic) XYZTimeoutLayer *timeoutLayer;

@end

@implementation XYZTimeoutView

#pragma mark - Creation and initialization

- (instancetype)init {
    self = [super init];

    if (self) {
        [self initialization];
        [self update];
    }

    return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];

    if (self) {
        [self initialization];
        [self update];
    }

    return self;
}

- (void)initialization {
    [self setTimeoutLayer:[XYZTimeoutLayer layer]];

    [[self layer] addSublayer:[self timeoutLayer]];
}

- (void)update {
    [[self timeoutLayer] setPadding:[self padding]];
    [[self timeoutLayer] setProgress:[self progress]];
    [[self timeoutLayer] setStrokeWidth:[self strokeWidth]];
    [[self timeoutLayer] setStrokeColor:[self strokeColor]];
    [[self timeoutLayer] setDurationOfTimeoutAnimation:[self durationOfTimeoutAnimation]];
}

#pragma mark - Setter methods

- (void)setProgress:(CGFloat)progress {
    _progress = MAX(0.0f, MIN(progress, 100.0f));

    [[self timeoutLayer] setFrame:[self bounds]];
    [[self timeoutLayer] setProgress:[self progress]];
}

- (void)setPadding:(CGFloat)padding {
    _padding = padding;

    [[self timeoutLayer] setPadding:[self padding]];
}

- (void)setStrokeWidth:(CGFloat)strokeWidth {
    _strokeWidth = strokeWidth;

    [[self timeoutLayer] setStrokeWidth:[self strokeWidth]];
}

- (void)setDurationOfTimeoutAnimation:(CGFloat)durationOfTimeoutAnimation {
    _durationOfTimeoutAnimation = durationOfTimeoutAnimation;

    [[self timeoutLayer]
     setDurationOfTimeoutAnimation:[self durationOfTimeoutAnimation]];
}

- (void)setStrokeColor:(UIColor *)strokeColor {
    _strokeColor = strokeColor;

    [[self timeoutLayer] setStrokeColor:[self strokeColor]];
}

@end

XYZTimeoutLayer

@import QuartzCore;
@import UIKit;

/**
 *    A layer used to animate timeout line.
 */
@interface XYZTimeoutLayer : CALayer

/**
 *    Current progress in percent.
 */
@property(nonatomic) CGFloat progress;

/**
 *    Padding between outer view edge and timeout line.
 */
@property(nonatomic) CGFloat padding;

/**
 *    A width of timeout line.
 */
@property(nonatomic) CGFloat strokeWidth;

/**
 *    A duration of timeout animation in seconds.
 */
@property(nonatomic) CGFloat durationOfTimeoutAnimation;

/**
 *    A color of timeout line.
 */
@property(nonatomic) UIColor *strokeColor;

@end

#import "XYZTimeoutLayer.h"
#import "XYZTimeoutLocation.h"

@implementation XYZTimeoutLayer

@dynamic progress;

#pragma mark - Layer creation and initialization

- (instancetype)initWithLayer:(id)layer {
    self = [super initWithLayer:layer];

    if (self && [layer isKindOfClass:[XYZTimeoutLayer class]]) {
        [self copyFrom:(XYZTimeoutLayer *)layer];
    }

  return self;
}

#pragma mark - Property methods

+ (BOOL)needsDisplayForKey:(NSString *)key {
    if ([NSStringFromSelector(@selector(progress)) isEqualToString:key]) {
        return YES;
    }

    return [super needsDisplayForKey:key];
}

#pragma mark - Animation methods

- (id<CAAction>)actionForKey:(NSString *)event {
    if ([NSStringFromSelector(@selector(progress)) isEqualToString:event]) {
        return [self createAnimationForKey:event];
    }

    return [super actionForKey:event];
}

#pragma mark - Draw

- (void)drawInContext:(CGContextRef)ctx {
    // Initialization
    CGRect rect = [self drawRect];
    CGPoint pointTopLeft = CGPointMake(rect.origin.x, rect.origin.y);
    CGPoint pointTopRight = CGPointMake(rect.origin.x + rect.size.width, rect.origin.y);
    CGPoint pointBottomLeft = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height);
    CGPoint pointBottomRight = CGPointMake(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height);
    XYZTimeoutLocation *location = [XYZTimeoutLocation endLocationForPercent:[self progress] rect:rect];

    // Draw initialization
    CGContextSetLineWidth(ctx, [self strokeWidth]);
    CGContextSetStrokeColorWithColor(ctx, [[self strokeColor] CGColor]);

    // Move to start point
    CGContextMoveToPoint(ctx, pointTopRight.x, pointTopRight.y - ([self strokeWidth] / 2));

    // Set draw path
    if(TOP == [location edge]){
        CGContextAddLineToPoint(ctx, pointBottomRight.x, pointBottomRight.y);
        CGContextAddLineToPoint(ctx, pointBottomLeft.x, pointBottomLeft.y);
        CGContextAddLineToPoint(ctx, pointTopLeft.x, pointTopLeft.y);
        CGContextAddLineToPoint(ctx, rect.origin.x + [location scope], pointTopRight.y);
    } else if(LEFT == [location edge]) {
        CGContextAddLineToPoint(ctx, pointBottomRight.x, pointBottomRight.y);
        CGContextAddLineToPoint(ctx, pointBottomLeft.x, pointBottomLeft.y);
        CGContextAddLineToPoint(ctx, pointTopLeft.x, rect.origin.y + rect.size.height - [location scope]);
    } else if(BOTTOM == [location edge]) {
        CGContextAddLineToPoint(ctx, pointBottomRight.x, pointBottomRight.y);
        CGContextAddLineToPoint(ctx, rect.origin.x + rect.size.width - [location scope], pointBottomLeft.y);
    } else if(RIGHT == [location edge]) {
        CGContextAddLineToPoint(ctx, pointBottomRight.x, rect.origin.y + [location scope] - ([self strokeWidth] / 2));
    }

    // Draw
    CGContextStrokePath(ctx);
}

#pragma mark - Helper Methods

- (void)copyFrom:(XYZTimeoutLayer *)layer {
    [self setPadding:[layer padding]];
    [self setProgress:[layer progress]];
    [self setStrokeWidth:[layer strokeWidth]];
    [self setStrokeColor:[layer strokeColor]];
    [self setDurationOfTimeoutAnimation:[layer durationOfTimeoutAnimation]];
}

- (CABasicAnimation *)createAnimationForKey:(NSString *)key {
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key];

    [animation setDuration:[self durationOfTimeoutAnimation]];
    [animation setFromValue:[[self presentationLayer] valueForKey:key]];
    [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];

    return animation;
}

- (CGRect)drawRect {
    CGRect rect = [self bounds];

    CGRect drawRect = CGRectMake([self padding], [self padding],
                                 rect.size.width - (self.padding * 2),
                                 rect.size.height - (self.padding * 2));

    return drawRect;
}

@end

XYZTimeoutLocation

@import Foundation;
@import CoreGraphics;

/**
 *    An enum used to mark end of timeout line.
 */
typedef NS_ENUM(NSUInteger, Edge) {
    /**
     *    The end of timeout line is at top.
     */
    TOP = 0,
    /**
     *    The end of timeout line is at left.
     */
    LEFT,
    /**
     *    The end of timeout line is at bottom.
     */
    BOTTOM,
    /**
     *    The end of timeout line is at right.
     */
    RIGHT
};

/**
 *    A class that holds end of timeout line.
 */
@interface XYZTimeoutLocation : NSObject

/**
 *    The edge of the view where timeout line ended.
 */
@property(nonatomic) Edge edge;

/**
 *    The end scope to draw.
 */
@property(nonatomic) CGFloat scope;

/**
 *    Calculates scope for specified percent value for given rect.
 *
 *    @param percent A percent to calculate scope for it.
 *    @param rect    A rect to calculate scope for it.
 *
 *    @return A scope of rect for specified percent.
 */
+ (CGFloat)scopeForPercent:(CGFloat)percent rect:(CGRect)rect;

/**
 *    Returns an instance of MVPTimeoutLocation that holds edge of view and scope to draw for specified percent value for given rect.
 *
 *    @param percent A percent to calculate scope for it.
 *    @param rect    A rect to calculate scope for it.
 *
 *    @return An instance of MVPTimeoutLocation that holds edge of view and scope to draw.
 */
+ (XYZTimeoutLocation *)endLocationForPercent:(CGFloat)percent rect:(CGRect)rect;

@end

#import "XYZTimeoutLocation.h"

@implementation XYZTimeoutLocation

+ (XYZTimeoutLocation *)endLocationForPercent:(CGFloat)percent rect:(CGRect)rect {
    CGFloat scope = [XYZTimeoutLocation scopeForPercent:percent rect:rect];
    XYZTimeoutLocation *location = [[XYZTimeoutLocation alloc] init];

    if (scope > rect.size.height) {
        scope -= rect.size.height;

        if (scope > rect.size.width) {
            scope -= rect.size.width;

            if (scope > rect.size.height) {
                scope -= rect.size.height;

                location.edge = TOP;
                location.scope = scope;
            } else {
                location.edge = LEFT;
                location.scope = scope;
            }
        } else {
            location.edge = BOTTOM;
            location.scope = scope;
        }
    } else {
        location.edge = RIGHT;
        location.scope = scope;
    }

    return location;
}

+ (CGFloat)scopeForPercent:(CGFloat)percent rect:(CGRect)rect {
    CGFloat scope = (rect.size.width * 2) + (rect.size.height * 2);
    CGFloat scopeForPercent = (scope / 100) * percent;

    return scopeForPercent;
}

@end

我对[XYZTimeoutLocation endLocationForPercent]方法不满意。如果有人更了解如何执行此操作,请更新/编辑。

在下面的UIViewController插入代码中进行测试:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    [self.timeoutView setTranslatesAutoresizingMaskIntoConstraints:NO];
    [self.timeoutView setStrokeColor:[UIColor greenColor]];
    [self.timeoutView setDurationOfTimeoutAnimation:1];
    [self.timeoutView setStrokeWidth:10.0f];
    [self.timeoutView setPadding:10.0f];

    self.percent = 0;

    [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        if ((self.percent = (self.percent + 10)) > 100) {
            self.percent = 0;
        }


        [self.timeoutView setProgress:self.percent];
    }];
}
相关问题