MaskLayer实例(刮奖demo)

作者: joshualiyz | 来源:发表于2016-06-28 17:00 被阅读856次

今天在简书上看到了一个刮刮乐的demo,作者的思路很有意思,推荐大家去阅读下。

最近的项目要做im,有下面的场景:

聊天发图片.png
这个气泡的实现用到了maskLayer,正好可以实现一个刮奖的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以内的区域会显示layer本身的内容,mask以外的区域会显示layer后面的内容(相当于透明)。这里需要两点注意:

  • mask必须是一个独立的layer,不能拥有super layer
  • 不支持嵌套的mask

上图

demo.gif

View Hierarchy

没用Reveal,大伙凑活看吧。

view.png view hierarchy.png

主要三个View:

  • 背景UIImageView--scratch_bg.png(蓝色背景)
  • ScratchView--设置mask的自定义view
  • UILabel--显示刮奖结果,可以根据具体需求改为其他view

工作原理

如上所示,mask的设置在ScratchView中,捕获手指的移动创建mask的layer并设置给ScratchView。
这样一来,mask区域内显示ScratchView本身的内容(ScratchView的子view),mask区域外继续显示ScratchView后面的内容(背景图)。

如何绘制maskLayer?

首先要明白,mask是一个CALayer,创建一个不规则的CALayer首选CAShapeLayer

其次,CAShapeLayer通过path来定义形状,我们的目标就是把用户的每一次移动轨迹通过path来表示;

再其次,用户移动轨迹必然不能通过一个path来表示(做path的union操作......想都不敢想),所以我们把每个用户轨迹用一个CAShapeLayer表示,然后通过addSublayer方法添加到mask中。

最后,明白了我们的绘制方法,剩下最后的问题就是如何绘制path。为了体现出用户移动轨迹的圆滑边界和手指宽度,我们需要在每次移动之后绘制一个从上一次起点到此次终点的圆柱型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中执行self.layer.mask = [CALayer new];可以把当前view设置为全透。
- (UIBezierPath *) getPathFromPointA:(CGPoint)a toPointB : (CGPoint) b方法负责生成两点之间的圆柱型path。
每当用户移动一小段距离之后,我们便创建一个新的CAShapeLayer,添加到mask中。

以上源码

Next

  • 因为在touchesMovedtouchesEnded会创建新对象并且add到mask中,无疑会持续消耗内存,还是要考虑添加一些path union之类的策略。准备从CALayer- (BOOL)containsPoint:(CGPoint)p;方法入手。

相关文章

  • MaskLayer实例(刮奖demo)

    今天在简书上看到了一个刮刮乐的demo,作者的思路很有意思,推荐大家去阅读下。 最近的项目要做im,有下面的场景:...

  • 刮奖

    需求 刮开浮层显示奖品没有中奖:显示遗憾没有中奖中奖:显示奖品名字刮开后与后台进行数据交互,告知后台是否中奖 原理...

  • 刮奖

    昨天傍晚因下雨,没去广场转。于是就去郑辉逛超市,楼上楼下逛了一会儿,买点日常生活用品、蔬菜等。结帐走到出口...

  • 刮奖

    今天中午天气很热,想喝酸奶,我迫不及待的打开冰箱,发现冰箱里只有最后一大包了,我比姐姐睇先拿出那一大包酸...

  • 刮奖

    ^_^ 刮奖,抠出个“谢” (⊙o⊙)甩出奖票

  • 迷恋了刮刮奖

    怎么办!

  • 100-days-Coding - day12

    day12-0827 html - 刮刮奖 刮刮奖的整体思路,就是canvas的动态绘制「事件驱动」 使用两个ca...

  • iOS 刮奖

    前段时间公司项目提了个刮奖需求,网上找了很多,不过大多不能用,或者不合适。所以参考网友代码自己写了一个 D...

  • 刮奖人生

    小时候刮奖 刮出谢字还不扔 非要把谢谢惠顾 都刮的干干净净才舍得放手 和后来太多的事一模一样

  • 刮奖后

    接女儿回家的路上,她认真地说:"妈妈,如果我中了30万,一定会把钱分给你和爸爸。" 我笑着说:"好呀!真是孝顺孩子...

网友评论

  • Zrock:兄台,不知道有没有考虑,刮开范围,因为刮奖,肯定不需要全部刮开的
    joshualiyz:@Zrock demo只是提供一个解决思路和masklayer的例子。具体需求还是看具体情况。 :smiley:
  • 虚心若愚:请问git动图 怎么搞得。
    joshualiyz:@虚心若愚 我用的Mac上的licecap
  • Yehuaqiang:demo用atan崩了换个atan2m.m
    Yehuaqiang:@joshualiyz 是的,偶然发生当width为0时候atan会崩 :smiley:
    joshualiyz:@Yehuaqiang 多谢提醒!不过还是建议用atan,处理下边界情况即可。因为我们算角度只是用于画弧,如果用atan2的话,画弧的代码也需要改一下。

本文标题:MaskLayer实例(刮奖demo)

本文链接:https://www.haomeiwen.com/subject/ihytjttx.html