[TOC]

㈠CALayer

import

要学Core Animation就应该先学好Layer(层)。因为: layer是实现animation的载体,所有动画都是基于CALayer来实现。

  • 图层
  • 继承NSObject,不能响应事件 <区别与UIVIew,UIView继承UIResponder,能响应事件。>
  • If the layer object was created by a view, the view typically assigns itself as the layer’s delegate automatically, and you should not change that relationship. ==创建UIView默认拥有 CALayer,而且CALayer 的 Delegate 是 UIView==。

UIView与Layer之间的关系图:

  • UIView有一个属性layer(图层), 一个View可以有很多图层,只有root layer才是真正跟view打交道的,UIView之所以能显示在屏幕上,就是图层展现的结果。

    当UIView需要显示到屏幕上时,会调用drawRect:方法进行绘图,并且会将所有内容绘制在自己的图层上,绘图完毕后,系统会将图层拷贝到屏幕上,于是就完成了UIView的显示。

    因此,通过操作这个CALayer对象,可以很方便地调整UIView的一些界面属性,比如:阴影、圆角大小、边框宽度和颜色等。

  • Layer层是可以放很多个子layer,也就可以实现多种多样的呈现效果。

UIView与Layer之间的关系图

Api_CALayer

@interface CALayer : NSObject <NSCoding, CAMediaTiming>
@protocol CAAction, CALayerDelegate; //对应UIView就是layer的代理
@interface CALayer : NSObject <NSCoding, CAMediaTiming>

# Creating a Layer //初始化
+ (instancetype)layer;
- (instancetype)init;
- (instancetype)initWithLayer:(id)layer;

//代理
@property(nullable, weak) id <CALayerDelegate> delegate;

//Identifying the Layer
@property(nullable, copy) NSString *name;


# Accessing Related Layer Objects
- (nullable instancetype)presentationLayer;
- (instancetype)modelLayer;


# Providing the Layer’s Content
@property(nullable, strong) id contents;  //self.customView.layer.contents=(id)[UIImage imageNamed:@"me"].CGImage; contents表示接受内容
@property CGRect contentsRect;
@property CGRect contentsCenter;
- (void)display;
- (void)drawInContext:(CGContextRef)ctx;


# Modifying the Layer’s Appearance
@property(copy) NSString *contentsGravity;

@property float opacity;
@property(getter=isHidden) BOOL hidden;
@property(nullable, strong) CALayer *mask;//面具
@property BOOL masksToBounds;
@property(getter=isDoubleSided) BOOL doubleSided;

@property CGFloat cornerRadius;
@property CGFloat borderWidth;
@property(nullable) CGColorRef borderColor;
@property(nullable) CGColorRef backgroundColor;

@property(nullable) CGColorRef shadowColor;
@property float shadowOpacity;
@property CGSize shadowOffset;
@property CGFloat shadowRadius;
@property(nullable) CGPathRef shadowPath; //路径

@property(nullable, copy) NSDictionary *style;
@property BOOL allowsGroupOpacity;
@property BOOL allowsEdgeAntialiasing;



# Accessing the Layer’s Filters
@property(nullable, copy) NSArray *filters;
@property(nullable, strong) id compositingFilter;
@property(nullable, copy) NSArray *backgroundFilters;

@property(copy) NSString *minificationFilter;
@property(copy) NSString *magnificationFilter;
@property float minificationFilterBias;


# Configuring the Layer’s Rendering Behavior
@property(getter=isOpaque) BOOL opaque;
@property CAEdgeAntialiasingMask edgeAntialiasingMask;
@property(getter=isGeometryFlipped) BOOL geometryFlipped;
- (BOOL)contentsAreFlipped;
@property BOOL drawsAsynchronously
- (void)renderInContext:(CGContextRef)ctx;
@property BOOL shouldRasterize;
@property CGFloat rasterizationScale;



# Modifying the Layer Geometry
@property CGRect frame;
@property CGRect bounds;
@property CGPoint position;
@property CGFloat zPosition;  //z轴
@property CGPoint anchorPoint; //锚点
@property CGFloat anchorPointZ;
@property CGFloat contentsScale



# Managing the Layer’s Transform

