iOS 动画二 CAGradientLayer颜色渐变 CATransformLayer立体效果

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(基础图层,设置了 backgroundColorcontents 为静态图片)
  • 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];
}
相关推荐
用户2181697049305 小时前
iOS 动画 一 CABasicAnimation和CAKeyFrameAnimation
objective-c
2501_915918412 天前
使用 VS Code 写 Swift 做 iOS 开发,插件能实现哪些功能,还有哪些短板
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
00后程序员张3 天前
Xcode vs KXApp,体积差几十G,选轻量方案还是完整工具链?
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
黑科技iOS上架5 天前
Cocos应用App Store4.3被拒怎么自查
macos·ios·objective-c·cocoa·审核·ios混淆·cocos混淆
YJlio6 天前
锤子助手第012个开关:启用朋友圈视频进度条的位置、验证方法与播放交互边界
objective-c·视频播放·功能验证·锤子助手·朋友圈视频进度条·ios微信插件·交互测试
星辰即远方6 天前
RunLoop初步了解
macos·objective-c·cocoa
代码的小搬运工6 天前
Runloop
macos·objective-c·cocoa
用户2181697049309 天前
iOS 底层原理 runloop
objective-c