Создание анимируемого полупрозрачного наложения со слоями Core Animation - PullRequest
6 голосов
/ 25 февраля 2012

Я создаю прожектор, который перемещается по содержимому в моем приложении, например:

Spotlight A Spotlight B

В примере приложения (показано выше) фоновый слойсиний, и у меня есть слой поверх него, который затемняет все, кроме круга, который показывает это нормально.У меня это работает (вы можете увидеть, как в коде ниже).В моем реальном приложении есть не только синее, но и другое содержимое CAL, а не только синее.

Вот моя проблема: он не анимируется.Я использую CGContext рисунок, чтобы создать круг (который является пустым пятном в другом черном слое).Когда вы нажимаете кнопку в моем примере приложения, я рисую круг другого размера в другом месте.

Я бы хотел, чтобы это плавно переводилось и масштабировалось, а не прыгало, как это происходит в настоящее время.Это может потребовать другого метода создания эффекта прожектора, или может быть способ, которым я не знаю, чтобы неявно анимировать вызов -drawLayer:inContext:.

Образец приложения легко создать:

  1. Создание нового приложения Какао (с использованием ARC)
  2. Добавление структуры Quartz
  3. Перетащите пользовательский вид и кнопку на XIB
  4. Ссылка на пользовательскийview для нового класса (SpotlightView) с кодом ниже
  5. Delete SpotlightView.h, поскольку я включил его содержимое в SpotlightView.m
  6. Установите для выхода кнопки действие -moveSpotlight:

Обновление (свойство mask)

Мне нравится предложение Дэвида Реннквиста в комментариях использовать свойство затемненного слоя mask, чтобы вырезатьдыра, которую я мог бы затем самостоятельно передвигать.Проблема в том, что по какой-то причине свойство mask работает противоположно тому, как я ожидаю, что маска будет работать.Когда я указываю круговую маску, все, что появляется, это круг.Я ожидал, что маска будет работать противоположным образом, маскируя область с помощью 0 alpha.

Маскировка кажется правильным способом, но если мне нужно заполнить весь слой и вырезатьиз дыры, тогда я могу сделать это так, как я первоначально написал.Кто-нибудь знает, как инвертировать свойство -[CALayer mask], чтобы нарисованная область была вырезана из изображения слоя?

/ Update

Вот код дляSpotlightView:

//
//  SpotlightView.m
//

#import <Quartz/Quartz.h>

@interface SpotlightView : NSView
- (IBAction)moveSpotlight:(id)sender;
@end

@interface SpotlightView ()
@property (strong) CALayer *spotlightLayer;
@property (assign) CGRect   highlightRect;
@end


@implementation SpotlightView

@synthesize spotlightLayer;
@synthesize highlightRect;

- (id)initWithFrame:(NSRect)frame {
    if ((self = [super initWithFrame:frame])) {
        self.wantsLayer = YES;

        self.highlightRect = CGRectNull;

        self.spotlightLayer = [CALayer layer];
        self.spotlightLayer.frame = CGRectInset(self.layer.bounds, -50, -50);
        self.spotlightLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;

        self.spotlightLayer.opacity = 0.60;
        self.spotlightLayer.delegate = self;

        CIFilter *blurFilter = [CIFilter filterWithName:@"CIGaussianBlur"];
        [blurFilter setValue:[NSNumber numberWithFloat:5.0]
                      forKey:@"inputRadius"];
        self.spotlightLayer.filters = [NSArray arrayWithObject:blurFilter];

        [self.layer addSublayer:self.spotlightLayer];
    }

    return self;
}

- (void)drawRect:(NSRect)dirtyRect {}

- (void)moveSpotlight:(id)sender {
    [self.spotlightLayer setNeedsDisplay];
}

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
    if (layer == self.spotlightLayer) {
        CGContextSaveGState(ctx);

        CGColorRef blackColor = CGColorCreateGenericGray(0.0, 1.0);

        CGContextSetFillColorWithColor(ctx, blackColor);
        CGColorRelease(blackColor);

        CGContextClearRect(ctx, layer.bounds);
        CGContextFillRect(ctx, layer.bounds);

        // Causes the toggling
        if (CGRectIsNull(self.highlightRect) || self.highlightRect.origin.x != 25) {
            self.highlightRect = CGRectMake(25, 25, 100, 100);
        } else {
            self.highlightRect = CGRectMake(NSMaxX(self.layer.bounds) - 50,
                                            NSMaxY(self.layer.bounds) - 50,
                                            25, 25);
        }

        CGRect drawnRect = [layer convertRect:self.highlightRect
                                    fromLayer:self.layer];
        CGMutablePathRef highlightPath = CGPathCreateMutable();
        CGPathAddEllipseInRect(highlightPath, NULL, drawnRect);
        CGContextAddPath(ctx, highlightPath);

        CGContextSetBlendMode(ctx, kCGBlendModeClear);
        CGContextFillPath(ctx);

        CGPathRelease(highlightPath);
        CGContextRestoreGState(ctx);
    }
    else {
        CGColorRef blueColor = CGColorCreateGenericRGB(0, 0, 1.0, 1.0);
        CGContextSetFillColorWithColor(ctx, blueColor);
        CGContextFillRect(ctx, layer.bounds);
        CGColorRelease(blueColor);
    }
}