@property CATransform3D transform; //矩阵
@property CATransform3D sublayerTransform;
- (CGAffineTransform)affineTransform;
- (void)setAffineTransform:(CGAffineTransform)m;



# Managing the Layer Hierarchy
@property(nullable, copy) NSArray<CALayer *> *sublayers;
@property(nullable, readonly) CALayer *superlayer;

- (void)insertSublayer:(CALayer *)layer atIndex:(unsigned)idx;
- (void)insertSublayer:(CALayer *)layer below:(nullable CALayer *)sibling;
- (void)insertSublayer:(CALayer *)layer above:(nullable CALayer *)sibling;

- (void)replaceSublayer:(CALayer *)layer with:(CALayer *)layer2;

- (void)addSublayer:(CALayer *)layer;
- (void)removeFromSuperlayer;



# Updating Layer Display

- (void)setNeedsDisplay;
- (void)setNeedsDisplayInRect:(CGRect)r;
- (BOOL)needsDisplay;
- (void)displayIfNeeded;
@property BOOL needsDisplayOnBoundsChange;
+ (BOOL)needsDisplayForKey:(NSString *)key;



# Layer Animations
- (void)addAnimation:(CAAnimation *)anim forKey:(nullable NSString *)key;   //添加动画;Key用来区分多个动画,如果只有一个就就可以设为nil。
- (nullable CAAnimation *)animationForKey:(NSString *)key;
- (void)removeAllAnimations;
- (void)removeAnimationForKey:(NSString *)key;
- (nullable NSArray<NSString *> *)animationKeys;


# Managing Layer Resizing and Layout
- (void)setNeedsLayout;
- (void)layoutIfNeeded;
- (void)layoutSublayers;
- (BOOL)needsLayout;

- (CGSize)preferredFrameSize;



# Getting the Layer’s Actions
+ (nullable id<CAAction>)defaultActionForKey:(NSString *)event;
- (nullable id<CAAction>)actionForKey:(NSString *)event;
@property(nullable, copy) NSDictionary<NSString *, id<CAAction>> *actions;


# Mapping Between Coordinate and Time Spaces
- (CGPoint)convertPoint:(CGPoint)p fromLayer:(nullable CALayer *)l;
- (CGPoint)convertPoint:(CGPoint)p toLayer:(nullable CALayer *)l;
- (CGRect)convertRect:(CGRect)r fromLayer:(nullable CALayer *)l;
- (CGRect)convertRect:(CGRect)r toLayer:(nullable CALayer *)l;

- (CFTimeInterval)convertTime:(CFTimeInterval)t fromLayer:(nullable CALayer *)l;
- (CFTimeInterval)convertTime:(CFTimeInterval)t toLayer:(nullable CALayer *)l;



# Hit Testing
- (nullable CALayer *)hitTest:(CGPoint)p;
- (BOOL)containsPoint:(CGPoint)p;



# Scrolling
@property(readonly) CGRect visibleRect;
- (void)scrollPoint:(CGPoint)p;


# Key-Value Coding Extensions
+ (nullable id)defaultValueForKey:(NSString *)key;
- (BOOL)shouldArchiveValueForKey:(NSString *)key;
- (void)scrollRectToVisible:(CGRect)r;


# Instance Properties
@property(copy) NSString *contentsFormat


@end



@protocol CAAction

/* Called to trigger the event named 'path' on the receiver. The object
 * (e.g. the layer) on which the event happened is 'anObject'. The
 * arguments dictionary may be nil, if non-nil it carries parameters
 * associated with the event. */

- (void)runActionForKey:(NSString *)event object:(id)anObject arguments:(nullable NSDictionary *)dict;

@end


@protocol CALayerDelegate <NSObject>
@optional

