MaskLayer實(shí)例(刮獎demo)

今天在簡書上看到了一個刮刮樂的demo,作者的思路很有意思,推薦大家去閱讀下。

最近的項目要做im,有下面的場景:

聊天發(fā)圖片.png

這個氣泡的實(shí)現(xiàn)用到了maskLayer,正好可以實(shí)現(xiàn)一個刮獎的demo。于是乎...搞起!

maskLayer介紹

CALayer有一個mask屬性,這便是我們今天的主角??聪滤歉墒裁吹?

/* A layer whose alpha channel is used as a mask to select between the
 * layer's background and the result of compositing the layer's
 * contents with its filtered background. Defaults to nil. When used as
 * a mask the layer's `compositingFilter' and `backgroundFilters'
 * properties are ignored. When setting the mask to a new layer, the
 * new layer must have a nil superlayer, otherwise the behavior is
 * undefined. Nested masks (mask layers with their own masks) are
 * unsupported. */

@property(nullable, strong) CALayer *mask;

簡單理解就是,如果mask不為nil,那么mask以內(nèi)的區(qū)域會顯示layer本身的內(nèi)容,mask以外的區(qū)域會顯示layer后面的內(nèi)容(相當(dāng)于透明)。這里需要兩點(diǎn)注意:

  • mask必須是一個獨(dú)立的layer,不能擁有super layer
  • 不支持嵌套的mask

上圖

demo.gif

View Hierarchy

沒用Reveal,大伙湊活看吧。

view.png
view hierarchy.png

主要三個View:

  • 背景UIImageView--scratch_bg.png(藍(lán)色背景)
  • ScratchView--設(shè)置mask的自定義view
  • UILabel--顯示刮獎結(jié)果,可以根據(jù)具體需求改為其他view

工作原理

如上所示,mask的設(shè)置在ScratchView中,捕獲手指的移動創(chuàng)建mask的layer并設(shè)置給ScratchView。
這樣一來,mask區(qū)域內(nèi)顯示ScratchView本身的內(nèi)容(ScratchView的子view),mask區(qū)域外繼續(xù)顯示ScratchView后面的內(nèi)容(背景圖)。

如何繪制maskLayer?

首先要明白,mask是一個CALayer,創(chuàng)建一個不規(guī)則的CALayer首選CAShapeLayer ;

其次,CAShapeLayer通過path來定義形狀,我們的目標(biāo)就是把用戶的每一次移動軌跡通過path來表示;

再其次,用戶移動軌跡必然不能通過一個path來表示(做path的union操作......想都不敢想),所以我們把每個用戶軌跡用一個CAShapeLayer表示,然后通過addSublayer方法添加到mask中。

最后,明白了我們的繪制方法,剩下最后的問題就是如何繪制path。為了體現(xiàn)出用戶移動軌跡的圓滑邊界和手指寬度,我們需要在每次移動之后繪制一個從上一次起點(diǎn)到此次終點(diǎn)的圓柱型path,如下圖:


繪制path.png

Code

ScratchView.h定義如下:

#import <UIKit/UIKit.h>

IB_DESIGNABLE
@interface ScratchView : UIView
@property (nonatomic) IBInspectable CGFloat scratchLineWidth;
@end

scratchLineWidth用來表示圓柱形軌跡的寬度。

ScratchView.m:

#import "ScratchView.h"

@interface ScratchView ()
{
    CGPoint startPoint;
}
@property (nonatomic, strong) CALayer * maskLayer;
@end

@implementation ScratchView

- (void) awakeFromNib
{
    [super awakeFromNib];
    self.layer.mask = [CALayer new];
}

- (void) touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [[event allTouches] anyObject];
    CGPoint touchLocation = [touch locationInView:self];
    startPoint = touchLocation;
}

