iOS动画指南 - 3.Layer Animations的进阶使用

本篇预备知识

  • 这一系列是讲iOS开发中动画的使用,所以得基本熟悉iOS开发.
  • 代码都是基于swift的,所以也得了解swift啊.
  • 这一篇是在前一篇的基础上写的,所以得知道Layer Animations的基本使用吧!

概述

上一篇iOS动画指南 - 2.Layer Animations的基本使用中介绍了Layer Animations的一些基本使用,这一篇我们通过几个小的例子深入了解Layer Animations的用法,所以相比上篇,这篇无论是从篇幅还是连贯性都会有点大,大家准备上车吧.

文章大纲

  1. 可以在多个值之间变幻的CAKeyframeAnimation.
  2. 可以画出各种形状的CAShapeLayer.


    DOG VS FOX
  3. 可以给文字添加效果的CAGradientLayer.


    滑动解锁效果
  4. 有轨迹的下拉刷新.


    模拟下拉刷新
  5. 可以无限复制的CAReplicatorLayer.


    CAReplicatorLayer

1. CAKeyframeAnimation

开发中情况多种多样,从一个值到另一个值的fromValue和toValue属性并不能高效的满足开发需要,比如我们要将一个view一次经过三个点呢?难道分为两次去做,那太麻烦了.对的,可以用CAKeyframeAnimation去实现,CAKeyframeAnimation有个属性values是个数组完美替代了fromValue,toValue,我们可以把三个点放进values数组,解决问题.

        let flight = CAKeyframeAnimation(keyPath: "position")
        flight.duration = 2.0
        // 无限重复
        flight.repeatCount = MAXFLOAT
        
        //  注意:不能将CGPoint直接赋值给values需要转换,数组中的元素可以使结构体
        // .map { NSValue(CGPoint: $0)}可以将数组中的每一个CGPoint转化为NSValue
        flight.values = [
            CGPoint(x: 50.0, y: 100.0),
            CGPoint(x: view.frame.width-50, y: 160),
            CGPoint(x: 50.0, y: view.center.y),
            CGPoint(x: 50.0, y: 100.0)
            ].map { NSValue(CGPoint: $0)}
        
            // 等价于上面代码
//        flight.values = [
//            NSValue(CGPoint: CGPoint(x: 50.0, y: 100.0)),
//            NSValue(CGPoint: CGPoint(x: view.frame.width-50, y: 160)),
//            NSValue(CGPoint: CGPoint(x: 50.0, y: view.center.y)),
//            NSValue(CGPoint: CGPoint(x: 50.0, y: 100.0)),
//            ]

        flight.keyTimes = [0.0, 0.33, 0.66, 1.0]
        dogImageView.layer.addAnimation(flight, forKey: nil)

或者我们可以做一下view的左右晃动,不添加在上面位移基础上,单独去实现:

        let wobble = CAKeyframeAnimation(keyPath: "transform.rotation")
        wobble.duration = 2.5
        wobble.repeatCount = MAXFLOAT
        // 会依次遍历数组中每一个值
        wobble.values = [0.0, -M_PI_4/4, 0.0, M_PI_4/4, 0.0]
        // 为values中的值设置时间,keyTimes按照百分比来的,[0,1]之间
        wobble.keyTimes = [0.0, 0.25, 0.5, 0.75, 1.0]
        dogImageView.layer.addAnimation(wobble, forKey: nil)

2. CAShapeLayer

使用CAShapeLayer可以绘制各种图形.
比如用来画圆:

        let circleLayer = CAShapeLayer()
        let maskLayer = CAShapeLayer()
        
        circleLayer.path = UIBezierPath(ovalInRect: dogImageView.bounds).CGPath
        circleLayer.fillColor = UIColor.clearColor().CGColor
        maskLayer.path = circleLayer.path
        // 超出maskLayer部分裁剪掉
        dogImageView.layer.mask = maskLayer
        dogImageView.layer.addSublayer(circleLayer)

接下来让我们来看下:


DOG VS FOX

由于git图片是循环播放的,所以很难分辨动画的开始和结束,动画的开始其实是这样的:


这是AvatarView的层级:


  • photoLayer : 是用来放置图片的.
  • circleLayer: 是用来绘制圆形的.
  • maskLayer: 是用来裁剪图片的.
  • label: 用于设置名字.

让我们来分析下步骤:

  1. 设置两张图片的圆角
  2. 两张图片向中间移动,完成后将图片变成方角
  3. 在两张图片在中间的时候,将两张图片做一个椭圆的碰撞效果
  4. 后退,图片返回到开始的位置,完成后执行步骤1

实现:
我们对控件做了封装,具体看源码.
1 . 在AvatarView中的didMoveToWindow方法中将新建好的几个属性添加进去

 override func didMoveToWindow() {
        layer.addSublayer(photoLayer)
        photoLayer.mask = maskLayer
        layer.addSublayer(circleLayer)
        addSubview(label)
        
    }

2 . 重写layoutSubviews方法,设置图片的圆角

    override func layoutSubviews() {
        photoLayer.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height)
        circleLayer.path = UIBezierPath(ovalInRect: bounds).CGPath
        circleLayer.strokeColor = UIColor.whiteColor().CGColor
        circleLayer.lineWidth = lineWidth
        circleLayer.fillColor = UIColor.clearColor().CGColor
        maskLayer.path = circleLayer.path
        maskLayer.position = CGPoint(x: 0.0, y: 0.0)
        
        label.frame = CGRect(x: 0.0, y: bounds.size.height + 10.0, width: bounds.size.width, height: 24.0)
    }

3 . 定义外部控制方法func boundsOffset: boundsOffset: morphSize用于传入偏移位置,以及图片碰撞时候需要设置的尺寸:

 func boundsOffset(boundsOffset:CGFloat, morphSize: CGSize) {
}

4 . 在boundsOffset方法中设置图片往中间位移:

        // 前进
        UIView.animateWithDuration(animationDuration, delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.0, options: [], animations: {
            self.frame.origin.x = boundsOffset
            }, completion: {_ in
                // 将圆角图片变成方角图片
                self.animateToSquare()
        })

5 . 当图片在中间的时候会有一个碰撞效果:

        // 碰撞效果
        let morphedFrame = (originalCenter.x > boundsOffset) ?
            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)

6 . 返回到初始位置:

         // 后退
        UIView.animateWithDuration(animationDuration, delay: animationDuration, usingSpringWithDamping: 0.7, initialSpringVelocity: 1.0, options: [], animations: {
            self.center = originalCenter
            }, completion: {_ in
                delay(seconds: 0.1) {
                    if !self.isSquare {
                            self.boundsOffset(boundsOffset, morphSize: morphSize)
                    }
                }
        })    

7 . 将圆角图片变成方角图片.严格意义上这不是最后一步,第四步有一个self.animateToSquare()