- (void)displayLayer:(CALayer *)layer;
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
- (void)layerWillDraw:(CALayer *)layer;
- (void)layoutSublayersOfLayer:(CALayer *)layer;
- (nullable id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event;

@end

/** Layer `contentsGravity' values. **/

CA_EXTERN NSString * const kCAGravityCenter;
CA_EXTERN NSString * const kCAGravityTop;
CA_EXTERN NSString * const kCAGravityBottom;
CA_EXTERN NSString * const kCAGravityLeft;
CA_EXTERN NSString * const kCAGravityRight;
CA_EXTERN NSString * const kCAGravityTopLeft;
CA_EXTERN NSString * const kCAGravityTopRight;
CA_EXTERN NSString * const kCAGravityBottomLeft;
CA_EXTERN NSString * const kCAGravityBottomRight;
CA_EXTERN NSString * const kCAGravityResize;
CA_EXTERN NSString * const kCAGravityResizeAspect;
CA_EXTERN NSString * const kCAGravityResizeAspectFill;


/** Layer `contentsFormat` values. **/

CA_EXTERN NSString * const kCAContentsFormatRGBA8Uint ;
CA_EXTERN NSString * const kCAContentsFormatRGBA16Float;
CA_EXTERN NSString * const kCAContentsFormatGray8Uint;


/** Contents filter names. **/

CA_EXTERN NSString * const kCAFilterNearest;
CA_EXTERN NSString * const kCAFilterLinear;


/* Trilinear minification filter. Enables mipmap generation. Some
 * renderers may ignore this, or impose additional restrictions, such
 * as source images requiring power-of-two dimensions. */

CA_EXTERN NSString * const kCAFilterTrilinear;


/** Layer event names. **/

CA_EXTERN NSString * const kCAOnOrderIn;
CA_EXTERN NSString * const kCAOnOrderOut;


/** The animation key used for transitions. **/

CA_EXTERN NSString * const kCATransition;

typedef NS_OPTIONS (unsigned int, CAEdgeAntialiasingMask)
{
    kCALayerLeftEdge      = 1U << 0,      /* Minimum X edge. */
    kCALayerRightEdge     = 1U << 1,      /* Maximum X edge. */
    kCALayerBottomEdge    = 1U << 2,      /* Minimum Y edge. */
    kCALayerTopEdge       = 1U << 3,      /* Maximum Y edge. */
};

layer的坐标系统:

CALayer有2个非常重要的属性:position和anchorPoint

@property CGPoint position;
//position是相对于父layer而言的,用来设置CALayer在父层中的位置,默认为(0, 0),左上角。

@property CGPoint anchorPoint;
//称为“定位点”、“锚点”
//anchorPoint是相对于自身而言的,决定着CALayer身上的哪个点会在position属性所指的位置
//它的x、y取值范围都是0~1,默认为(0.5, 0.5),也就是正中央:因此,默认锚点下的position其实就相当于center。

说明:添加一个红色图层到绿色图层上

  • 红色图层显示到什么位置,由position属性决定。假设红色图层的position是(100,100)
  • 到底把红色图层的哪个点移动到(100,100)的坐标位置,这就是锚点,红色图层的锚点是(0,0)

img

红色图层的锚点是(0.5,0.5)

img

红色图层的锚点是(1,1)

img

红色图层的锚点是(0.5,0)

img

//其他关键属性:

// 与UIView的bounds类似的,获取或设置图层的大小
@property CGRect bounds;

// 层与层之间有上下层的关系,设置Z轴方向的值,可以指定哪个层在上,哪个层在下
// Higher values place this layer visually closer to the viewer than layers with lower values.
@property CGFloat zPosition;

// 图层形变,做动画常用
@property CATransform3D transform;
@property CATransform3D sublayerTransform;
- (CGAffineTransform)affineTransform;
- (void)setAffineTransform:(CGAffineTransform)m;

//添加动画
- (void)addAnimation:(CAAnimation *)anim forKey:(nullable NSString *)key;  //Key用来区分多个动画,如果只有一个就就可以设为nil。

温馨提示:在CALayer中很少使用frame属性,因为frame本身不支持动画效果,通常使用boundsposition代替。CALayer中透明度使用opacity表示而不是alpha;中心点使用position表示而不是center

应用

创建一个layer需要5步:

CALayer *layer=[[CALayeralloc]init]; //layer
layer.position=CGPointMake(50,50); //anchorPoint在父layer中的位置
layer.bounds=CGRectMake(0,0,30, 30); //大小
layer.backgroundColor=[UIColorredColor].CGColor; //颜色
[self.view.layeraddSublayer:layer]; //加到父layer上

边框:borderWidth+borderColor
  layer.borderWidth=3;
  layer.borderColor=[UIColorpurpleColor].CGColor;

圆角: 
   layer1.cornerRadius=45;
   layer.masksToBounds=YES;//切掉超出的部分

阴影:shadowOpacity+shadowOffset+shadowColor
   layer.shadowColor=[UIColorblackColor].CGColor;
   layer.shadowOffset=CGSizeMake(10,10);
   layer.shadowOpacity=1;

遮罩:mask
  //参见下面

1.在layer上添加内容(layer)

//设置边框的宽度为20
self.customView.layer.borderWidth=5;
//设置边框的颜色
self.customView.layer.borderColor=[UIColor blackColor].CGColor;
//设置layer的圆角
self.customView.layer.cornerRadius=20;
//设置超过子图层的部分裁减掉
self.customView.layer.masksToBounds=YES;

//在view的图层上添加一个image,contents表示接受内容
//注意:layer中不能直接接受UI框架中的东西
self.customView.layer.contents=(id)[UIImage imageNamed:@"me"].CGImage;

2.设置阴影

//设置阴影的颜色
self.customView.layer.shadowColor=[UIColor blackColor].CGColor;
//设置阴影的偏移量,如果为正数,则代表为往右边偏移
self.customView.layer.shadowOffset=CGSizeMake(15, 5);
//设置阴影的透明度(0~1之间,0表示完全透明)
self.customView.layer.shadowOpacity=0.6;

3.设置2D 3D效果

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
  //通过uiview设置(2D效果,x,y二个方向)
  self.iconView.transform=CGAffineTransformMakeTranslation(0, -100);
  //通过layer来设置(3D效果,x,y,z三个方向)
  self.iconView.layer.transform=CATransform3DMakeTranslation(100, 20, 0);
  //3D旋转
  self.iconView.layer.transform=CATransform3DMakeRotation(M_PI_4, 1, 1, 0.5);


 //通过KVC来设置
  NSValue *v=[NSValue valueWithCATransform3D:CATransform3DMakeTranslation(100, 20, 0)];
  [self.iconView.layer setValue:v forKeyPath:@"transform"];
  //在x的方向上向左移动100
  [self.iconView.layer setValue:@(-100) forKeyPath:@"transform.translation.x"];
}