- (void) touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [[event allTouches] anyObject];
    CGPoint touchLocation = [touch locationInView:self];
    CAShapeLayer * layer = [CAShapeLayer new];
    layer.path = [self getPathFromPointA:startPoint toPointB:touchLocation].CGPath;
    if(!_maskLayer){
        _maskLayer = [CALayer new];
    }
    [_maskLayer addSublayer:layer];
    
    self.layer.mask = _maskLayer;
    startPoint = touchLocation;
}

- (void) touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [[event allTouches] anyObject];
    CGPoint touchLocation = [touch locationInView:self];
    CAShapeLayer * layer = [CAShapeLayer new];
    layer.path = [self getPathFromPointA:startPoint toPointB:touchLocation].CGPath;
    if(!_maskLayer){
        _maskLayer = [CALayer new];
    }
    [_maskLayer addSublayer:layer];
    
    self.layer.mask = _maskLayer;
}

- (UIBezierPath *) getPathFromPointA:(CGPoint)a toPointB : (CGPoint) b
{
    UIBezierPath * path = [UIBezierPath new];
    UIBezierPath * curv1 = [UIBezierPath bezierPathWithArcCenter:a radius:self.scratchLineWidth startAngle:angleBetweenPoints(a, b)+M_PI_2 endAngle:angleBetweenPoints(a, b)+M_PI+M_PI_2 clockwise:b.x >= a.x];
    [path appendPath:curv1];
    UIBezierPath * curv2 = [UIBezierPath bezierPathWithArcCenter:b radius:self.scratchLineWidth startAngle:angleBetweenPoints(a, b)-M_PI_2 endAngle:angleBetweenPoints(a, b)+M_PI_2 clockwise:b.x >= a.x];
    [path addLineToPoint:CGPointMake(b.x * 2 - curv2.currentPoint.x, b.y * 2 - curv2.currentPoint.y)];
    [path appendPath:curv2];
    [path addLineToPoint:CGPointMake(a.x * 2 - curv1.currentPoint.x, a.y * 2 - curv1.currentPoint.y)];
    [path closePath];
    return path;
}

CGFloat angleBetweenPoints(CGPoint first, CGPoint second) {
    CGFloat height = second.y - first.y;
    CGFloat width = first.x - second.x;
    CGFloat rads = atan(height/width);
    return -rads;
}

@end

- (void) awakeFromNib中執(zhí)行self.layer.mask = [CALayer new];可以把當(dāng)前view設(shè)置為全透。
- (UIBezierPath *) getPathFromPointA:(CGPoint)a toPointB : (CGPoint) b方法負(fù)責(zé)生成兩點(diǎn)之間的圓柱型path。
每當(dāng)用戶移動一小段距離之后,我們便創(chuàng)建一個新的CAShapeLayer,添加到mask中。

以上源碼

Next

  • 因為在touchesMovedtouchesEnded會創(chuàng)建新對象并且add到mask中,無疑會持續(xù)消耗內(nèi)存,還是要考慮添加一些path union之類的策略。準(zhǔn)備從CALayer- (BOOL)containsPoint:(CGPoint)p;方法入手。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載:http://www.itdecent.cn/p/32fcadd12108 每個UIView有一個伙伴稱為l...
    F麥子閱讀 6,567評論 0 13
  • 在iOS中隨處都可以看到絢麗的動畫效果,實(shí)現(xiàn)這些動畫的過程并不復(fù)雜,今天將帶大家一窺ios動畫全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,688評論 6 30
  • 在iOS中隨處都可以看到絢麗的動畫效果,實(shí)現(xiàn)這些動畫的過程并不復(fù)雜,今天將帶大家一窺iOS動畫全貌。在這里你可以看...
    F麥子閱讀 5,261評論 5 13
  • 每個UIView有一個伙伴稱為layer,一個CALayer。UIView實(shí)際上并沒有把自己畫到屏幕上;它繪制本身...
    shenzhenboy閱讀 3,252評論 0 17
  • 金聲破曙司晨報, 禽中君子今古褒。 清啼一唱雄姿傲, 尤見風(fēng)流骨氣高。
    白沙六閱讀 783評論 0 0

友情鏈接更多精彩內(nèi)容