Swift - 刮刮卡效果的实现(附样例)
作者:hangge | 2017-07-13 08:10
刮刮卡是指卡上覆盖有涂层,刮开后才能看到下方的数字和字母密码等文字的卡片。常用于电话卡、游戏卡、抽奖卡、刮刮乐卡等上面。
虽然刮刮卡最早只是实体卡,但现在越来越多的网站或者 App 上的抽奖也会模拟刮刮卡效果。用户在屏幕上划动,就能刮开涂层看到下方信息。比起点击直接开奖,这种方式会让用户更有参与感。
下面演示如何实现一个刮刮卡效果,为了方便使用我会将其封装成组件。
1,实现原理
(1)刮刮卡由两张叠在一起的图片组成。底图显示的是中奖信息,上面覆盖的是涂层图片。
(2)刮开涂层的效果其实就是一个简单的画图板功能。手指滑动时在涂层的相应位置上绘制透明线条,露出下方的底图,看起来就像是刮开涂层一样。
(3)刮奖的过程中还会实时统计出刮开区域占总面积的百分比,其计算方式是“透明像素个数 / 总像素个数”。
2,效果图
(1)手指在屏幕划动,即可刮开涂层露出下方的中奖图片。
(2)在刮奖的过程中,下方会实时显示出当前已刮开面积占总面积的百分比。为了让用户体验更好,有时没必要让用户把整张彩票都完全刮开,通常刮了超过一定面积(比如 75%),就自动将剩余部分全部刮开,
(3)默认使用的笔触是矩形的,这样刮奖痕迹会呈锯齿状,显得更真实些。当然我们也可以使用圆形的笔触。下图显示二者的区别。