4.点击放大移动

#import "LayerViewController.h"
#define kLayerWidth 50


@interface LayerViewController ()

@property (nonatomic, strong) CALayer *movableCircleLayer;

@end

@implementation LayerViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];

    self.movableCircleLayer = [CALayer layer];
    // 指定大小
    self.movableCircleLayer.bounds = CGRectMake(0, 0, kLayerWidth, kLayerWidth);
    // 指定中心点
    self.movableCircleLayer.position = self.view.center;
    // 变成圆形
    self.movableCircleLayer.cornerRadius = kLayerWidth / 2;
    // 指定背景色
    self.movableCircleLayer.backgroundColor = [UIColor blueColor].CGColor;
    // 设置阴影
    self.movableCircleLayer.shadowColor = [UIColor grayColor].CGColor;
    self.movableCircleLayer.shadowOffset = CGSizeMake(5, 5);
    self.movableCircleLayer.shadowOpacity = 0.8;

    [self.view.layer addSublayer:self.movableCircleLayer];
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    CGFloat width = kLayerWidth;
    if (self.movableCircleLayer.bounds.size.width <= kLayerWidth) {
        width = kLayerWidth * 2.5;
    }
    // 修改大小
    self.movableCircleLayer.bounds = CGRectMake(0, 0, width, width);
    // 将中心位置放到点击位置
    self.movableCircleLayer.position = [[touches anyObject] locationInView:self.view];
    // 再修改成圆形
    self.movableCircleLayer.cornerRadius = width / 2;
}

5.mask属性

CALayer有一个属性叫做mask

我们称为遮罩:

遮罩层必须至少有两个图层,上面的一个图层为“遮罩层”,下面的称“被遮罩层”;只有相重叠的地方才会被显示:在重叠区域,遮罩层变成"透明",可以看到被遮罩层的显示的东西,而其他区域看不到,看上去就像是被切割了一样。

