当动画中的块调用停止时,填充颜色动画闪烁

Esk*_*918 3 core-animation core-graphics calayer ios uibezierpath

我无法弄清楚动画块完成后动画从fromValue到toValue闪烁的原因.我知道在完成动画后,您必须将CALayer的值设置为动画的结束状态,以使其看起来保持一致.然而,无论我在哪种顺序调用这些方法,我都会不断获得闪烁的结果.我正在做的是绘制一个带有biezer路径的复选标记,然后一旦strokeEnd动画完成,我通过动画fillColor属性填写复选标记.当用户选择与复选标记相关联的tableviewcell的行时,填充复选标记功能和重置复选标记功能都被触发.哦,如果这有所不同,我正在使用AutoLayout.

所以我真的想知道一些事情1)一旦显示表视图单元格,我调用公共函数shoudSetCheckmarkToCheckedState,它将boolean self.userSelectedCheckmark设置为函数中传递的参数isChecked.从那里我调用[self setNeedsLayout],它触发layoutSubviews并调用shouldDrawCheckmark ...函数.我这样做的原因是因为如果我没有第一次绘制单元格就没有框架设置,所以我的绘图看起来很乱.因此,每当我更改userSelectedCheckmark属性时,我应该调用setNeedsLayout还是有更好的方法.

2)为什么动画完成后复选标记会闪烁.我想我知道为什么,因为当动画完成时,图层属性被重置为图层开始动画时所处的状态.那么我该如何解决这个问题呢?我会触发一个计时器,在动画结束前一毫秒更改填充颜色,但感觉不对.

继续代码btw

typedef void (^animationCompletionBlock)(void);
#define kAnimationCompletionBlock @"animationCompletionBlock"

#import "CheckmarkView.h"
#import "UIColor+HexString.h"

@interface CheckmarkView()
@property (nonatomic,strong) CAShapeLayer *checkmarkLayer;
@property (nonatomic,assign) BOOL userSelectedCheckmark;
- (void)shouldDrawCheckmarkToLayerWithAnimation:(BOOL)animateCheckmark;
@end

@implementation CheckmarkView

#pragma mark - Lifecycle
/**********************/
- (id)initWithFrame:(CGRect)frame{
    self = [super initWithFrame:frame];
    if (self) {
        self.translatesAutoresizingMaskIntoConstraints = FALSE;
        self.layer.cornerRadius = 5.0;
        [self setClipsToBounds:TRUE];
    }
    return self;
}
- (void)layoutSubviews{
   [self shouldDrawCheckmarkToLayerWithAnimation:self.userSelectedCheckmark];
}

#pragma mark - Public Methods
/***************************/
- (void)shouldSetCheckmarkToCheckedState:(BOOL)isChecked{
    self.userSelectedCheckmark = isChecked;
    [self setNeedsLayout];
}