3,组件代码
(1)刮刮卡涂层(ScratchMask.swift)
import UIKit
//刮刮卡涂层
class ScratchMask: UIImageView {
//代理对象
weak var delegate: ScratchCardDelegate?
//线条形状
var lineType:CGLineCap!
//线条粗细
var lineWidth: CGFloat!
//保存上一次停留的位置
var lastPoint: CGPoint?
override init(frame: CGRect) {
super.init(frame: frame)
//当前视图可交互
isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//触摸开始
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//多点触摸只考虑第一点
guard let touch = touches.first else {
return
}
//保存当前点坐标
lastPoint = touch.location(in: self)
//调用相应的代理方法
delegate?.scratchBegan?(point: lastPoint!)
}
//滑动
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
//多点触摸只考虑第一点
guard let touch = touches.first, let point = lastPoint, let img = image else {
return
}
//获取最新触摸点坐标
let newPoint = touch.location(in: self)
//清除两点间的涂层
eraseMask(fromPoint: point, toPoint: newPoint)
//保存最新触摸点坐标,供下次使用
lastPoint = newPoint
//计算刮开面积的百分比
let progress = getAlphaPixelPercent(img: img)
//调用相应的代理方法
delegate?.scratchMoved?(progress: progress)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
//多点触摸只考虑第一点
guard touches.first != nil else {
return
}
//调用相应的代理方法
delegate?.scratchEnded?(point: lastPoint!)
}
//清除两点间的涂层
func eraseMask(fromPoint: CGPoint, toPoint: CGPoint) {
//根据size大小创建一个基于位图的图形上下文
UIGraphicsBeginImageContextWithOptions(self.frame.size, false, UIScreen.main.scale)
//先将图片绘制到上下文中
image?.draw(in: self.bounds)
//再绘制线条
let path = CGMutablePath()
path.move(to: fromPoint)
path.addLine(to: toPoint)
let context = UIGraphicsGetCurrentContext()!
context.setShouldAntialias(true)
context.setLineCap(lineType)
context.setLineWidth(lineWidth)
context.setBlendMode(.clear) //混合模式设为清除
context.addPath(path)
context.strokePath()
//将二者混合后的图片显示出来
image = UIGraphicsGetImageFromCurrentImageContext()
//结束图形上下文
UIGraphicsEndImageContext()
}
//获取透明像素占总像素的百分比
private func getAlphaPixelPercent(img: UIImage) -> Float {
//计算像素总个数
let width = Int(img.size.width)
let height = Int(img.size.height)
let bitmapByteCount = width * height
//得到所有像素数据
let pixelData = UnsafeMutablePointer<UInt8>.allocate(capacity: bitmapByteCount)
let colorSpace = CGColorSpaceCreateDeviceGray()
let context = CGContext(data: pixelData,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: width,
space: colorSpace,
bitmapInfo: CGBitmapInfo(rawValue:
CGImageAlphaInfo.alphaOnly.rawValue).rawValue)!
let rect = CGRect(x: 0, y: 0, width: width, height: height)
context.clear(rect)
context.draw(img.cgImage!, in: rect)
//计算透明像素个数
var alphaPixelCount = 0
for x in 0...Int(width) {
for y in 0...Int(height) {
if pixelData[y * width + x] == 0 {
alphaPixelCount += 1
}
}
}
free(pixelData)
return Float(alphaPixelCount) / Float(bitmapByteCount)
}
}
(2)刮刮卡组件、及相关代理协议(ScratchCard.swift)
import UIKit
//刮刮卡代理协议
@objc protocol ScratchCardDelegate {
@objc optional func scratchBegan(point: CGPoint)
@objc optional func scratchMoved(progress: Float)
@objc optional func scratchEnded(point: CGPoint)
}
//刮刮卡
class ScratchCard: UIView {
//涂层
var scratchMask: ScratchMask!
//底层券面图片
var couponImageView: UIImageView!
//刮刮卡代理对象
weak var delegate: ScratchCardDelegate?
{
didSet
{
scratchMask.delegate = delegate
}
}
public init(frame: CGRect, couponImage: UIImage, maskImage: UIImage,
scratchWidth: CGFloat = 15, scratchType: CGLineCap = .square) {
super.init(frame: frame)
let childFrame = CGRect(x: 0, y: 0, width: self.frame.width,
height: self.frame.height)
//添加底层券面
couponImageView = UIImageView(frame: childFrame)
couponImageView.image = couponImage
self.addSubview(couponImageView)
//添加涂层
scratchMask = ScratchMask(frame: childFrame)
scratchMask.image = maskImage
scratchMask.lineWidth = scratchWidth
scratchMask.lineType = scratchType
self.addSubview(scratchMask)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
4,使用样例
import UIKit
class ViewController: UIViewController, ScratchCardDelegate {
@IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
//创建刮刮卡组件
let scratchCard = ScratchCard(frame: CGRect(x:20, y:80, width:241, height:106),
couponImage: UIImage(named: "coupon.png")!,
maskImage: UIImage(named: "mask.png")!)
//设置代理
scratchCard.delegate = self
self.view.addSubview(scratchCard)
}
//滑动开始
func scratchBegan(point: CGPoint) {
print("开始刮奖:\(point)")
}
//滑动过程
func scratchMoved(progress: Float) {
print("当前进度:\(progress)")
//显示百分比
let percent = String(format: "%.1f", progress * 100)
label.text = "刮开了:\(percent)%"
}
//滑动结束
func scratchEnded(point: CGPoint) {
print("停止刮奖:\(point)")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
画笔的粗细和样式也是可以设置的:
let scratchCard = ScratchCard(frame: CGRect(x:20, y:80, width:241, height:106),
couponImage: UIImage(named: "coupon.png")!,
maskImage: UIImage(named: "mask.png")!,
scratchWidth: 20, scratchType: .round)
源码下载:
全部评论(2)
厉害!请问百分比到达一定数值,怎么设置全部刮开?
站长回复:可以直接把刮刮卡表面涂层(scratchMask)隐藏即可,比如:scratchCard.scratchMask.alpha = 0
66666
站长回复:多谢支持 :)