遮罩层Color属性是无关紧要的,真正重要的是轮廓,即路径,按照轮廓切割。

案例

1.渐变字体

img

CAGradientLayer *gradientLayer = [CAGradientLayer layer];  
gradientLayer.frame = CGRectMake(100, 300, 200, 25);  
[gradientLayer setStartPoint:CGPointMake(0.0, 0.0)];  
[gradientLayer setEndPoint:CGPointMake(0.0, 1.0)];  
gradientLayer.colors = @[(id)[UIColor redColor].CGColor, (id)[UIColor yellowColor].CGColor,(id)[UIColor greenColor].CGColor];  

UILabel *label = [[UILabel alloc] initWithFrame:gradientLayer.bounds];  
label.text = @"红黄绿渐变~~";  
label.font = [UIFont boldSystemFontOfSize:25];  
label.backgroundColor = [UIColor clearColor];  
[self.view addSubview:label];  

[self.view.layer addSublayer:gradientLayer];  
gradientLayer.mask = label.layer;

原理:label的layer作为遮罩层,它的轮廓就是文字,文字部分透明,透过文字看到被遮罩层gradientLayer的渐变颜色,透明文字轮廓叠加在渐变颜色上面所呈现的效果。

2.颜色渐变的图片

img

CAGradientLayer *gradientLayer = [CAGradientLayer layer];  
gradientLayer.frame = CGRectMake(100, 300, 150, 50);  
gradientLayer.colors = @[(id)[UIColor redColor].CGColor, (id)[UIColor yellowColor].CGColor,(id)[UIColor greenColor].CGColor];  
[gradientLayer setStartPoint:CGPointMake(0.0, 0.0)];  
[gradientLayer setEndPoint:CGPointMake(0.0, 1.0)];  

UIImageView *imageView = [[UIImageView alloc]initWithFrame:gradientLayer.bounds];  
imageView.image = [UIImage imageNamed:@"Map_help_button"];  
[self.view addSubview:imageView];  

[self.view.layer addSublayer:gradientLayer];  
gradientLayer.mask = imageView.layer;  

//imageview的layer的轮廓是图片的轮廓,透明叠加在gradientLayer上面。

3.通过CAShapeLayer实现图片的裁剪

img

UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];
imageView.center = self.view.center;
imageView.image = [UIImage imageNamed:@"Share_bag"];
[self.view addSubview:imageView];

UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(50, 50)
                                                    radius:100 / 2.f
                                                startAngle:0
                                                  endAngle:M_PI*2
                                                 clockwise:YES];
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = imageView.bounds;
maskLayer.path = path.CGPath;

[self.view.layer addSublayer:maskLayer];
imageView.layer.mask = maskLayer;

裁剪后的效果图:

img

4.渐变进度条

整个动画效果主要可以分成4步骤:

//1.创建一个CALayer 做为背景色进度条

CALayer *bgLayer = [CALayer layer];
bgLayer.frame = CGRectMake(kNumberMarkWidth / 2, self.numberMarkView.bottom + 10.f, self.width - kNumberMarkWidth / 2, kProcessHeight);
bgLayer.backgroundColor = [UIColor colorWithHex:0xF5F5F5].CGColor;
bgLayer.masksToBounds = YES;
bgLayer.cornerRadius = kProcessHeight / 2;
[self.layer addSublayer:bgLayer];

//2.创建一个CAGradientLayer 渐变效果的Layer

self.gradientLayer =  [CAGradientLayer layer];
self.gradientLayer.frame = bgLayer.frame;
self.gradientLayer.masksToBounds = YES;
self.gradientLayer.cornerRadius = kProcessHeight / 2;
// 设置渐变颜色数组
[self.gradientLayer setColors:[NSArray arrayWithObjects:
                               (id)[[UIColor colorWithHex:0xFF6347] CGColor],
                               [(id)[UIColor colorWithHex:0xFFEC8B] CGColor],
                               (id)[[UIColor colorWithHex:0xEEEE00] CGColor],
                               (id)[[UIColor colorWithHex:0x7FFF00] CGColor],
                               nil]];