func animateToSquare() {
        isSquare = true
        let squarePath = UIBezierPath(rect: 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
    }

大体步骤就这些,下面我们就可以在viewController.swift中使用了

  1. 创建两个AvatarView,设置好图片,大小,位置.
  2. 调用AvatarView的boundsOffset方法,设置偏移位置,以及图片碰撞时候需要的尺寸.
        let avatarSize = avatar1.frame.size
        let morphSize = CGSize(
            width: avatarSize.width * 0.85,
            height: avatarSize.height * 1.05)
        let bounceXOffset: CGFloat = view.frame.size.width/2.0 - avatar1.lineWidth*2 - avatar1.frame.width
        avatar2.boundsOffset(bounceXOffset, morphSize:morphSize)
        avatar1.boundsOffset(avatar1.frame.origin.x - bounceXOffset, morphSize:morphSize)

3. CAGradientLayer

我们几乎每天都会看到的iPhone滑动来解锁的文字效果是怎么实现的呢?
一步一步来吧 >.<!

  1. 搞一个懒加载来设置CAGradientLayer
    lazy var gradientLayer: CAGradientLayer = {
        let gradientLayer = CAGradientLayer()
        
        // 设置开始位置和结束位置
        gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
        gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
        
        // 从左到右依次的这几种颜色,颜色是渐变的
        let colors = [
            UIColor.blackColor().CGColor,
            UIColor.whiteColor().CGColor,
            UIColor.blackColor().CGColor
        ]
        gradientLayer.colors = colors
        
        // 颜色的位置
        let locations = [0.25, 0.5, 0.75]
        gradientLayer.locations = locations
        
        return gradientLayer
    }()

新建一个view,随便设置位置尺寸,将gradientLayer添加到view上面

        let gradientView = UIView(frame: CGRect(x: 0, y: self.view.frame.height/2, width: self.view.frame.width, height: 80))
        gradientView.backgroundColor = UIColor.lightGrayColor()
        gradientLayer.frame = gradientView.bounds
        gradientView.layer.addSublayer(gradientLayer)
        view.addSubview(gradientView)

然后我们就可以看到这样的效果:



怎么样是不是有点那么回事了!我们还要让它动起来
2 . 为gradientLayer添加动画效果
CABasicAnimation有一个locations属性,通过修改颜色位置形成动画.

        let gradientAnimation = CABasicAnimation(keyPath: "locations")
        gradientAnimation.fromValue = [0.0, 0.0, 0.25]
        gradientAnimation.toValue = [0.75, 1.0, 1.0]
        gradientAnimation.duration = 3.0
        gradientAnimation.repeatCount = Float.infinity
        gradientLayer.addAnimation(gradientAnimation, forKey: nil)

git掉帧这么严重将就着看吧!
3 . 修改白色区域的大小
这边我们调整白色区域将其*3倍,更好看一点.
4 . 考虑到代码的可复用性,我们对代码做了封装,自定义了一个view:GradientLabel ,view里面有一个text属性,通过图形上下文把字符串绘制到view上面.


 // 设置字体属性
    lazy var textAttributes: [String: AnyObject] = {
        let style = NSMutableParagraphStyle()
        style.alignment = .Center
        
        return [
            NSFontAttributeName:UIFont(name: "HelveticaNeue-Thin", size: 28.0)!,
            NSParagraphStyleAttributeName:style
        ]
    }()
    @IBInspectable var text: String! {
        didSet {
            
            setNeedsDisplay()
            UIGraphicsBeginImageContextWithOptions(frame.size, false, 0)
            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
        }
    }

然后外面就可以这样使用了

 override func viewDidLoad() {
        super.viewDidLoad()
        let label = GradientLabel()
        label.center = view.center
        label.bounds = CGRect(x: 0, y: 0, width: 239, height: 44)
        label.text = "> 滑动来解锁"
        view.addSubview(label)
        view.backgroundColor = UIColor.darkGrayColor()
    }
滑动解锁效果

4. 有轨迹的下拉刷新.

下拉刷新几乎每个APP都会用到,由于有现成的第三方框架,所以要自己动手实现的情况并不是很多,但有时候需求要求自定义,所以了解下原理吧!
原理很简单:其实就是通过代理方法监听tableView的滚动,当完成刷新就恢复原来的样子.
这次我们要做一个有特效的.

  1. 使用CAShapeLayer绘制一个带有虚线的圆
        let ovalShapeLayer: CAShapeLayer = CAShapeLayer()
        let airplaneLayer: CALayer = CALayer()
        // 白色的圈
        ovalShapeLayer.strokeColor = UIColor.whiteColor().CGColor
        ovalShapeLayer.fillColor = UIColor.clearColor().CGColor
        ovalShapeLayer.lineWidth = 4.0
        ovalShapeLayer.lineDashPattern = [2, 3]
        let refreshRadius = frame.size.height/2 * 0.8
        ovalShapeLayer.path = UIBezierPath(ovalInRect: CGRect(x: frame.size.width/2 - refreshRadius, y:frame.size.height/2 - refreshRadius , width: 2*refreshRadius, height: 2*refreshRadius)).CGPath
        layer.addSublayer(ovalShapeLayer)

然后在开始位置添加飞机图片

        // 添加飞机图片
        let airplaneImage = UIImage(named: "airplane")
        airplaneLayer.contents = airplaneImage?.CGImage
        airplaneLayer.bounds = CGRect(x: 0.0, y: 0.0, width: (airplaneImage?.size.width)!, height: airplaneImage!.size.height)
        airplaneLayer.position = CGPoint(x: frame.size.width/2 + frame.size.height/2 * 0.8, y: frame.size.height/2)
        layer.addSublayer(airplaneLayer)

2 . 设置开始刷新,结束刷新动画

    // 开始刷新
    func beginRefreshing() {
        isRefreshing = true
        UIView.animateWithDuration(0.3, animations: {
            var newInsets = self.scrollView!.contentInset
            newInsets.top += self.frame.size.height
            self.scrollView!.contentInset = newInsets
        })
        
        let strokeStartAnimation = CABasicAnimation(keyPath: "strokeStart")
        strokeStartAnimation.fromValue = -0.5
        strokeStartAnimation.toValue = 1.0
        
        let strokeEndAnimation = CABasicAnimation(keyPath: "strokeEnd")
        strokeEndAnimation.fromValue = 0.0
        strokeEndAnimation.toValue = 1.0
        
        let strokeAnimationGroup = CAAnimationGroup()
        strokeAnimationGroup.duration = 1.5
        strokeAnimationGroup.repeatDuration = 5.0
        strokeAnimationGroup.animations = [strokeStartAnimation, strokeEndAnimation]
        ovalShapeLayer.addAnimation(strokeAnimationGroup, forKey: nil)
        
        let flightAnimation = CAKeyframeAnimation(keyPath: "position")
        flightAnimation.path = ovalShapeLayer.path
        flightAnimation.calculationMode = kCAAnimationPaced
        
        let airplaneOrientationAnimation = CABasicAnimation(keyPath: "transform.rotation")
        airplaneOrientationAnimation.fromValue = 0
        airplaneOrientationAnimation.toValue = 2 * M_PI
        
        
        let flightAnimationGroup = CAAnimationGroup()
        flightAnimationGroup.duration = 1.5
        flightAnimationGroup.repeatDuration = 5.0
        flightAnimationGroup.animations = [flightAnimation, airplaneOrientationAnimation]
        airplaneLayer.addAnimation(flightAnimationGroup, forKey: nil)
        
    }
    
    // 结束刷新
    func endRefreshing() {
        isRefreshing = false
        
        UIView.animateWithDuration(0.3, delay:0.0, options: .CurveEaseOut ,animations: {
            var newInsets = self.scrollView!.contentInset
            newInsets.top -= self.frame.size.height
            self.scrollView!.contentInset = newInsets
            }, completion: {_ in
        })
    }

3 . 在tabelView的scrollView方法中根据偏移量设置动画的开始和结束

 func scrollViewDidScroll(scrollView: UIScrollView) {
        
        let offsetY = CGFloat( max(-(scrollView.contentOffset.y + scrollView.contentInset.top), 0.0))
        self.progress = min(max( offsetY / frame.size.height, 0.0), 1.0)
        
        if !refreshing {
            redrawFromProgress(progress)
        }
    }
    func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        if !refreshing && self.progress >= 1.0 {
            delegate?.refreshViewDidRefresh(self)
            beginRefreshing() 
        }
    }
