Fill color animation Flicker stopped when a block was called in the animation

It’s hard for me to understand why the animation flickers fromValue toValue after completing my animation block. I know that after the animation finishes, you must set the CALayer values ​​to the final state of the animation so that it looks consistent. But no matter what order I call these methods, I always get a flickering result. What I do is draw a checkmark using the biezer path, and after the strokeEnd animation is complete, I fill the checkmark by animating the fillColor property. The checkmark function and the reset checkmark function are activated when the user selects the tableviewcell row with which the flag is associated. Oh, and I use AutoLayout if that matters.

So, I’m actually interested in a few 1) When the table view cell is displayed, I call the public function shoudSetCheckmarkToCheckedState, which sets the logical value self.userSelectedCheckmark to the isChecked parameter passed to the function. From there, I call [self setNeedsLayout], which launches layoutSubviews and calls the shouldDrawCheckmark function .... The reason why I do this is that if I'm not the first time the cell is drawn, there is no frame, so my picture looks messy . So I have to call setNeedsLayout every time I change the userSelectedCheckmark property or there is a better way.

2) Why the checkmark flickers after the animation is completed. I think I know why, because when the animation completes the properties of the layers, reset refers to the same state they were in when the layer started the animation. So how can I fix this? I just start the timer to change the fill color by a millisecond before the animation ends, but that doesn't seem right.

heres btw code

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(); } 
+2
source share
1 answer

To answer your first question

layoutSubviews will only be called once per loop cycle. You can call [self setNeedsLayout] as much as you want, without worrying about getting into a performance hit by setting out your views in vain.

Link: Kubitschek, Jim. "Abuse of UIView". October 11, 2012. http://jimkubicek.com/blog/2012/10/11/using-uiview/

To answer the second question, you are right in why it flickers. The problem is that there is no guarantee that the animationDidStop callback method will be called before the reset layer appears.

There are several ways to fix this; the next way simply involves adding additional code without modifying the existing code.

 //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]; 

When CAAnimation completes, it is removed from the layer and calls the reset level, so the first thing we will do is stop deleting the animation.

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

}

When the animationDidStop method is called, we set the layer properties as before, then remove the animation from the layer.

Another thing to think about is that when you change the appearance of CALayer, it becomes implicit (automatically) animated. Therefore, when you configure the completion block, you want to explicitly specify the main animation so as not to animate

 animationCompletionBlock block; block = ^(void){ [CATransaction begin]; [CATransaction setDisableActions:YES]; [self.checkmarkLayer setFillColor:[UIColor whiteColor].CGColor]; [CATransaction commit]; }; 
+8
source

All Articles