iOS动画——Layer Animations(三)

我胡汉三又回来啦,忙过了前一段时间以后,今天又给大家带来了两个动画,一个是形变、一个是颜色渐变。如下所示:


动画来源《iOS Animation by tutorials》

源码在github,地址在本文最后。


第一个动画:
我们来分析一下第一个动画,两个头像的位移我们前面已经讲过了,今天主要讲讲形变,在这个动画里一个是变椭圆,一个是变回正方形。
这两个忍者(这里以及后面都把他称呼为忍者)并不是UIImageView,而是一个UIView,然后在UIView中添加了UIImage(事实上用UIImageView也可以)。
我们来看一下他的层次结构:

事实上有三层哦

那么我们要做的第一步就是吧这三个图层添加到UIView:
<pre><code>

let photoLayer         = CALayer()
let circleLayer        = CAShapeLayer()
let maskLayer          = CAShapeLayer()

/**
将Layer添加到父类
*/
override func didMoveToWindow() {
layer.addSublayer(photoLayer)
photoLayer.mask = maskLayer //遮罩层
layer.addSublayer(circleLayer)

}
/**
初始化三个Layer
*/
override func layoutSubviews() {
    
    //Size the avatar image to fit
    photoLayer.frame = CGRect(
        x: (bounds.size.width - image.size.width + lineWidth)/2,
        y: (bounds.size.height - image.size.height - lineWidth)/2,
        width: image.size.width,
        height: image.size.height)
    
    //Draw the circle
    circleLayer.path = UIBezierPath(ovalInRect: bounds).CGPath
    circleLayer.strokeColor = UIColor.whiteColor().CGColor
    circleLayer.lineWidth = lineWidth
    circleLayer.fillColor = UIColor.clearColor().CGColor
    
    //Size the layer
    maskLayer.path = circleLayer.path
    maskLayer.position = CGPoint(x: 0.0, y: 10.0)
    
}

</code></pre>

初始化部分:
我们声明了三个CALayer,CAShapeLayer继承自CALayer,然而这个layer可以用** UIBezierPath画出你想要的图形。
我们可以看到photoLayer声明为了一个正方形、
circleLayer则用UIBezierPath画了一个圆圈,并且设置了线条的颜色和线条的宽度,同时把填充颜色设置为clearColor。
maskLayer也是一个圆,但是我们同时设置他的
positon属性,讲他向下挪了10个point,注意position的坐标远点为左上角。
设置部分:
我们将
maskLayerphotoLayer添加到了UIView上,前面我们说过了photoLayer**是一个正方形,那么他是如何显示为圆形的呢,我们给他设置了一个遮罩层:

   photoLayer.mask = maskLayer        //遮罩层

写过flash的同学一定知道遮罩层的概念是什么,遮罩层就是显示的部分,如上设置以后,photoLayer就只显示maskLayer的部分,所以当maskLayer发生改变的时候,photoLayer显示的部分也会发生改变,所以我们接下来只需要对maskLayer进行动画设置就可以。

我们现在运行一下程序看一下效果,你可以看到是这样的:

1.png

好的接下来就是关键的动画代码了,如下:

<pre><code>
func bounceOffPoint(bouncePoint: CGPoint, morphSize: CGSize) {
let originalCenter = center

    UIView.animateWithDuration(animationDuration, delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.0, options: nil, animations: {
        
        self.center = bouncePoint
        
        }, completion: {_ in
            //complete bounce to
            if self.shouldTransitionToFinishedState {
                self.animateToSquare()
            }
    })
    
    UIView.animateWithDuration(animationDuration, delay: animationDuration, usingSpringWithDamping: 0.7, initialSpringVelocity: 1.0, options: nil, animations: {
        self.center = originalCenter
        }, completion: {_ in
            delay(seconds: 0.1) {
                if !self.isSquare {
                    self.bounceOffPoint(bouncePoint, morphSize: morphSize)
                }
            }
    })

/// 椭圆动画

    let morphedFrame = (originalCenter.x > bouncePoint.x) ?
        
        CGRect(x: 0.0, y: bounds.height - morphSize.height,
            width: morphSize.width, height: morphSize.height):
        
        CGRect(x: bounds.width - morphSize.width,
            y: bounds.height - morphSize.height,
            width: morphSize.width, height: morphSize.height)
    
    let morphAnimation = CABasicAnimation(keyPath: "path")
    morphAnimation.duration = animationDuration
    morphAnimation.toValue = UIBezierPath(ovalInRect: morphedFrame).CGPath
    morphAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
    
    circleLayer.addAnimation(morphAnimation, forKey:nil)
    maskLayer.addAnimation(morphAnimation, forKey: nil)
}
/**
方形动画
*/
func animateToSquare() {
    isSquare = true
    
    let squarePath = UIBezierPath(rect: self.bounds).CGPath
    let morph = CABasicAnimation(keyPath: "path")
    
    morph.duration = 0.25
    morph.fromValue = circleLayer.path
    morph.toValue = squarePath
    
    circleLayer.addAnimation(morph, forKey: nil)
    maskLayer.addAnimation(morph, forKey: nil)
    
    circleLayer.path = squarePath
    maskLayer.path = squarePath
}

</code></pre>