// 设置渐变位置数组
[self.gradientLayer setLocations:@[@0.3, @0.5, @0.7, @1]];
// 设置渐变开始和结束位置
[self.gradientLayer setStartPoint:CGPointMake(0, 0)];
[self.gradientLayer setEndPoint:CGPointMake(1, 0)];

//3. 创建一个Mask Layer,并设置为CAGradientLayer渐变层的Mask. 然后通过设置maskLayer宽度来控制进度

self.maskLayer = [CALayer layer];
self.maskLayer.frame = CGRectMake(0, 0, (self.width - kNumberMarkWidth / 2) * self.percent / 100.f, kProcessHeight);
[self.gradientLayer setMask:self.maskLayer];

// 进度条动画
// 进度条动画

5.图片剪裁

#import "ImageViewController.h"
#define kPhotoWidth 100

@interface ImageViewController ()
@end

@implementation ImageViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];

    CALayer *layer = [CALayer layer];
    layer.delegate = self;
    layer.bounds = CGRectMake(0, 0, kPhotoWidth, kPhotoWidth);
    layer.position = self.view.center;
    layer.cornerRadius = kPhotoWidth / 2;

    //要设置此属性才能裁剪成圆形,但是添加此属性后,下面设置的阴影就没有了。
    layer.masksToBounds = YES;
    layer.borderColor = [UIColor whiteColor].CGColor;
    layer.borderWidth = 1;

    //  // 阴影
    //  layer.shadowColor = [UIColor blueColor].CGColor;
    //  layer.shadowOffset = CGSizeMake(4, 4);
    //  layer.shadowOpacity = 0.9;
    [self.view.layer addSublayer:layer];

    // 当设置masksToBounds为YES后,要想要阴影效果,就需要额外添加一个图层作为阴影图层了
    CALayer *shadowLayer = [CALayer layer];
    shadowLayer.position = layer.position;
    shadowLayer.bounds = layer.bounds;
    shadowLayer.cornerRadius = layer.cornerRadius;
    shadowLayer.shadowOpacity = 1.0;
    shadowLayer.shadowColor = [UIColor redColor].CGColor;
    shadowLayer.shadowOffset = CGSizeMake(2, 1);
    shadowLayer.borderWidth = layer.borderWidth;
    shadowLayer.borderColor = [UIColor whiteColor].CGColor;
    [self.view.layer insertSublayer:shadowLayer below:layer];

    // 调用此方法,否则代理不会调用
    [layer setNeedsDisplay];
}

#pragma mark - CALayerDelegate
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
    //将当前上下文入栈
    CGContextSaveGState(ctx);

    // 注意:坐标系统与UIView的不同,这里使用的是笛卡尔积坐标系,也就是左下角为(0,0)

    // 先向平移后旋转也可以解决倒立的问题
    CGContextTranslateCTM(ctx, kPhotoWidth, kPhotoWidth);
    CGContextRotateCTM(ctx, M_PI);

    UIImage *image = [UIImage imageNamed:@"test.jpg"];
    CGContextDrawImage(ctx, CGRectMake(0, 0, kPhotoWidth, kPhotoWidth), image.CGImage);

    // 任务完成后,将当前上下文退栈
    CGContextRestoreGState(ctx);
}

/*
注意:
1. 一旦设置了层的layer.masksToBounds为YES,那么阴影效果就没有了,也就是不能直接对该层设置shadow相关的属性了,设置了也没有作用。因此,这里使用另外一个图层来专门呈现阴影效果的。我们需要将阴影层放在图片层的下面,别把图片层给挡住了。

2. 默认是不会调用代理方法的,必须要调用setNeedsDisplay方法才会回调。因此,要绘制哪个层就调用哪个层对象调用该方法。

3. CGContextSaveGState方法是入栈操作,也就是将当前设备上下文入栈。既然有入栈,自然也得有出栈,最后一行CGContextRestoreGState就是将设备上下文出栈。 

4. 矩阵操作:通过CGContextDrawImage绘制的图片是倒立的,因此我们需要进行矩阵相关变换。
*/

results matching ""

    No results matching ""