【问题标题】:Fill Color Animation Flickers when calling block in animation did stop动画中调用块时填充颜色动画闪烁确实停止
【发布时间】:2014-10-20 01:55:56
【问题描述】:

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

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

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

这里是代码

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();
}

【问题讨论】:

  • 我删除了 [self setNeedsLayout] 调用和 layoutSubviews 方法,一切正常。也许你可以上传一张出了什么问题的照片。
  • 您是否将视图加载到 tableview 单元格中?
  • 是的,但实际上你应该能够尽可能多地调用 [self setNeedsLayout]。我已经更新了我的答案

标签: ios core-graphics core-animation calayer uibezierpath


【解决方案1】:

回答你的第一个问题

layoutSubviews 在每个运行循环中只会被调用一次。您可以随意调用 [self setNeedsLayout],而不必担心不必要地布置视图会影响性能。

引用自: 库比切克,吉姆。 “滥用 UIView。” 2012 年 10 月 11 日。 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];

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

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

[self.checkmarkLayer removeAllAnimations];

}

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

要考虑的另一件事是,当您更改 CALayer 的外观时,它会隐式(自动)变为动画。所以当你设置完成块时,你要明确告诉核心动画不要动画

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

【讨论】:

    猜你喜欢
    • 2015-06-22
    • 1970-01-01
    • 2016-06-25
    • 2015-11-26
    • 1970-01-01
    • 2013-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多