美文网首页Swift_Learn画图与动画Swift编程
Swift之CAGradientLayer 实现渐变色效果

Swift之CAGradientLayer 实现渐变色效果

作者: 大脸猫121 | 来源:发表于2016-09-18 16:05 被阅读4270次

小伙伴们在开发一款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

总结

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

相关文章

网友评论

  • 王艳锋:怎么设置在background 上,现在会把subview 覆盖
    大脸猫121:@王艳锋 嗯嗯,我以前我出现过这个问题,好棒
    王艳锋:找到办法了,self.layer.insertSublayer(gradientLayer, at: 0),这样不会遮挡subview
  • 西瓜阿多多:用 snapKit 布局的, 如何设置渐变 我打印的View的frame是(0,0,0,0)
    大脸猫121:@小兔不白siy 应该是因为此时你的view还没有大小,你可以尝试在已经添加完之后设置渐变
  • c5abaf69f91b:我设置了背景的渐变色 但是为什么只渐变了一部分?另外的部分是白色
    大脸猫121:@天空久久 :smile: 找到问题出现的原因就好
    c5abaf69f91b:2种,我找到错误了,我设置的frame不对
    大脸猫121:@天空久久 这个还得视情况而定,你设置了几种颜色啊?
  • 5d789e8efb6a:老师让我明天让我讲以下课外知识,我看这个很不错, 别的都没意思,赞赞赞,可以让我装一波b了
    大脸猫121:@大王叫俺来巡山啦 哈哈,互相学习啦
    5d789e8efb6a:@萌小猫121 谢谢啦, 请问你是工程师吗? 我有好多自己编程问题,我快要炸了
    大脸猫121:@大王叫俺来巡山啦 :blush: 能帮到你很开心

本文标题:Swift之CAGradientLayer 实现渐变色效果

本文链接:https://www.haomeiwen.com/subject/sbtgettx.html