CAGradientLayer
CAGradientLayer可以极大的丰富页面的色彩。增加设计感。
然后我们的动画知识一个中间过程。动画结束又恢复到原始的内容显示。因此,我们可以直接赋值最终的值,然后动画的最终值刚好是我们的视图,然后给一个开始值做动画,这样就不需要removeOnCompletion。
普通颜色渐变
ini
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor redColor];
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
// 颜色数组,必须强转 id,存 CGColorRef
gradientLayer.colors = @[
(__bridge id)[UIColor blackColor].CGColor,
(__bridge id)[UIColor redColor].CGColor,
(__bridge id)[UIColor greenColor].CGColor
];
// 颜色断点,0~1,和colors数量一一对应,传nil则均匀渐变
gradientLayer.locations = @[@0.0,@0.5, @1.0];
// 起点、终点:比例坐标 (0,0)左上角,(1,1)右下角
// 从上到下:start(0.5,0) end(0.5,1)
// 从左到右:start(0,0.5) end(1,0.5)
gradientLayer.startPoint = CGPointMake(0.5, 0);
gradientLayer.endPoint = CGPointMake(0.5, 1);
// 渐变类型
// kCAGradientLayerAxial 线性(默认)
// kCAGradientLayerRadial 径向
// kCAGradientLayerConic 圆锥渐变 iOS12+
gradientLayer.type = kCAGradientLayerAxial;
// 设置frame,⚠️图层不会自动跟随view约束,布局变化时需要更新frame
gradientLayer.frame = self.view.bounds;
// 插入到最底层当背景
[self.view.layer insertSublayer:gradientLayer atIndex:0];
[self beginAnimation:gradientLayer];
}
- (void)beginAnimation:(CAGradientLayer *)layer{
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"colors";
animation.duration = 8;
animation.fromValue = @[(__bridge id)[UIColor yellowColor].CGColor,
(__bridge id)[UIColor greenColor].CGColor,
(__bridge id)[UIColor blueColor].CGColor];
animation.toValue = @[
(__bridge id)[UIColor blackColor].CGColor,
(__bridge id)[UIColor redColor].CGColor,
(__bridge id)[UIColor greenColor].CGColor
];
[layer addAnimation:animation forKey:nil];
}
渐变文字(mask遮罩)
ini
CATextLayer *textLayer = [CATextLayer layer];
NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:@"OC渐变文字" attributes:@{
NSFontAttributeName: [UIFont systemFontOfSize:40],
NSForegroundColorAttributeName: [UIColor blackColor]
}];
textLayer.string = attrStr;
CAGradientLayer *gradLayer = [CAGradientLayer layer];
gradLayer.frame = CGRectMake(20, 200, 300, 60);
textLayer.frame = CGRectMake(0, 0, gradLayer.bounds.size.width, gradLayer.bounds.size.height);
gradLayer.colors = @[
(__bridge id)[UIColor redColor].CGColor,
(__bridge id)[UIColor greenColor].CGColor
];
gradLayer.startPoint = CGPointMake(0, 0.5);
gradLayer.endPoint = CGPointMake(1, 0.5);
gradLayer.mask = textLayer;
[self.view.layer addSublayer:gradLayer];
}
渐变圆角边框
只看CAShapeLayer的透明通道,不看颜色。
ini
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0,
0,
200,
80) cornerRadius:12];
shapeLayer.path = path.CGPath;
shapeLayer.lineWidth = 4;
// 渲染当前的shapeLayer,只有当前的shaperlayer有颜色,CAGradientLayer才会在上面再覆盖一个颜色
shapeLayer.fillColor = [UIColor clearColor].CGColor;
// shapeLayer.strokeColor = [UIColor greenColor].CGColor;至于是啥颜色都不重要,只看透明通道
shapeLayer.strokeColor = [UIColor blackColor].CGColor;
[self.view.layer addSublayer:shapeLayer];
CAGradientLayer *grad = [CAGradientLayer layer];
grad.colors = @[
(__bridge id)[UIColor systemTealColor].CGColor,
(__bridge id)[UIColor systemPinkColor].CGColor
];
grad.startPoint = CGPointMake(0, 0.4);
grad.endPoint = CGPointMake(1, 0.6);
grad.frame = CGRectMake(100, 100, 200, 80);
grad.mask = shapeLayer;
[self.view.layer addSublayer:grad];
}
4. CAGradientLayer 转 UIImage(用于button背景)
常用的CALayer都可以这么调用。
这些图层的内容完全由 Core Animation 自身管理,不依赖外部渲染引擎:
CAGradientLayer(渐变层)CAShapeLayer(形状层)CATextLayer(文本层)CALayer(基础图层,设置了backgroundColor或contents为静态图片)CAReplicatorLayer(复制层)CAEmitterLayer(粒子发射器层,但动画帧可能不完整)
无法或不可靠渲染的图层类型
这些图层的内容由系统其他框架(如 WebKit、Metal、OpenGL)提供,renderInContext: 无法捕获其真实像素:
WKWebView的底层CALayer:这是最常见的"坑"。即使你调用renderInContext:,得到的也往往是空白或黑屏,因为网页内容由独立的渲染进程绘制,不与主线程的 Core Graphics 上下文共享。CAEAGLLayer/CAMetalLayer:用于 OpenGL/Metal 渲染的图层,其内容是 GPU 直接输出的帧缓冲区,renderInContext:无法访问。AVPlayerLayer:视频播放层,其内容是视频解码器输出的动态帧,静态截图通常无效。SCNView/SKView的底层图层:3D/2D 游戏引擎渲染的内容,同样无法通过此方法捕获
ini
- (UIImage *)gradientImageWithLayer:(CAGradientLayer *)layer {
UIGraphicsBeginImageContextWithOptions(layer.bounds.size, NO, [UIScreen mainScreen].scale);
[layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
// 调用示例
// UIImage *img = [self gradientImageWithLayer:gradLayer];
// self.button.backgroundColor = [UIColor colorWithPatternImage:img];
自定义View,底层直接为CAGradientLayer
UIView的初始化过程中会调用+ (Class)layerClass,如果不写,则默认创建一个CALayer作为self.layer。写了,就会创建一个指定的CALayer子类对象。
比如OpenGL ES中,就是返回一个[CAEAGLLayer class]。
.h
objectivec
#import <UIKit/UIKit.h>
@interface GradientView : UIView
@property (nonatomic, readonly) CAGradientLayer *gradientLayer;
@end
.m
objectivec
#import "GradientView.h"
#import <QuartzCore/QuartzCore.h>
@implementation GradientView
+ (Class)layerClass {
return [CAGradientLayer class];
}
- (CAGradientLayer *)gradientLayer {
return (CAGradientLayer *)self.layer;
}
@end
使用
ini
GradientView *gView = [[GradientView alloc] initWithFrame:CGRectMake(0,0,200,200)];
gView.gradientLayer.colors = @[
(__bridge id)[UIColor redColor].CGColor,
(__bridge id)[UIColor blueColor].CGColor
];
gView.gradientLayer.startPoint = CGPointMake(0,0);
gView.gradientLayer.endPoint = CGPointMake(1,1);
[self.view addSubview:gView];
CATransformLayer
普通的CALayer做矩阵变化的时候,会丢失深度信息,只能平面旋转。
CATransformLayer会保留深度信息,但是本身并没有颜色,需要将CALayer添加到CATransformLayer上面显示。
objectivec
// 平移
CATransform3D CATransform3DMakeTranslation(CGFloat x, CGFloat y, CGFloat z);
// 旋转:在向量(x,y,z)轴旋转 angle弧度
CATransform3D CATransform3DRotate(CATransform3D t, CGFloat angle, CGFloat x, CGFloat y, CGFloat z);
变换顺序:先平移,再旋转 (矩阵乘法顺序,顺序不能颠倒!颠倒就会旋转中心错乱)
M_PI_2= π/2 = 90°
CATransform3D.m34=0表示正交投影,不等于0表示透视投影。CATransform3D.m34=-1/500一般设置这个。
基本使用
正交投影,m34是0。不论远近,都是一样的大小。
透视投影,m34不是0。随着距离增加,位置会变小。CATransform3DTranslate(transform, 0, 0, -2000);负数表示往屏幕里面越来越远,视图变小。整数会让视图变大。
ini
{
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(0, 0, 100, 100);
layer.backgroundColor = [UIColor redColor].CGColor;
// 这个变化矩阵是
// [1 0 0 0]
// [0 1 0 1]
// [0 0 1 0]
// [0 0 0 1]
// 表示这个矩阵啥效果都没有,不平移,不缩放,不旋转
// 一般用来初始化
CATransform3D transform = CATransform3DIdentity;
// 设置透视感:分母越小,透视感越强(近大远小越夸张)
transform.m34 = -1/500.0;
transform = CATransform3DTranslate(transform, 0, 0, -2000);
layer.transform = transform;
CATransformLayer *transformLayer = [CATransformLayer layer];
transformLayer.frame = CGRectMake(50, 100, 100, 100);
[transformLayer addSublayer:layer];
[self.view.layer addSublayer:transformLayer];
}
{
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(0, 0, 100, 100);
layer.backgroundColor = [UIColor redColor].CGColor;
CATransform3D transform = CATransform3DIdentity;
// 设置透视感:分母越小,透视感越强(近大远小越夸张)
// 默认是0
// transform.m34 = 0;
transform = CATransform3DTranslate(transform, 0, 0, -2000);
layer.transform = transform;
CATransformLayer *transformLayer = [CATransformLayer layer];
transformLayer.frame = CGRectMake(200, 100, 100, 100);
[transformLayer addSublayer:layer];
[self.view.layer addSublayer:transformLayer];
return;
}
基于上面的基本信息。我们就可以做到一个3D的游戏渲染引擎了。
但是这样写的话,全部是CPU运算,会比较卡。另外要注意,背面的话要旋转,不然在layer上添加字体会是反的。
下面是一个添加矩形的方法
ini
/**
* 创建一个 3D 长方体
* @param x, y, z: 立方体中心在父视图中的位置
* @param width: 立方体的宽度 (X轴方向)
* @param height: 立方体的高度 (Y轴方向)
* @param depth: 立方体的深度/厚度 (Z轴方向)
* @param r_x/y/z: 整体旋转角度
* @param colors: 6个面的颜色数组 (顺序建议:前、后、左、右、上、下)
*/
- (CATransformLayer *)createBoxAtX:(CGFloat)x
Y:(CGFloat)y
Z:(CGFloat)z
Width:(CGFloat)width
Height:(CGFloat)height
Depth:(CGFloat)depth
RotationX:(CGFloat)r_x
RotationY:(CGFloat)r_y
RotationZ:(CGFloat)r_z
Colors:(NSArray<UIColor *> *)colors {
NSAssert(colors.count == 6, @"必须提供6个面的颜色");
// 1. 创建容器
CATransformLayer *boxLayer = [CATransformLayer layer];
boxLayer.position = CGPointMake(x, y);
// 对容器应用整体旋转
CATransform3D boxTransform = CATransform3DIdentity;
boxTransform = CATransform3DRotate(boxTransform, r_x, 1, 0, 0);
boxTransform = CATransform3DRotate(boxTransform, r_y, 0, 1, 0);
boxTransform = CATransform3DRotate(boxTransform, r_z, 0, 0, 1);
// 应用 Z 轴位移
boxTransform = CATransform3DTranslate(boxTransform, 0, 0, z);
boxLayer.transform = boxTransform;
// 辅助变量:各维度的一半,用于计算平移距离
CGFloat halfW = width / 2.0;
CGFloat halfH = height / 2.0;
CGFloat halfD = depth / 2.0;
// 定义创建单个面的 Block,减少重复代码
void (^addFace)(NSInteger index, CGSize size, CATransform3D transform) = ^(NSInteger index, CGSize size, CATransform3D transform) {
CALayer *face = [CALayer layer];
face.frame = CGRectMake(0, 0, size.width, size.height);
face.backgroundColor = colors[index].CGColor;
// 可选:添加边框让轮廓更清晰
face.borderColor = [UIColor whiteColor].CGColor;
face.borderWidth = 1.0;
// 关键:设置锚点为中心,这样旋转和位移都是基于面中心的
face.anchorPoint = CGPointMake(0.5, 0.5);
// 修正位置:因为 anchorPoint 变了,需要调整 position 保证面在局部坐标系原点开始构建
face.position = CGPointMake(size.width / 2.0, size.height / 2.0);
face.transform = transform;
[boxLayer addSublayer:face];
};
// --- 1. 前面 (Front) ---
// 面向屏幕,沿 Z 轴向前推 halfD
{
CATransform3D t = CATransform3DMakeTranslation(0, 0, halfD);
addFace(0, CGSizeMake(width, height), t);
}
// --- 2. 后面 (Back) ---
// 绕 Y 轴转 180 度,再沿 Z 轴向前推 halfD (此时局部 Z 轴反向,所以是推向后方)
{
CATransform3D t = CATransform3DMakeRotation(M_PI, 0, 1, 0);
t = CATransform3DTranslate(t, 0, 0, halfD);
addFace(1, CGSizeMake(width, height), t);
}
// --- 3. 左面 (Left) ---
// 绕 Y 轴转 -90 度,沿 Z 轴推 halfW
{
CATransform3D t = CATransform3DMakeRotation(-M_PI_2, 0, 1, 0);
t = CATransform3DTranslate(t, 0, 0, halfW);
addFace(2, CGSizeMake(depth, height), t);
}
// --- 4. 右面 (Right) ---
// 绕 Y 轴转 90 度,沿 Z 轴推 halfW
{
CATransform3D t = CATransform3DMakeRotation(M_PI_2, 0, 1, 0);
t = CATransform3DTranslate(t, 0, 0, halfW);
addFace(3, CGSizeMake(depth, height), t);
}
// --- 5. 上面 (Top) ---
// 绕 X 轴转 90 度,沿 Z 轴推 halfH
{
CATransform3D t = CATransform3DMakeRotation(M_PI_2, 1, 0, 0);
t = CATransform3DTranslate(t, 0, 0, halfH);
addFace(4, CGSizeMake(width, depth), t);
}
// --- 6. 下面 (Bottom) ---
// 绕 X 轴转 -90 度,沿 Z 轴推 halfH
{
CATransform3D t = CATransform3DMakeRotation(-M_PI_2, 1, 0, 0);
t = CATransform3DTranslate(t, 0, 0, halfH);
addFace(5, CGSizeMake(width, depth), t);
}
return boxLayer;
}
- (void)viewDidLoad {
[super viewDidLoad];
CALayer *layer1 = [self createBoxAtX:100 Y:100 Z:-200 Width:100 Height:20 Depth:40 RotationX:0 RotationY:0 RotationZ:0 Colors:@[[UIColor redColor],[UIColor greenColor],[UIColor purpleColor], [UIColor whiteColor],[UIColor blackColor],[UIColor grayColor]]];
[self.view.layer addSublayer:layer1];
}