当动画中的块调用停止时,填充颜色动画闪烁 [英] Fill Color Animation Flickers when calling block in animation did stop

查看:135
本文介绍了当动画中的块调用停止时,填充颜色动画闪烁的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Im having trouble figuring out why the animation is flickering from the fromValue to the toValue after my animation block is complete. I know that after you complete an animation you have to set the values of the CALayer to the end state of the animation to keep it looking consistent. Yet no matter what order i call these methods in i keep getting the flickering result. What I'm doing is drawing a checkmark with a biezer path and then once the strokeEnd animation is complete i fill in the check mark by animating the fillColor property. The fill in checkmark function and reset checkmark function are all triggered when the user selects the row of the tableviewcell the checkmark is associated with. Oh ya and I am using AutoLayout if that makes a difference.

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

So I'm wondering a few things actually one 1) Once the table view cell is being displayed i call the public function shoudSetCheckmarkToCheckedState which sets the boolean self.userSelectedCheckmark to the parameter isChecked thats passed in the function. From there i call [self setNeedsLayout],which triggers layoutSubviews and it calls the shouldDrawCheckmark... function. The reason why I do this is because if I don't the first time the cell is drawn there is no frame set so my drawing looks a mess. So should I be calling setNeedsLayout every time i change the userSelectedCheckmark property or is there a better way.

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

2) Why is the checkmark flickering once the animation is complete. I think i know why, its because when the animation completes the layers properties are reset to the same state they were in when the layer started animating. So then how can I fix this? I would just trigger a timer to change the fill color a millisecond before the animation ends but that doesn't feel right.

继承代码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();
}


推荐答案

回答你的第一个问题


每个运行循环只会调用一次layoutSubviews。您可以根据需要随意调用[self setNeedsLayout],而不必担心会不必要地表达您的观点。

layoutSubviews will only be called once per run-loop. You can call [self setNeedsLayout] as much as you want without worrying about taking a performance hit laying out your views needlessly.

引用来自:
Kubicek,吉姆。 滥用UIView。 2012年10月11日。
http://jimkubicek.com/blog/2012/10/ 11 / using-uiview /

要回答你的第二个问题,你对它闪烁的原因是正确的。问题是无法保证在重置图层外观之前将调用animationDidStop回调方法。

To answer your second question, you are right about why it is flickering. The problem is that there is no guarantee that the animationDidStop callback method will be called before the layer's appearance is reset.

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

There are a couple of ways to fix this, the following way just involves adding some extra code without changing your 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];

当CAAnimation完成时,它会从图层中移除自身并导致图层重置,因此第一个我们要做的就是阻止动画被移除。

When a CAAnimation completes, it removes itself from the layer and causes the layer to reset, so the first thing we will do is stop the animation from being removed.

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

[self.checkmarkLayer removeAllAnimations];

}

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

When the animationDidStop method is called, we set the layer's properties like before, then we remove the animations from the layer.

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

One more thing to think about is that when you change a CALayer's appearance, it gets implicitly (automatically) animated. So when you setup the completion block, you want to explicitly tell core animation not to animate

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

这篇关于当动画中的块调用停止时,填充颜色动画闪烁的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