Swift之CAGradientLayer 实现渐变色效果

小伙伴们在开发一款APP的时候,为了优化用户的体验往往会用到多种颜色和多个图片。纯色往往让人觉得单调,使用颜色渐变能够给用户更好的体验。那我们接下来就来体验一场颜色的盛宴吧~~~

如何简单而又轻松的做出一个渐变效果?有三种方法。第一种方法是最捷径的,直接使用渐变效果的图片。但是最大的缺点呢就是你无法控制渐变的幅度,除非你对每一种状态制作一个图像。这个工程量就十分巨大了。第二种方法是使用 Core Graphics ,但是你需要掌握关于 CG 的知识(例如图形的上下文,色彩空间,等等)。Core Graphics 框架是面向高级开发者的,很多新手不善于使用,从而无法做出渐变效果(因为我也不会😂)。
所以我给大家介绍的是下面这种方法,即便捷又简单的方法:利用 CAGradientLayer 对象。

CAGradientLayer 是 CALayer 的子类。可以使用它来达到渐变效果。产生一个简单的颜色梯度超简单,同时它还提供了一些属性用于调整效果。需要大家注意的是:需要在 view 的 layer 层上展示 CAGradientLayer 。所以我们所有的编码都是在 layer 层上进行。 CAGradientLayer 美中不足的是,它不支持辐射渐变,但是如果你确实需要的话可以通过 CAGradientLayer 来扩展实现。

一、初步了解CAGradientLayer

下面我们先来写一个简单的颜色阶梯:
创建一个CAGradientLayer并设置相关属性
 var gradientLayer: CAGradientLayer!

 //初始化gradientLayer并设置相关属性
 func createGradientLayer() {
    gradientLayer = CAGradientLayer()
    gradientLayer.frame = self.view.bounds
    //设置渐变的主颜色
    gradientLayer.colors = [UIColor.redColor().CGColor, UIColor.yellowColor().CGColor]
    //将gradientLayer作为子layer添加到主layer上
    self.view.layer.addSublayer(gradientLayer)
}

在viewWillAppear中显示颜色阶梯:

 override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    self.createGradientLayer()
 }
运行后的效果如下图所示:
简单的颜色阶梯.png

二、渐变色

在createGradientLayer()这个方法中我们并没有设置很多属性,但是有一句是必不可少的,那就是设置渐变的主颜色,我们还可以在数组中添加更多的颜色:

  gradientLayer.colors = [UIColor.redColor().CGColor, UIColor.orangeColor().CGColor, UIColor.blueColor().CGColor, UIColor.magentaColor().CGColor, UIColor.yellowColor().CGColor] 
运行后的效果如下图所示:
渐变色.png

三、切换渐变色

colors 属性是兼容动画的,也就是说如果我们可以通过动画来改变颜色渐变效果。来实验一下,先来构造一个颜色数组的集合。然后我们将会让每个颜色集(每个颜色数组)在我们点击的时候进行替换,并且通过动画方式。

首先我们来创建一个colorSets 数组用来存储颜色集和currentColorSet 将做为获取颜色集的索引下标。

 var colorSets = [[CGColor]]()
 var currentColorSet: Int!

然后我们来设置一下颜色集:

func createColorSets() {
    colorSets.append([UIColor.redColor().CGColor, UIColor.yellowColor().CGColor])
    colorSets.append([UIColor.greenColor().CGColor, UIColor.cyanColor().CGColor])
    colorSets.append([UIColor.blackColor().CGColor, UIColor.lightGrayColor().CGColor])
    colorSets.append([UIColor.blueColor().CGColor, UIColor.magentaColor().CGColor])
    
    currentColorSet = 0
}

我们在viewDidLoad中添加一个点击手势:

override func viewDidLoad() {
    super.viewDidLoad()
    
    self.createColorSets()
    //一个手指的点击
    let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.handleTapGesture(_:)))
    self.view.addGestureRecognizer(tapGestureRecognizer)
}
//点击事件
func handleTapGesture(tap: UITapGestureRecognizer) {
    /**
     首先我们需要确定下一个颜色集的下标是多少。如果使用的颜色集合是数组中的最后一个,我们需要重新计数下标( currentColorSet = 0 ),如果不是上述情况,让 currentColorSet 自加一即可。
     */
    if currentColorSet < colorSets.count - 1 {
        currentColorSet! += 1
    } else {
        currentColorSet = 0
    }
  
    //添加渐变动画
    let colorChangeAnimation = CABasicAnimation(keyPath: "colors")
    colorChangeAnimation.delegate = self
    colorChangeAnimation.duration = 2.0
    colorChangeAnimation.toValue = colorSets[currentColorSet]
    colorChangeAnimation.fillMode = kCAFillModeForwards
    colorChangeAnimation.removedOnCompletion = false
    gradientLayer.addAnimation(colorChangeAnimation, forKey: "colorChaneg")
}