模拟下拉刷新

5. 可以无限复制的CAReplicatorLayer

CALayer的子类CAReplicatorLayer通过它可以对其创建的对象进行复制,从而做出复杂的效果.
1 . 创建一个CAReplicatorLayer,然后进行复制.

        let replicator = CAReplicatorLayer()
        let dot = CALayer()
        
        let dotLength : CGFloat = 6.0
        let dotOffset : CGFloat = 8.0
        
        replicator.frame = view.bounds
        view.layer.addSublayer(replicator)
        
        dot.frame = CGRect(x: replicator.frame.size.width - dotLength, y: replicator.position.y, width: dotLength, height: dotLength)
        dot.backgroundColor = UIColor.lightGrayColor().CGColor
        dot.borderColor = UIColor(white: 1.0, alpha: 1.0).CGColor
        dot.borderWidth = 0.5
        dot.cornerRadius = 1.5
        replicator.addSublayer(dot)
        
        // 进行复制
        replicator.instanceCount = Int(view.frame.size.width / dotOffset)
        replicator.instanceTransform = CATransform3DMakeTranslation(-dotOffset, 0.0, 0.0)

2 . 让它动起来,并且让每一个dot做一点延迟.

        let move = CABasicAnimation(keyPath: "position.y")
        move.fromValue = dot.position.y
        move.toValue = dot.position.y - 50.0
        move.duration = 1.0
        move.repeatCount = 10
        dot.addAnimation(move, forKey: nil)
        // 延迟 0.02秒
        replicator.instanceDelay = 0.02

