剪切变形我想要做类似如下的内容:与动画
How to mask an image in IOS sdk?
我想覆盖黑色半透明整个屏幕。然后,我想从半透明的黑色遮盖物中划出一个圆圈,以便清晰地看清楚。我这样做是为了突出显示教程的屏幕部分。
然后我想要将切出的圆圈设置为屏幕其他部分的动画。我也希望能够垂直拉伸水平切割圆圈&,就像使用通用按钮背景图像一样。
剪切变形我想要做类似如下的内容:与动画
How to mask an image in IOS sdk?
我想覆盖黑色半透明整个屏幕。然后,我想从半透明的黑色遮盖物中划出一个圆圈,以便清晰地看清楚。我这样做是为了突出显示教程的屏幕部分。
然后我想要将切出的圆圈设置为屏幕其他部分的动画。我也希望能够垂直拉伸水平切割圆圈&,就像使用通用按钮背景图像一样。
这不是一个简单的。我可以为你带来一点好处。这是棘手的动画。下面是一些代码的输出我扔在一起:
的代码是这样的:
- (void)viewDidLoad
{
[super viewDidLoad];
// Create a containing layer and set it contents with an image
CALayer *containerLayer = [CALayer layer];
[containerLayer setBounds:CGRectMake(0.0f, 0.0f, 500.0f, 320.0f)];
[containerLayer setPosition:[[self view] center]];
UIImage *image = [UIImage imageNamed:@"cool"];
[containerLayer setContents:(id)[image CGImage]];
// Create your translucent black layer and set its opacity
CALayer *translucentBlackLayer = [CALayer layer];
[translucentBlackLayer setBounds:[containerLayer bounds]];
[translucentBlackLayer setPosition:
CGPointMake([containerLayer bounds].size.width/2.0f,
[containerLayer bounds].size.height/2.0f)];
[translucentBlackLayer setBackgroundColor:[[UIColor blackColor] CGColor]];
[translucentBlackLayer setOpacity:0.45];
[containerLayer addSublayer:translucentBlackLayer];
// Create a mask layer with a shape layer that has a circle path
CAShapeLayer *maskLayer = [CAShapeLayer layer];
[maskLayer setBorderColor:[[UIColor purpleColor] CGColor]];
[maskLayer setBorderWidth:5.0f];
[maskLayer setBounds:[containerLayer bounds]];
// When you create a path, remember that origin is in upper left hand
// corner, so you have to treat it as if it has an anchor point of 0.0,
// 0.0
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:
CGRectMake([translucentBlackLayer bounds].size.width/2.0f - 100.0f,
[translucentBlackLayer bounds].size.height/2.0f - 100.0f,
200.0f, 200.0f)];
// Append a rectangular path around the mask layer so that
// we can use the even/odd fill rule to invert the mask
[path appendPath:[UIBezierPath bezierPathWithRect:[maskLayer bounds]]];
// Set the path's fill color since layer masks depend on alpha
[maskLayer setFillColor:[[UIColor blackColor] CGColor]];
[maskLayer setPath:[path CGPath]];
// Center the mask layer in the translucent black layer
[maskLayer setPosition:
CGPointMake([translucentBlackLayer bounds].size.width/2.0f,
[translucentBlackLayer bounds].size.height/2.0f)];
// Set the fill rule to even odd
[maskLayer setFillRule:kCAFillRuleEvenOdd];
// Set the translucent black layer's mask property
[translucentBlackLayer setMask:maskLayer];
// Add the container layer to the view so we can see it
[[[self view] layer] addSublayer:containerLayer];
}
你将不得不动画,你可以根据用户的输入建立遮罩层,但它会有点具有挑战性。注意我将矩形路径附加到圆形路径的行,然后在形状图层上稍后几行设置填充规则。这些是使倒置掩模成为可能的原因。如果你将它们留在外面,你会在圆的中心显示半透明的黑色,然后在外面显示没有任何东西(如果有意义的话)。
也许尝试玩这个代码有点看看你是否可以得到它的动画。随着时间的推移,我会再玩一次,但这是一个非常有趣的问题。很想看到一个完整的解决方案。
UPDATE:所以这里是它的另一种刺。这里的麻烦在于,这个让半透明面具看起来不是黑色而是白色,但好处是这个圆形可以非常容易地进行动画制作。
这一个建立一个复合层与半透明层和圆层是兄弟姐妹内的父层作为掩码。
我添加了一个基本的动画这一个,所以我们可以看到圈层动画。
- (void)viewDidLoad
{
[super viewDidLoad];
CGRect baseRect = CGRectMake(0.0f, 0.0f, 500.0f, 320.0f);
CALayer *containerLayer = [CALayer layer];
[containerLayer setBounds:baseRect];
[containerLayer setPosition:[[self view] center]];
UIImage *image = [UIImage imageNamed:@"cool"];
[containerLayer setContents:(id)[image CGImage]];
CALayer *compositeMaskLayer = [CALayer layer];
[compositeMaskLayer setBounds:baseRect];
[compositeMaskLayer setPosition:CGPointMake([containerLayer bounds].size.width/2.0f, [containerLayer bounds].size.height/2.0f)];
CALayer *translucentLayer = [CALayer layer];
[translucentLayer setBounds:baseRect];
[translucentLayer setBackgroundColor:[[UIColor blackColor] CGColor]];
[translucentLayer setPosition:CGPointMake([containerLayer bounds].size.width/2.0f, [containerLayer bounds].size.height/2.0f)];
[translucentLayer setOpacity:0.35];
[compositeMaskLayer addSublayer:translucentLayer];
CAShapeLayer *circleLayer = [CAShapeLayer layer];
UIBezierPath *circlePath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0.0f, 0.0f, 200.0f, 200.0f)];
[circleLayer setBounds:CGRectMake(0.0f, 0.0f, 200.0f, 200.0f)];
[circleLayer setPosition:CGPointMake([containerLayer bounds].size.width/2.0f, [containerLayer bounds].size.height/2.0f)];
[circleLayer setPath:[circlePath CGPath]];
[circleLayer setFillColor:[[UIColor blackColor] CGColor]];
[compositeMaskLayer addSublayer:circleLayer];
[containerLayer setMask:compositeMaskLayer];
[[[self view] layer] addSublayer:containerLayer];
CABasicAnimation *posAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
[posAnimation setFromValue:[NSValue valueWithCGPoint:[circleLayer position]]];
[posAnimation setToValue:[NSValue valueWithCGPoint:CGPointMake([circleLayer position].x + 100.0f, [circleLayer position].y + 100)]];
[posAnimation setDuration:1.0f];
[posAnimation setRepeatCount:INFINITY];
[posAnimation setAutoreverses:YES];
[circleLayer addAnimation:posAnimation forKey:@"position"];
}
(UPDATE:另请参阅my other answer介绍如何设置多个独立的,重叠的孔。)
让我们用一个普通的老UIView
用半透明黑色backgroundColor
,并给其层口罩从中间切出一个洞。我们需要一个实例变量来引用孔观点:
@implementation ViewController {
UIView *holeView;
}
加载主视图后,我们希望孔视图中添加作为一个子视图:
- (void)viewDidLoad {
[super viewDidLoad];
[self addHoleSubview];
}
因为我们想移动的无论位于何处,都可以很方便地使孔视图非常大,以覆盖其余内容。我们将它做成10000x10000。 (这不会占用任何更多的内存,因为iOS不自动分配一个位图的看法。)
- (void)addHoleSubview {
holeView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10000, 10000)];
holeView.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.5];
holeView.autoresizingMask = 0;
[self.view addSubview:holeView];
[self addMaskToHoleView];
}
现在,我们需要补充的是切一个洞出洞视图的面具。我们将通过创建一个复合路径来完成此操作,该复合路径由一个巨大的矩形组成,其中央有一个较小的圆。我们将用黑色填充路径,使圆圈不填充,因此透明。黑色部分的alpha = 1.0,所以它使孔视图的背景颜色显示。透明部分具有alpha = 0.0,因此部分孔视图也是透明的。
- (void)addMaskToHoleView {
CGRect bounds = holeView.bounds;
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = bounds;
maskLayer.fillColor = [UIColor blackColor].CGColor;
static CGFloat const kRadius = 100;
CGRect const circleRect = CGRectMake(CGRectGetMidX(bounds) - kRadius,
CGRectGetMidY(bounds) - kRadius,
2 * kRadius, 2 * kRadius);
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:circleRect];
[path appendPath:[UIBezierPath bezierPathWithRect:bounds]];
maskLayer.path = path.CGPath;
maskLayer.fillRule = kCAFillRuleEvenOdd;
holeView.layer.mask = maskLayer;
}
请注意,我已将圆圈放在10000x10000视图的中心。这意味着我们可以设置holeView.center
来设置相对于其他内容的圆的中心。因此,例如,我们可以很容易地以动画上下在主视图:
- (void)viewDidLayoutSubviews {
CGRect const bounds = self.view.bounds;
holeView.center = CGPointMake(CGRectGetMidX(bounds), 0);
// Defer this because `viewDidLayoutSubviews` can happen inside an
// autorotation animation block, which overrides the duration I set.
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:2 delay:0
options:UIViewAnimationOptionRepeat
| UIViewAnimationOptionAutoreverse
animations:^{
holeView.center = CGPointMake(CGRectGetMidX(bounds),
CGRectGetMaxY(bounds));
} completion:nil];
});
}
这里是什么样子:
但它在现实生活中更顺畅。
你可以找到一个完整的工作测试项目in this github repository。
Rob,大半透明层的好主意。 +1 – 2013-03-13 16:56:30
这非常酷。对我来说,这里的关键是这两行: [path appendPath:[UIBezierPath bezierPathWithRect:bounds]]; maskLayer.fillRule = kCAFillRuleEvenOdd; 我只能得到“黑暗”的聚光灯,并试图弄清楚如何在罗布这里找到如何获得聚光灯的路径。 Rob的代码在最后追加边界路径有效地进行反演。可悲的是,我想要多个聚光灯,其中一些重叠,但我不想在这些路径的交叉点产生偶数的效果,所以它不适用于我。尽管如此,仍然是一个很酷的技术 – 2014-03-07 05:26:13
@SimoneManganelli [检查我的新答案在这里。](http://stackoverflow.com/a/22262689/77567) – 2014-03-07 23:27:43
这里有一个答案适用于多个独立的,可能重叠的聚光灯。
我建立了我的视图层次是这样的:
SpotlightsView with black background
UIImageView with `alpha`=.5 (“dim view”)
UIImageView with shape layer mask (“bright view”)
在朦胧的视图将显示为灰色,因为其alpha与顶层视图的黑色混合的形象。
明亮的视图不变暗,但它只显示它的蒙版在哪里让它。所以我只是将面具设置为包含聚光灯区域和其他地方。
这里是什么样子:
我将实现它的UIView
这个接口的子类:
// SpotlightsView.h
#import <UIKit/UIKit.h>
@interface SpotlightsView : UIView
@property (nonatomic, strong) UIImage *image;
- (void)addDraggableSpotlightWithCenter:(CGPoint)center radius:(CGFloat)radius;
@end
我需要QuartzCore(也称为核心动画)和Objective-C运行时间来实现它:
// SpotlightsView.m
#import "SpotlightsView.h"
#import <QuartzCore/QuartzCore.h>
#import <objc/runtime.h>
我需要为子视图实例变量,遮罩层,以及个别聚光灯路径的数组:
@implementation SpotlightsView {
UIImageView *_dimImageView;
UIImageView *_brightImageView;
CAShapeLayer *_mask;
NSMutableArray *_spotlightPaths;
}
为了实现image
财产,我只是通过它传递给你的形象子视图:
#pragma mark - Public API
- (void)setImage:(UIImage *)image {
_dimImageView.image = image;
_brightImageView.image = image;
}
- (UIImage *)image {
return _dimImageView.image;
}
要添加一个可拖动的聚光灯下,我创建概述了聚光灯下的路径,将其添加到阵列,并标记自己为需要布局:
- (void)addDraggableSpotlightWithCenter:(CGPoint)center radius:(CGFloat)radius {
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(center.x - radius, center.y - radius, 2 * radius, 2 * radius)];
[_spotlightPaths addObject:path];
[self setNeedsLayout];
}
我需要重写一些方法UIView
来处理初始化和布局。我会处理或者正在创建编程或在厦门国际银行或故事情节通过委派共同初始化代码的私有方法:
#pragma mark - UIView overrides
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self commonInit];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
[self commonInit];
}
return self;
}
我会处理在不同的辅助方法为每个子视图布局:
- (void)layoutSubviews {
[super layoutSubviews];
[self layoutDimImageView];
[self layoutBrightImageView];
}
要将聚光灯在被触摸时拖动,我需要覆盖一些UIResponder
方法。我想单独处理每个触摸,所以我只是遍历更新的接触,传递每一个到一个辅助方法:
#pragma mark - UIResponder overrides
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches){
[self touchBegan:touch];
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches){
[self touchMoved:touch];
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
[self touchEnded:touch];
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
[self touchEnded:touch];
}
}
现在,我将实现私有外观和布局方法。
#pragma mark - Implementation details - appearance/layout
首先我会做通用的初始化代码。我想我的背景色设置为黑色,因为这是使暗淡的图像视图暗淡的一部分,我想支持多点触摸:
- (void)commonInit {
self.backgroundColor = [UIColor blackColor];
self.multipleTouchEnabled = YES;
[self initDimImageView];
[self initBrightImageView];
_spotlightPaths = [NSMutableArray array];
}
我的两个图像子视图将主要的配置方法相同,所以我会打电话给另一个私有方法来创建模糊图像视图,然后调整它实际上是暗淡:
- (void)initDimImageView {
_dimImageView = [self newImageSubview];
_dimImageView.alpha = 0.5;
}
我会打电话给同一个辅助方法来创造美好的景色,再加入其屏蔽子层:
- (void)initBrightImageView {
_brightImageView = [self newImageSubview];
_mask = [CAShapeLayer layer];
_brightImageView.layer.mask = _mask;
}
创建两个图像视图助手方法设置内容模式,并增加了新的视图作为一个子视图:
- (UIImageView *)newImageSubview {
UIImageView *subview = [[UIImageView alloc] init];
subview.contentMode = UIViewContentModeScaleAspectFill;
[self addSubview:subview];
return subview;
}
奠定了昏暗的图像来看,我只需要它的框架设置为我的界限:
- (void)layoutDimImageView {
_dimImageView.frame = self.bounds;
}
奠定了明亮的图像来看,我需要它的框架设置为我的界限,我需要更新其屏蔽层的路径是个别聚光灯路径的工会:
- (void)layoutBrightImageView {
_brightImageView.frame = self.bounds;
UIBezierPath *unionPath = [UIBezierPath bezierPath];
for (UIBezierPath *path in _spotlightPaths) {
[unionPath appendPath:path];
}
_mask.path = unionPath.CGPath;
}
请注意,这不是包含每个点的真正联合。它依靠填充模式(默认值,kCAFillRuleNonZero
)确保重复封闭的点包含在蒙版中。
接下来,触摸处理。
#pragma mark - Implementation details - touch handling
UIKit的时候给我发一个新的触摸,我会找到包含触摸个别聚光灯路径,该路径连接到触摸作为关联的对象。这意味着我需要一个相关联的对象键,它只是需要我可以采取的地址一些私人的事情:
static char kSpotlightPathAssociatedObjectKey;
这里我居然找到路径,并将其附加的触感。如果触摸超出我的任何聚光灯下的路径,我忽略它:
- (void)touchBegan:(UITouch *)touch {
UIBezierPath *path = [self firstSpotlightPathContainingTouch:touch];
if (path == nil)
return;
objc_setAssociatedObject(touch, &kSpotlightPathAssociatedObjectKey,
path, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
当UIKit的告诉我触摸移动,我看如果触摸具有附加的路径。如果是这样,我翻译(幻灯片)的路径,自从我上次看到它以来触摸移动的量。然后我标志自己的布局:
- (void)touchMoved:(UITouch *)touch {
UIBezierPath *path = objc_getAssociatedObject(touch,
&kSpotlightPathAssociatedObjectKey);
if (path == nil)
return;
CGPoint point = [touch locationInView:self];
CGPoint priorPoint = [touch previousLocationInView:self];
[path applyTransform:CGAffineTransformMakeTranslation(
point.x - priorPoint.x, point.y - priorPoint.y)];
[self setNeedsLayout];
}
当触摸结束或取消时,我实际上不需要做任何事情。 Objective-C的运行时将取消相关联的连接路径(如果有的话)自动:
- (void)touchEnded:(UITouch *)touch {
// Nothing to do
}
要查找包含触摸,我只是遍历所有的聚光灯路径的路径,询问每一个,如果它包含触摸:
- (UIBezierPath *)firstSpotlightPathContainingTouch:(UITouch *)touch {
CGPoint point = [touch locationInView:self];
for (UIBezierPath *path in _spotlightPaths) {
if ([path containsPoint:point])
return path;
}
return nil;
}
@end
我已经上传了一个完整的演示to github。
您的第一个解决方案只允许使用单个遮罩,而您的第二个解决方案需要使用背景图像。我将如何去获得多个面具,但没有图像?我想这应该是你的解决方案的组合?我在我现有的视图上需要多个剪贴画。 – Oren 2014-09-15 22:20:16
如果您拥有子视图的任意视图,并且您想要多个聚光灯,那很复杂。我现在不想写这个问题的又一个非常复杂的答案。 – 2014-09-15 23:03:55
我可以告诉你,我会用[[ - [UIView snapshotViewAfterScreenUpdates:]'](https://developer.apple.com/library/ios/documentation/uikit/reference/uiview_class/uiview/uiview.html# // apple_ref/occ/instm/UIView/snapshotViewAfterScreenUpdates :)替换背景图片。 – 2014-09-15 23:05:17
我一直在努力解决这个问题,并在这里发现了一些很棒的帮助,所以我想我会分享我的解决方案,结合我在网上找到的一些不同的想法。我添加的一个附加功能是为了切出渐变效果。这个解决方案的附加好处是它可以与任何UIView一起使用,而不仅仅与图像一起使用。
第一子UIView
,除了帧黑掉你想要的一切切出:
// BlackOutView.h
@interface BlackOutView : UIView
@property (nonatomic, retain) UIColor *fillColor;
@property (nonatomic, retain) NSArray *framesToCutOut;
@end
// BlackOutView.m
@implementation BlackOutView
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(context, kCGBlendModeDestinationOut);
for (NSValue *value in self.framesToCutOut) {
CGRect pathRect = [value CGRectValue];
UIBezierPath *path = [UIBezierPath bezierPathWithRect:pathRect];
// change to this path for a circular cutout if you don't want a gradient
// UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:pathRect];
[path fill];
}
CGContextSetBlendMode(context, kCGBlendModeNormal);
}
@end
如果你不想模糊效果,那么你可以交换路径的椭圆形之一,并跳过模糊面膜下面。否则,切口将是正方形的,并以圆形渐变填充。
创建中心的梯度形状的透明和黑色逐渐淡化:
// BlurFilterMask.h
@interface BlurFilterMask : CAShapeLayer
@property (assign) CGPoint origin;
@property (assign) CGFloat diameter;
@property (assign) CGFloat gradient;
@end
// BlurFilterMask.m
@implementation CRBlurFilterMask
- (void)drawInContext:(CGContextRef)context
{
CGFloat gradientWidth = self.diameter * 0.5f;
CGFloat clearRegionRadius = self.diameter * 0.25f;
CGFloat blurRegionRadius = clearRegionRadius + gradientWidth;
CGColorSpaceRef baseColorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat colors[8] = { 0.0f, 0.0f, 0.0f, 0.0f, // Clear region colour.
0.0f, 0.0f, 0.0f, self.gradient }; // Blur region colour.
CGFloat colorLocations[2] = { 0.0f, 0.4f };
CGGradientRef gradient = CGGradientCreateWithColorComponents (baseColorSpace, colors, colorLocations, 2);
CGContextDrawRadialGradient(context, gradient, self.origin, clearRegionRadius, self.origin, blurRegionRadius, kCGGradientDrawsAfterEndLocation);
CGColorSpaceRelease(baseColorSpace);
CGGradientRelease(gradient);
}
@end
现在你只需要将这两个一起调用,并传入所需的UIView
小号切口
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self addMaskInViews:@[self.viewCutout1, self.viewCutout2]];
}
- (void) addMaskInViews:(NSArray *)viewsToCutOut
{
NSMutableArray *frames = [NSMutableArray new];
for (UIView *view in viewsToCutOut) {
view.hidden = YES; // hide the view since we only use their bounds
[frames addObject:[NSValue valueWithCGRect:view.frame]];
}
// Create the overlay passing in the frames we want to cut out
BlackOutView *overlay = [[BlackOutView alloc] initWithFrame:self.view.frame];
overlay.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.8];
overlay.framesToCutOut = frames;
[self.view insertSubview:overlay atIndex:0];
// add a circular gradients inside each view
for (UIView *maskView in viewsToCutOut)
{
BlurFilterMask *blurFilterMask = [BlurFilterMask layer];
blurFilterMask.frame = maskView.frame;
blurFilterMask.gradient = 0.8f;
blurFilterMask.diameter = MIN(maskView.frame.size.width, maskView.frame.size.height);
blurFilterMask.origin = CGPointMake(maskView.frame.size.width/2, maskView.frame.size.height/2);
[self.view.layer addSublayer:blurFilterMask];
[blurFilterMask setNeedsDisplay];
}
}
如果您只是想要即插即用的东西,我向CocoaPods添加了一个库,允许您创建带有矩形/圆孔的叠加层,允许用户与覆盖层后面的视图进行交互。这是其他答案中使用的类似策略的Swift实现。我用它来为我们的应用程序创建一个本教程:
库被称为TAOverlayView,并使用Apache 2.0的开源。
注意:我还没有实现移动孔(除非像在其他答案中一样移动整个覆盖图)。
嗨马特!你还可以告诉我如何将图像保存为圆形区域为png。 – 2013-12-29 07:03:58