接下来的代码是关于动画的。这里面最重要的属性是 duration ,它代表着动画的过渡时长;另外, toValue 属性用来设置终点状态的期望颜色集 (这些是在 CABasicAnimation 类初始化时需要指定的属性)。另外两个属性用来将动画的最终状态保留在 layer 中,而不还原回之前状态。但是这不是持续的,我们需要在动画结束后,显式设置渐变色。我们通过重写以下方法,可以在 CABasicAnimation 结束后执行需要的操作:

override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
    if flag {
        gradientLayer.colors = colorSets[currentColorSet]
    }
}

在这不要忘了修改一下createGradientLayer()中修改gradientLayer.colors

 gradientLayer.colors = colorSets[currentColorSet]
运行后的效果如下:
点击渐变.gif

四、设置颜色的相对坐标

我们前面看到的效果,颜色的位置都是默认的均分,下面我们来自定义的实现:
这个可以通过 CAGradientLayer 的 locations 属性来设置。该属性需要传入一个 NSNumber 对象数组,每个数字确定了每个颜色的起始位置(starting location)。另外,这些数字是浮点数,取值范围在 0.0 到 1.0 之间。
 gradientLayer.locations = [0.0, 0.35]

效果是这个样子哒:

点击渐变.gif

这个时候我突然后一个想法,可不可以通过用两个手指进行缩放来实现颜色块儿的布局呢?

我们来添加一个两个手指的点击手势:
    let twoFingerTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.handleTwoFingerTapGesture(_:)))
    twoFingerTapGestureRecognizer.numberOfTouchesRequired = 2
    self.view.addGestureRecognizer(twoFingerTapGestureRecognizer)

随机创建两个颜色的位置。并且,增加第一个位置总比第二个位置坐标相对值小的约束。另外,每次在控制台中输出新的位置

func handleTwoFingerTapGesture(tap: UITapGestureRecognizer) {
    
    let secondColorLocation = arc4random_uniform(100)
    let firstColorLocation = arc4random_uniform(secondColorLocation - 1)
    gradientLayer.locations = [NSNumber(double: Double(firstColorLocation)/100.0), NSNumber(double: Double(secondColorLocation)/100.0)]
    print(gradientLayer.locations!)
}

效果是这个样子哒:

点击渐变.gif

五、渐变方向

上面所展现的效果都是竖直方向上的变化,那么可不可以实现360°的旋转呢? Of course!

首先我们来创建一个枚举来描述梯度方向
enum PanDirections: Int {
   case Right
   case Left
   case Bottom
   case Top
   case TopLeftToBottomRight
   case TopRightToBottomLeft
   case BottomLeftToTopRight
   case BottomRightToTopLeft
}
创建一个新的属性来描述渐变梯度方向
var panDirection: PanDirections!

panDirection 属性根据手指的移动将会得到相应的值。我们需要解决的两个问题:首先,我们需要确定方向,并赋予该属性对应的数值。之后,需要检测手势方向,去确定 startPoint 和 endPoint 这两个属性的数值。

然后我们来创建一个拖动手指:
    let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(ViewController.handlePanGestureRecognizer(_:)))
    self.view.addGestureRecognizer(panGestureRecognizer)

我们将会使用 gesture recogniser 的 velocity 速度属性。如果速度在任意方向上(x 或 y)超过 300 个 point ,则会产生效果。逻辑很简单:为了检查在水平轴上的手势速度。然后在竖直方向上进行二次检测。

func handlePanGestureRecognizer(pan: UIPanGestureRecognizer) {
    let velocity = pan.velocityInView(self.view)
    
    if pan.state == UIGestureRecognizerState.Changed {
        if velocity.x > 300.0 {
            // 水平向右的情况
            // 之后检测竖直方向上的速度
            
            if velocity.y > 300.0 {
                // 从左上到右下
                panDirection = PanDirections.TopLeftToBottomRight
            }
            else if velocity.y < -300.0 {
                // 从左下到右上
                panDirection = PanDirections.BottomLeftToTopRight
            }
            else {
                // 水平向右
                panDirection = PanDirections.Right
            }
        }
        else if velocity.x < -300.0 {
            // 水平方向想左的情况
            // 之后检测数值方向上的速度
            
            if velocity.y > 300.0 {
                // 从右上到左下
                panDirection = PanDirections.TopRightToBottomLeft
            }
            else if velocity.y < -300.0 {
                // 从右下到左上
                panDirection = PanDirections.BottomRightToTopLeft
            }
            else {
                // 水平向左
                panDirection = PanDirections.Left
            }
        }
        else {
            // 只有竖直方向上的状态(向上或向下)
            
            if velocity.y > 300.0 {
                // 竖直向下
                panDirection = PanDirections.Bottom
            }
            else if velocity.y < -300.0 {
                // 竖直向上
                panDirection = PanDirections.Top
            }
            else {
                // 无手势
                panDirection = nil
            }
        }
    }
    else if pan.state == UIGestureRecognizerState.Ended {
        changeGradientDirection()
    }
}

