swift 写 iOS 空心字描边动画

参考资料

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() 这一行下面运行即可。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏转载gongluck的CSDN博客

CListCtrl控件使用方法总结

今天第一次用CListCtrl控件,遇到不少问题,查了许多资料,现将用到的一些东西总结如下: 以下未经说明,listctrl默认view 风格为report ...

41213
来自专栏Code_iOS

OpenGL ES 2.0 (iOS)[05-1]:进入 3D 世界,从正方体开始

a. 渲染管线的基础知识 《OpenGL ES 2.0 (iOS)[01]: 一步从一个小三角开始》

1523
来自专栏iOS开发攻城狮的集散地

UIScrollView视觉差动画

2266
来自专栏Flutter入门

Flutter开发实战分析-animation_demo瞎复写总结

以下代码基本参考于 flutter_gallery中的animation_demo示例。(可以结合本文看源码)

4963
来自专栏向治洪

ios动画

在iOS开发中,动画是提高用户体验重要的环节之一。一个设计严谨、精细的动画效果能给用户耳目一新的效果,这对于app而言是非常重要的。 简介 iOS动画主要是指C...

2205
来自专栏AhDung

【C#】分享一个可灵活设置边框的Panel

---------------------------更新:2014-05-19---------------------------

1091
来自专栏進无尽的文章

实践-跑马灯效果及实现过程解析

801
来自专栏程序员互动联盟

【专业技术】搜狗歌词窗口如何来实现

大家都见过以前Sogou歌词窗口的样子吧,感觉是歌词的字体直接贴在windows桌面上一样,但是还可以用鼠标控制,这个是怎么做成的呢?其实我也不知道^_^,估计...

37110
来自专栏jianhuicode

带着问题写React Native原生控件--Android视频直播控件

最近在做的采用React Native项目有一个需求,视频直播与直播流播放同一个布局中,带着问题去思考如何实现,能更容易找到问题关键点,下面分析这个控件解决方法...

1.7K8
来自专栏DeveWork

jQuery仿极客公园火箭发射“返回顶部”效果(优化篇)

承接上一篇《jQuery仿极客公园火箭发射“返回顶部”效果(初始篇)》,本文将对前一篇的代码进行优化。还是转载自andyliu: 先给出个演示Demo:演示地址...

2006

扫码关注云+社区

领取腾讯云代金券