#pragma mark - Private Methods
/****************************/
- (void)shouldDrawCheckmarkToLayerWithAnimation:(BOOL)animateCheckmark{

    if(self.userSelectedCheckmark){

        CGRect superlayerRect = self.bounds;
        if(!self.checkmarkLayer){

            self.checkmarkLayer = [CAShapeLayer layer];
            [self.checkmarkLayer setStrokeColor:[UIColor whiteColor].CGColor];

            UIBezierPath *checkMarkPath = [UIBezierPath bezierPath];
            //Start Point
            [checkMarkPath moveToPoint:CGPointMake(CGRectGetMinX(superlayerRect) + 5, CGRectGetMinY(superlayerRect) + 14)];

            //Bottom Point
            [checkMarkPath addLineToPoint:CGPointMake(CGRectGetMidX(superlayerRect), CGRectGetMaxY(superlayerRect) - 4)];

            //Top Right of self.checkmarkLayer
            [checkMarkPath addLineToPoint:CGPointMake(CGRectGetMaxX(superlayerRect) - 5, CGRectGetMinY(superlayerRect) + 8)];
            [checkMarkPath addLineToPoint:CGPointMake(checkMarkPath.currentPoint.x - 3, checkMarkPath.currentPoint.y - 4)];

            //Top Middle Point
            [checkMarkPath addLineToPoint:CGPointMake(CGRectGetMidX(superlayerRect) - 1, CGRectGetMidY(superlayerRect) + 2)];

            //Top left of self.checkmarkLayer
            [checkMarkPath addLineToPoint:CGPointMake(CGRectGetMinX(superlayerRect) + 7, CGRectGetMinY(superlayerRect) + 10)];
            [checkMarkPath closePath];
            [self.checkmarkLayer setPath:checkMarkPath.CGPath];
        }

        self.layer.backgroundColor = [UIColor colorWithHexString:UIColorOrangeB0].CGColor;
        [self.checkmarkLayer setFillColor:[UIColor colorWithHexString:UIColorOrangeB0].CGColor];
        [self.layer addSublayer:self.checkmarkLayer];

        if(animateCheckmark){

            animationCompletionBlock block;
            block = ^(void){
                [self.checkmarkLayer setFillColor:[UIColor whiteColor].CGColor];
            };

            CABasicAnimation *strokeAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
            [strokeAnimation setBeginTime:0.0];
            [strokeAnimation setFromValue:@(0.0f)];
            [strokeAnimation setToValue:@(1.0f)];
            [strokeAnimation setDuration:.8];

            CABasicAnimation *fillAnimation = [CABasicAnimation animationWithKeyPath:@"fillColor"];
            [fillAnimation setBeginTime:strokeAnimation.duration + .2];
            [fillAnimation setDuration:.2];
            [fillAnimation setFromValue:(id)[UIColor colorWithHexString:UIColorOrangeB0].CGColor];
            [fillAnimation setToValue:(id)[UIColor whiteColor].CGColor];

            CAAnimationGroup *group = [CAAnimationGroup animation];
            group.delegate = self;
            [group setDuration:1.5];
            [group setAnimations:@[strokeAnimation,fillAnimation]];
            [group setValue:block forKey:kAnimationCompletionBlock];
            [self.checkmarkLayer addAnimation:group forKey:nil];
        }
    }
    else{
        self.layer.backgroundColor = [UIColor colorWithHexString:UIColorWhiteOffset].CGColor;
        [self.checkmarkLayer setFillColor:[UIColor colorWithHexString:UIColorOrangeB0].CGColor];
        [self.checkmarkLayer removeFromSuperlayer];
    }

}


#pragma mark - CAAnimationBlock
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
    animationCompletionBlock theBlock = [theAnimation valueForKey: kAnimationCompletionBlock];
    if (theBlock)
        theBlock();
}
Run Code Online (Sandbox Code Playgroud)

Sam*_*Sam 9

回答你的第一个问题

layoutSubviews只会在每个运行循环中调用一次.您可以根据需要调用[self setNeedsLayout],而不必担心会在不必要的情况下显示您的观点.

引自:Kubicek,Jim."滥用UIView." 华侨城11日,2012年 http://jimkubicek.com/blog/2012/10/11/using-uiview/

要回答你的第二个问题,你是正确的,为什么它闪烁.问题是无法保证在重置图层外观之前调用animationDidStop回调方法.

有几种方法可以解决这个问题,以下方法只需添加一些额外的代码而无需更改现有代码.

//kCAFillModeForwards: the animatable properties take on the end value once it has finished
[strokeAnimation setFillMode:kCAFillModeForwards];
[strokeAnimation setRemovedOnCompletion:NO];
[fillAnimation setFillMode:kCAFillModeForwards];
[fillAnimation setRemovedOnCompletion:NO];
[group setFillMode:kCAFillModeForwards];
[group setRemovedOnCompletion:NO];

[self.checkmarkLayer addAnimation:group forKey:nil];
Run Code Online (Sandbox Code Playgroud)

当CAAnimation完成时,它将自己从图层中移除并导致图层重置,因此我们要做的第一件事是停止移除动画.

- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
    animationCompletionBlock theBlock = [theAnimation valueForKey: kAnimationCompletionBlock];
    if (theBlock)
        theBlock();

[self.checkmarkLayer removeAllAnimations];
Run Code Online (Sandbox Code Playgroud)

}

当调用animationDidStop方法时,我们像之前一样设置图层的属性,然后我们从图层中删除动画.

还有一件事需要考虑,当你改变CALayer的外观时,它会被隐式(自动)动画化.因此,当您设置完成块时,您希望明确告知核心动画不要动画

animationCompletionBlock block;
        block = ^(void){
            [CATransaction begin];
            [CATransaction setDisableActions:YES];
            [self.checkmarkLayer setFillColor:[UIColor whiteColor].CGColor];
            [CATransaction commit];
        };
Run Code Online (Sandbox Code Playgroud)