需要注意两点(除了确定手势方向以外):

  • 1.如果不满足任何一个方向的情况,panDirection 应赋 nil
  • 2.如果方向是特殊的,并且手势处于 Changed 状态。当手势结束时,将会调用 changeGradientDirection() 方法,因此该 panDirection 属性也适用于方向变化。

下面的方法也很容易,正如之前设置 startPoint 和 endPoint 属性一样,通过观测 x 和 y 的坐标来确定手势方向:

  func changeGradientDirection() {
    if panDirection != nil {
        switch panDirection.rawValue {
        case PanDirections.Right.rawValue:
            gradientLayer.startPoint = CGPointMake(0.0, 0.5)
            gradientLayer.endPoint = CGPointMake(1.0, 0.5)
            
        case PanDirections.Left.rawValue:
            gradientLayer.startPoint = CGPointMake(1.0, 0.5)
            gradientLayer.endPoint = CGPointMake(0.0, 0.5)
            
        case PanDirections.Bottom.rawValue:
            gradientLayer.startPoint = CGPointMake(0.5, 0.0)
            gradientLayer.endPoint = CGPointMake(0.5, 1.0)
            
        case PanDirections.Top.rawValue:
            gradientLayer.startPoint = CGPointMake(0.5, 1.0)
            gradientLayer.endPoint = CGPointMake(0.5, 0.0)
            
        case PanDirections.TopLeftToBottomRight.rawValue:
            gradientLayer.startPoint = CGPointMake(0.0, 0.0)
            gradientLayer.endPoint = CGPointMake(1.0, 1.0)
            
        case PanDirections.TopRightToBottomLeft.rawValue:
            gradientLayer.startPoint = CGPointMake(1.0, 0.0)
            gradientLayer.endPoint = CGPointMake(0.0, 1.0)
            
        case PanDirections.BottomLeftToTopRight.rawValue:
            gradientLayer.startPoint = CGPointMake(0.0, 1.0)
            gradientLayer.endPoint = CGPointMake(1.0, 0.0)
            
        default:
            gradientLayer.startPoint = CGPointMake(1.0, 1.0)
            gradientLayer.endPoint = CGPointMake(0.0, 0.0)
        }
    }
  }
运行后的结果如下:
点击渐变.gif
(2016/12/28)更新:

最近在仿哔哩哔哩,遇到了在cell上添加遮盖的问题,所以用到了CAGradientLayer ,其实很简单,就是在图片的layer上添加CAGradientLayer 。

 let gradientLayer = CAGradientLayer()
 gradientLayer.frame = CGRectMake(0, 100, 100, 30)
 gradientLayer.colors = [UIColor.clearColor().CGColor,UIColor.blackColor().CGColor]
 cell.icon.layer.addSublayer(gradientLayer)
 //也可以这样:
 cell.layer.insertSublayer(gradientLayer, atIndex: 0)

效果是这样的:


萌萌哒.png

总结

看到了上面的效果之后是不是觉得颜色渐变很容易实现呢?通过多个属性赋以合适的数值并将其组合,你可以很容易地实现一个不错的渐变效果。支持动画也是它的优势之一。快来动手实现你喜欢的效果吧! ( _ )/~~拜拜

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

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,029评论 4 62
  • 嗯哼嗯哼蹦擦擦~~~ 转载自:https://github.com/Tim9Liu9/TimLiu-iOS 目录 ...
    philiha阅读 4,846评论 0 6
  • 能疗伤的是时间里另外有东西 反正时间不是药 药在时间里 那些始终不和的人 他们的时间里没有药 或者是 真不幸 他们...
    梦姑凉阅读 277评论 0 0
  • 本宝宝好歹也是在社会上沉浮打拼的一根老油条了,这几天那个林欣还是林默的“林化名”可是给我又上了一堂课! 原来,有一...
    宫斗剧林林1阅读 582评论 0 0
  • 说死亡来自背后的人,在奔跑; 说死亡就在前方的人,在等待; 所以我,走走停停走走停停…… 。
    早晚的露露阅读 318评论 0 0