3 . 将2中的代码注释掉,做出这样的一个效果

        replicator.instanceDelay = 0.02

        let scale = CABasicAnimation(keyPath: "transform")
        scale.fromValue = NSValue(CATransform3D: CATransform3DIdentity)
        scale.toValue = NSValue(CATransform3D: CATransform3DMakeScale(1.4, 15, 1.0))
        scale.duration = 0.33
        scale.repeatCount = Float.infinity
        scale.autoreverses = true
        scale.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
        dot.addAnimation(scale, forKey: "dotScale")

4 .添加一个渐变色

        let fade = CABasicAnimation(keyPath: "opacity")
        fade.fromValue = 1.0
        fade.toValue = 0.2
        fade.duration = 0.33
        fade.beginTime = CACurrentMediaTime() + 0.33
        fade.repeatCount = Float.infinity
        fade.autoreverses = true
        fade.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
        dot.addAnimation(fade, forKey: "dotOpacity")

5 . 添加渐变的颜色

      
        let tint = CABasicAnimation(keyPath: "backgroundColor")
        tint.fromValue = UIColor.magentaColor().CGColor
        tint.toValue = UIColor.cyanColor().CGColor
        tint.duration = 0.66
        tint.beginTime = CACurrentMediaTime() + 0.28
        tint.fillMode = kCAFillModeBackwards
        tint.repeatCount = Float.infinity
        tint.autoreverses = true
        tint.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
        dot.addAnimation(tint, forKey: "dotColor")
  1. 设置成上下摇摆
        let initialRotation = CABasicAnimation(keyPath: "instanceTransform.rotation")
        initialRotation.fromValue = 0.0
        initialRotation.toValue = 0.01
        initialRotation.duration = 0.33
        initialRotation.removedOnCompletion = false
        initialRotation.fillMode = kCAFillModeForwards
        initialRotation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
        replicator.addAnimation(initialRotation, forKey: "initialRotation")

        let rotation = CABasicAnimation(keyPath: "instanceTransform.rotation")
        rotation.fromValue = 0.01
        rotation.toValue = -0.01
        rotation.duration = 0.99
        rotation.beginTime = CACurrentMediaTime() + 0.33
        rotation.repeatCount = Float.infinity
        rotation.autoreverses = true
        rotation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
        replicator.addAnimation(rotation, forKey: "replicatorRotation")

本文整理自 : iOS.Animations.by.Tutorials.v2.0
源码 : https://github.com/DarielChen/DemoCode
如有疑问,欢迎留言 :-D

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

推荐阅读更多精彩内容

  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥ios动画全貌。在这里你可以看...
    每天刷两次牙阅读 8,320评论 6 30
  • Core Animation Core Animation,中文翻译为核心动画,它是一组非常强大的动画处理API,...
    45b645c5912e阅读 2,967评论 0 21
  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌。在这里你可以看...
    F麦子阅读 4,996评论 5 13
  • 显式动画 显式动画,它能够对一些属性做指定的自定义动画,或者创建非线性动画,比如沿着任意一条曲线移动。 属性动画 ...
    清风沐沐阅读 1,866评论 1 5
  • 1. 《你好,旧时光》是八月长安笔下的青春,也是我们每个人的美好! 很久很久以前,写作文的时候经常用上这一句话“趁...
    半粒苦涩半粒糖阅读 322评论 0 0