首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >swift 写 iOS 空心字描边动画

swift 写 iOS 空心字描边动画

作者头像
Alan Zhang
发布2018-10-19 14:49:36
1.6K0
发布2018-10-19 14:49:36
举报
文章被收录于专栏:Alan's LabAlan's Lab

参考资料

http://oleb.net/blog/2010/12/animating-drawing-of-cgpath-with-cashapelayer/ https://github.com/ole/Animated-Paths

链接里的范例程序是 Objective-c 写的,这篇基本就是把它翻译成 swift 。

动画预览

你好!
你好!

开始扯

上篇写了 iOS 的 block-based animation 这一篇再来整点更加有趣的玩意。

效果就是上面那个 GIF 了,虽然实在想不到有谁会需要在程序里手写这种动画。。。不过咱们兴趣导向,开撸。。。

前置技能,至少都加 1 点: CAShapeLayer 。 CALayer 和 CoreAnimation: View Programming Guide for iOS 以及 Core Animation Programming Guide

CAPathLayer 看名字就可以知道是一个用来处理路径的 CALayer 子类,上面动画中字的轮廓就是我们提供给它的路径。之后,这个 CAPathLayer 提供了一个和 alpha 一样可以通过修改产生动画的属性,叫做 strokeEnd 。与之相对,还有一个属性叫做 strokeStart,它们的作用如下:

假设有一条路径如下,起点在最左边,终点在最右边:

--------------------------------------

strokeStart 表示绘制的起点, strokeEnd 表示绘制的终点,没在它们之间的部分不予绘制。这样讲太抽象,上设置加效果:

strokeStart = 0
strokeEnd   = 1
--------------------------------------

strokeStart = 0
strokeEnd   = 0.5
--------------------

strokeStart = 0.5
strokeEnd   = 1
                    ------------------

strokeStart = 0.25
strokeEnd   = 0.75
          -------------------

开头看到的动画可以分成两步:

  1. 实现 CAPathLayer 的 strokeEnd 从0到1动画
  2. 把上文中 CAPathLayer 的 path 属性换成文字的轮廓 path

第一步,也就是动画的部分:新建一个 Single View App ,在 ViewController 的 viewDidLoad 中输入下面的代码,就可以看到一个简单的例子,iOS模拟器左上角有一条红色斜线的动画。

let mpath = UIBezierPath()
mpath.moveToPoint(CGPoint(x: 0, y: 0))
mpath.addLineToPoint(CGPoint(x: 100, y: 100))
mpath.closePath()

// CGShapeLayer
let shapeLayer = CAShapeLayer()

shapeLayer.path = mpath.CGPath
shapeLayer.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
shapeLayer.strokeColor = UIColor.redColor().CGColor
shapeLayer.fillColor = UIColor.redColor().CGColor
shapeLayer.lineWidth = 3.0

view.layer.addSublayer(shapeLayer)

// CABaseAnimation
let animation = CABasicAnimation(keyPath: "strokeEnd");  // 改变 strokeEnd 创建动画

animation.fromValue = 0.0
animation.toValue = 1.0
animation.duration = 3

shapeLayer.addAnimation(animation, forKey: "storkeEnd")

第二部,获取文字的外轮廓 path :

let font = CTFontCreateWithName("STHeitiSC-Light", 72, nil)
let attrStr = NSAttributedString(string: "你好!", attributes: [kCTFontAttributeName: font])
let line = CTLineCreateWithAttributedString(attrStr)
let runArray = CTLineGetGlyphRuns(line) as [CTRunRef]

let letters = CGPathCreateMutable()

for runIndex in 0..<CFArrayGetCount(runArray) {
    let run: CTRunRef = runArray[runIndex]

    for runGlyphIndex in 0..<CTRunGetGlyphCount(run) {
        let thisGlyphRange = CFRangeMake(runGlyphIndex, 1)
        var glyph: CGGlyph = CGGlyph()
        var position: CGPoint = CGPoint()
        CTRunGetGlyphs(run, thisGlyphRange, &glyph)
        CTRunGetPositions(run, thisGlyphRange, &position)

        let letter = CTFontCreatePathForGlyph(font, glyph, nil)
        var t = CGAffineTransformMakeTranslation(position.x, position.y);

        CGPathAddPath(letters, &t, letter)
    }
}

关于上面那些 CTLine 、 CTRun 什么的内容,可以看 About Core Text 文档。这段代码的作用就是把一段 NSAttributedString 的轮廓 path 提取出来,放到 letters 中。

把这段代码放到前面那段的前面,super.viewDidLoad() 的后面,然后就可以修改 shapeLayer.path = mpath 为 shapeLayer.path = letters ,改完之后,开头的动画就已经实现了!(看不到或看不全的话, shapeLayer 的 frame 可能要调一调)

还有个问题,文字全部倒过来了。。。。。。。。。。这是因为 iOS 的屏幕坐标是以左上角为原点,纵轴向下为正方向,Mac OS 以左下角为原点,纵轴向上为正方向的。而 Core Text 最初是在 Mac OS 上用的。。。翻转 shapeLayer 的 y 轴即可:shapeLayer.geometryFlipped = true

最后版本:

view.backgroundColor = UIColor.darkGrayColor()

// 获取字符串轮廓的 path
let font = CTFontCreateWithName("STHeitiSC-Light", 72, nil)
let attrStr = NSAttributedString(string: "你好!", attributes: [kCTFontAttributeName: font])
let line = CTLineCreateWithAttributedString(attrStr)
let runArray = CTLineGetGlyphRuns(line) as [CTRunRef]

let letters = CGPathCreateMutable()

for runIndex in 0..<CFArrayGetCount(runArray) {
    let run: CTRunRef = runArray[runIndex]

    for runGlyphIndex in 0..<CTRunGetGlyphCount(run) {
        let thisGlyphRange = CFRangeMake(runGlyphIndex, 1)
        var glyph: CGGlyph = CGGlyph()
        var position: CGPoint = CGPoint()
        CTRunGetGlyphs(run, thisGlyphRange, &glyph)
        CTRunGetPositions(run, thisGlyphRange, &position)

        let letter = CTFontCreatePathForGlyph(font, glyph, nil)
        var t = CGAffineTransformMakeTranslation(position.x, position.y);

        CGPathAddPath(letters, &t, letter)
    }
}

// CGShapeLayer
let shapeLayer = CAShapeLayer()

shapeLayer.path = letters               // 设置 CGShapeLayer 的 path 为上文中取出的字符串轮廓 path
let screenSize = UIScreen.mainScreen().bounds
shapeLayer.frame = CGRect(x: (screenSize.width-attrStr.size().width)/2,
                          y: (screenSize.height-attrStr.size().height)/2,
                      width: attrStr.size().width,
                     height: attrStr.size().height)
shapeLayer.strokeColor = UIColor.redColor().CGColor
shapeLayer.fillColor = nil
shapeLayer.lineWidth = 3.0
shapeLayer.geometryFlipped = true       // 上下翻转 ShapeLayer 的坐标系

view.layer.addSublayer(shapeLayer)

// CABaseAnimation
let animation = CABasicAnimation(keyPath: "strokeEnd");  // 改变 strokeEnd 属性,创建动画

animation.fromValue = 0.0
animation.toValue = 1.0
animation.duration = 3

shapeLayer.addAnimation(animation, forKey: "storkeEnd")

照旧,粘贴至 super.viewDidLoad() 这一行下面运行即可。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 参考资料
  • 动画预览
  • 开始扯
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档