小伙伴们在开发一款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()
}
运行后的效果如下图所示:
二、渐变色
在createGradientLayer()这个方法中我们并没有设置很多属性,但是有一句是必不可少的,那就是设置渐变的主颜色,我们还可以在数组中添加更多的颜色:
gradientLayer.colors = [UIColor.redColor().CGColor, UIColor.orangeColor().CGColor, UIColor.blueColor().CGColor, UIColor.magentaColor().CGColor, UIColor.yellowColor().CGColor]
运行后的效果如下图所示:
三、切换渐变色
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]
运行后的效果如下:
四、设置颜色的相对坐标
我们前面看到的效果,颜色的位置都是默认的均分,下面我们来自定义的实现:
这个可以通过 CAGradientLayer 的 locations 属性来设置。该属性需要传入一个 NSNumber 对象数组,每个数字确定了每个颜色的起始位置(starting location)。另外,这些数字是浮点数,取值范围在 0.0 到 1.0 之间。
gradientLayer.locations = [0.0, 0.35]
效果是这个样子哒:
这个时候我突然后一个想法,可不可以通过用两个手指进行缩放来实现颜色块儿的布局呢?
我们来添加一个两个手指的点击手势:
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!)
}
效果是这个样子哒:
五、渐变方向
上面所展现的效果都是竖直方向上的变化,那么可不可以实现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)
}
}
}
运行后的结果如下:
(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)
效果是这样的:
总结
看到了上面的效果之后是不是觉得颜色渐变很容易实现呢?通过多个属性赋以合适的数值并将其组合,你可以很容易地实现一个不错的渐变效果。支持动画也是它的优势之一。快来动手实现你喜欢的效果吧! ( _ )/~~拜拜