@end

1 Ответ

6 голосов
/ 29 февраля 2012

Я наконец получил это. Что подтолкнуло меня к ответу, так это слух о классе CAShapeLayer. Сначала я подумал, что это будет более простой способ отрисовки содержимого слоя, нежели отрисовка и очистка содержимого стандарта CALayer. Но я прочитал документацию по свойству path CAShapeLayer, в котором говорилось, что оно может быть анимированным, но неявным образом.

Хотя маска слоя могла бы быть более интуитивной и элегантной, представляется невозможным использовать маску для скрытия части слоя владельца, а не , показывающей часть, и поэтому я не мог ее использовать. Я доволен этим решением, так как довольно ясно, что происходит. Хотелось бы, чтобы в нем использовалась неявная анимация, но код анимации состоит всего из нескольких строк.

Ниже я изменил пример кода из вопроса, чтобы добавить плавную анимацию. (Я удалил код CIFilter, потому что он был посторонним. Решение все еще работает с фильтрами.)

//
//  SpotlightView.m
//

#import <Quartz/Quartz.h>

@interface SpotlightView : NSView
- (IBAction)moveSpotlight:(id)sender;
@end

@interface SpotlightView ()
@property (strong) CAShapeLayer *spotlightLayer;
@property (assign) CGRect   highlightRect;
@end


@implementation SpotlightView

@synthesize spotlightLayer;
@synthesize highlightRect;

- (id)initWithFrame:(NSRect)frame {
    if ((self = [super initWithFrame:frame])) {
        self.wantsLayer = YES;

        self.highlightRect = CGRectNull;

        self.spotlightLayer = [CAShapeLayer layer];
        self.spotlightLayer.frame = self.layer.bounds;
        self.spotlightLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
        self.spotlightLayer.fillRule = kCAFillRuleEvenOdd;

        CGColorRef blackoutColor = CGColorCreateGenericGray(0.0, 0.60);
        self.spotlightLayer.fillColor = blackoutColor;
        CGColorRelease(blackoutColor);

        [self.layer addSublayer:self.spotlightLayer];
    }

    return self;
}

- (void)drawRect:(NSRect)dirtyRect {}

- (CGPathRef)newSpotlightPathInRect:(CGRect)containerRect
                      withHighlight:(CGRect)spotlightRect {
    CGMutablePathRef shape = CGPathCreateMutable();

    CGPathAddRect(shape, NULL, containerRect);

    if (!CGRectIsNull(spotlightRect)) {
        CGPathAddEllipseInRect(shape, NULL, spotlightRect);
    }

    return shape;
}

- (void)moveSpotlight {
    CGPathRef toShape = [self newSpotlightPathInRect:self.spotlightLayer.bounds
                                       withHighlight:self.highlightRect];

    CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
    pathAnimation.fromValue = (__bridge id)self.spotlightLayer.path;
    pathAnimation.toValue   = (__bridge id)toShape;

    [self.spotlightLayer addAnimation:pathAnimation forKey:@"path"];
    self.spotlightLayer.path = toShape;

    CGPathRelease(toShape);
}

- (void)moveSpotlight:(id)sender {
    if (CGRectIsNull(self.highlightRect) || self.highlightRect.origin.x != 25) {
        self.highlightRect = CGRectMake(25, 25, 100, 100);
    } else {
        self.highlightRect = CGRectMake(NSMaxX(self.layer.bounds) - 50,
                                        NSMaxY(self.layer.bounds) - 50,
                                        25, 25);
    }

    [self moveSpotlight];
}

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
    CGColorRef blueColor = CGColorCreateGenericRGB(0, 0, 1.0, 1.0);
    CGContextSetFillColorWithColor(ctx, blueColor);
    CGContextFillRect(ctx, layer.bounds);
    CGColorRelease(blueColor);
}

@end
...