我们主要看椭圆动画部分和方形动画部分。
我们在这里可以看到我们还是创建了一个CALayer Aniamtion CABasicAnimation,这个动画在前面部分我们已经看到很多回了,但是在这里我们的keypath为“path”确实第一次做,当keypath为“path”的时候主要是对UIView的形状进行改变。
我们可以看到这个** CABasicAnimation的结构和之前还是一样的,同样是.toValue.fromValue.duration。所以当对形状进行动画的时候关键就是如何用 UIBezierPath**来描绘你想要编程的图像。
我们今天要描绘的是两个非常简单的图案。
<pre><code>
morphAnimation.toValue = UIBezierPath(ovalInRect: morphedFrame).CGPath //描绘了一个椭圆
let squarePath = UIBezierPath(rect: self.bounds).CGPath //描绘了一个正方形
</code></pre>

UIBezierPath给我们定制很多非常方便的初始化函数,更多信息请查看官方文档。

中间还有一些时间的计算和距离的计算这里就不多进行讲述,相信大家能自己看懂。


来看第二个动画

第二个动画就是一个渐变的色的改变,在这里我们需要了解一个类CAGradientLayer,这个图层很好的实现了对颜色的控制。
他有几个属性需要了解,startPointendPointcolorslocations,我们来看一下我们这里的对CAGradientLayer的初始化过程
<pre><code>
let gradientLayer: CAGradientLayer = {
let gradientLayer = CAGradientLayer()

// Configure the gradient here

gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)

let colors = [
  UIColor.yellowColor().CGColor,
  UIColor.greenColor().CGColor,
  UIColor.orangeColor().CGColor,
  UIColor.cyanColor().CGColor,
  UIColor.redColor().CGColor,
  UIColor.yellowColor().CGColor

]
gradientLayer.colors = colors

let locations = [
  0.0, 0.0, 0.0, 0.0, 0.0, 0.25
]
gradientLayer.locations = locations

return gradientLayer
}()

</code></pre>

这里需要注意的就是渐变方向默认是由上到下的,而当我们设施startPointendPoint以后可以改变渐变的方向。locations对应每一个colors中的color的位置。然后我们将这个CALayer添加到UIView。
<pre><code>
override func layoutSubviews() {

gradientLayer.frame = CGRect(
  x: -bounds.size.width,
  y: bounds.origin.y,
  width: 3 * bounds.size.width,
  height: bounds.size.height)

}

override func didMoveToWindow() {

super.didMoveToWindow()

layer.addSublayer(gradientLayer)

}
</code></pre>

当然我们必须记得给这个CALayer设置frame,不然默认为0,我们现在可以运行看一下效果:

1.png

我们已经实现了颜色的渐变,那么我们现在给他添加动画:
<pre><code>
let gradientAnimation = CABasicAnimation(keyPath: "locations")
gradientAnimation.fromValue = [
0.0, 0.0, 0.0, 0.0, 0.0, 0.25
]
gradientAnimation.toValue = [
0.65, 0.8, 0.85, 0.9, 0.95, 1.0
]

gradientAnimation.duration    = 3.0
gradientAnimation.repeatCount = Float.infinity

gradientLayer.addAnimation(gradientAnimation, forKey: nil)

</code></pre>

我们这里的keypath为locations也就是改变每一个颜色的位置,那么就实现了动画的过程。** CABasicAnimation**的框架和前面并没有太大的区别,我们 再来运行一下程序看一下效果:

10.gif

效果如上所示,我们已经大概完成了动画,颜色唰唰的滚动,咦,但是怎么好像和最前面看到的不大一样的,开篇我们看到的明明是一串字符串啊。哦~我们还需要制作一个遮罩层,这个遮罩层是一个文字的遮罩层。
代码如下:
<pre><code>
@IBInspectable var text: String! {
didSet {
UIGraphicsBeginImageContextWithOptions(frame.size, false, 0)
let context = UIGraphicsGetCurrentContext()
text.drawInRect(bounds, withAttributes: textAttributes)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

  let maskLayer             = CALayer()
  maskLayer.backgroundColor = UIColor.clearColor().CGColor
  maskLayer.frame           = CGRectOffset(bounds, bounds.size.width, 0)
  maskLayer.contents        = image.CGImage

    
  gradientLayer.mask        = maskLayer
}

}
</code></pre>

这里出现了很多没有见过的类,乍一眼看过去,“娘的,吓尿了~”,然而事实上并不复杂。
UIGraphicsBeginImageContextWithOptions用来创建一个context,他和** UIGraphicsEndImageContext是一对,在他们俩之间我们可以来绘制一些图片,
** UIGraphicsGetCurrentContext
创建了一个栈,

text.drawInRect(bounds, withAttributes: textAttributes)

这句话将文字绘制成图片,并压入上面我们创建的那一个栈,

 let image                 = UIGraphicsGetImageFromCurrentImageContext()

这句话取栈顶,也就是我们之前的用text绘制的图片,那么现在image就是我们之前的文字,只不过是UIImage格式的。你可以理解为PNG格式~(不知道我这么理解对不还是不对,若是有大神看到请指出。)

接下来设置遮罩层和上一个动画就没有太大的区别的了;

好了 好了,我们终于写完了这两个动画,这两个动画并不复杂,结合代码和我的解释 大家都应该能够看懂。


代码已上传github:https://github.com/superxlx/iOS_Animation_Test5

觉得本文对你有帮助的请关注我,并点击一下喜欢~亲~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,692评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,482评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,995评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,223评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,245评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,208评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,091评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,929评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,346评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,570评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,739评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,437评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,037评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,677评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,833评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,760评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,647评论 2 354

推荐阅读更多精彩内容