UIView
布局包括Frame、bounds、center,CALayer 则是 frame、bounds、positionanchorPoint
参数position
是依赖于父试图的bounds,移动父试图的bounds ,自试图也会跟着移动。/** Mapping between layer 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;
在判断图层之间的点击时,
containsPoint
方法来判断该point 是否在当前的图层中/* Returns true if the bounds of the layer contains point 'p'. */
- (BOOL)containsPoint:(CGPoint)p;
hitTest
方法,判断被点击的图层,伪代码呈现如下。- (nullable CALayer *)hitTest:(CGPoint)p;
即:CALayer *layer = [self.view.layer hitTest:point];
if(layer == self.targetLayer)xxxxxx
Layer 中有一个属性
geometryFlipped
来对图层构建来进行翻转。
实际应用中,可以写一个分类来进行Mac 的界面布局,使得界面编写可以更加适合iOS 开发人员。
使用图层蒙版为图层设置圆角
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:self.view.frame byRoundingCorners:UIRectCornerTopLeft|UIRectCornerTopRight cornerRadii:CGSizeMake(20, 20)];
shapeLayer.path = path.CGPath;
self.view.layer.mask = shapeLayer;
使用CoreGraphics 来给图层绘制圆角
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [UIScreen mainScreen].scale);
[[UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:radius] addClip];
[self.image drawInRect:self.bounds];
self.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
上述的几种方式里面 方法一会造成离屏渲染,最好的方式是使用layer addSublayer:layer
,这样会避免上述问题。
使用图层蒙版(Mask)来进行绘制在用Instruments的animation 查看时会出现黄色的
离屏渲染
信号, 但是使用Instruments的Timer 来查看程序执行耗时的时候,CAShapeLayer 的mask耗时相比较小。 耗时的原因是因为ShapeLayer 使用的是GPU 绘制,而coregraphics 使用的cpu .
过度动画是CAtransition参数。type 指定执行动画的类型、fillmode 指定动画在执行之后是否恢复原样。
CATransition *transition = [CATransition animation];
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
transition.type = @"cube";
transition.subtype = kCATransitionPush;
transition.fillMode = kCAFillModeForwards;
transition.duration = 0.5f;
[self.view.layer addAnimation:transition forKey:@"animation"];
CAShapeLayer
是一个通过矢量图形而不是bitmap
来绘制图层子类。
优点:
CAGradientLayer
是用来生成两种或更多颜色平滑渐变的。
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = self.view.frame;
[self.view.layer addSublayer:gradientLayer];
gradientLayer.colors = @[(__bridge id)[UIColor redColor].CGColor,(__bridge id)[UIColor blueColor].CGColor];
//设置gradientlayer 的起始点和截止点
gradientLayer.startPoint = CGPointMake(0, 0);
gradientLayer.endPoint = CGPointMake(1, 1);
CATiledLayer
在你如果需要绘制一个很大的图片的时候,从内存中读取是不明智的。在 UIImage
的imageNamed:
方法或者imageWithContentsofFile
的方法将会阻塞你的用户界面,至少会导致动画卡顿现象。
CATiledLayer 为载入大图造成的性能问题提供了一个解决方案:将大图分解成小片然后将他们单独按需载入。
使用CATiledLayer 来绘图的时候只需要遵守delegate然后实现相应的方法:
-(void)drawLayer:(CATiledLayer *)layer inContext:(CGContextRef)ctx{
CGRect bounds = CGContextGetClipBoundingBox(ctx);
NSInteger x = floorf(bounds.origin.x/layer.tileSize.width);
NSInteger y = floor(bounds.origin.x/layer.tileSize.height);
NSString *imageName = [NSString stringWithFormat:@""];
//使用切割之后的图片来进行绘制
NSString *imagePath = [[NSBundle mainBundle]pathForResource:imageName ofType:@"png"];
UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
UIGraphicsPushContext(ctx);
[image drawInRect:bounds];
UIGraphicsPopContext();
}
CABasicAnimation *basicAnimation = [CABasicAnimation animationWithKeyPath:@"position.x"];
动画的属性和说明
属性 | 说明 |
---|---|
duration | 动画的时长 |
repeatCount | 重复的次数,不停重复设置为HUGE_VALF |
repreatDuration | 在该动画内执行,不计算次数 |
beginTime | 设置动画开始的时间,从开始延迟几秒的话,设置为CACurrentMediaTime()+秒数 |
timeingFunction | 设置动画的速度变化 |
fromValue | 所改变属性的起始值 |
toValue | 所改变属性的结束时的值 |
防止动画结束后回到初始状态
basicAnimation.removedOnCompletion = NO;
basicAnimation.fillMode = KCAFillModeForwards;
代码演示:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.x"];
animation.duration = 5.0f;
animation.fromValue = [NSNumber numberWithInteger:0];
animation.toValue = [NSNumber numberWithInteger:5];
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
CAKeyframeAnimation 不同于CABasicAnimation ,他提供了一系列的值来做动画效果。起源于传动动画,意思是指主导的动画在显著改变发生时重绘当前帧(也就是关键帧),每帧之间剩下的绘制将交给系统来完成。CAKeynimation 也是同样的道理:你提供了显著的帧,然后CoreAnimation在每帧之间进行插入。
//使用贝塞尔曲线绘制 三元曲线
UIBezierPath *path = [[UIBezierPath alloc]init];
[path moveToPoint:CGPointMake(0, 200)];
[path addCurveToPoint:CGPointMake(200, 400) controlPoint1:CGPointMake(50, 100) controlPoint2:CGPointMake(100, 200)];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeLayer.strokeColor = [UIColor redColor].CGColor;
shapeLayer.lineWidth = 3.0f;
shapeLayer.path = path.CGPath;
[self.view.layer addSublayer:shapeLayer];
// 为上面的bgview 添加关键帧动画
CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animation];
keyAnimation.keyPath = @"position";
keyAnimation.duration = 4.0;
keyAnimation.path = path.CGPath;
keyAnimation.removedOnCompletion = NO;
keyAnimation.fillMode = kCAFillModeForwards;
[bgView.layer addAnimation:keyAnimation forKey:@"layer"];
得到的结果如下:
@w=50h=50
在使用关键帧动画时也可以给keyAnimation
的values
属性添加一组数字。
CAAnimationGroup
和CAKeyAnimation
是仅仅用于单独的属性,而CAAnimationGroup 可以把上述的animations
属性组合到一起。
CAKeyAnimation
和CABasicAnimation
的父类CAPropertyAnimation
提供了一个api 来移除当前指定animation
:[bgView.layer removeAnimationForKey:@""]
或者移除所有animation动画:
[bgView.layer removeAllAnimations];
在iOS中,软件绘图通常是有CoreGraphics 框架完成。相比于Core Animation和OpenGL,Core Graphic要慢。CALayerDelegate
中的-drawLayer:inContext:
方法或者UIView
中的-drawRect:
方法,图层就会创建一个绘制上下文,这个上下文需要的大小的内存可以从算式中得出, 如果是3x的屏幕上就会占用更多的内存:
width * heigt * 4bitm * screenScale
软件绘图的消耗代价比较高,提高绘制性能就要减少不必要的软件绘制,即使用CoreGraphic绘制。
在使用CoreGraphic 进行绘制的时候,通常是因为图片或者图层效果不能轻易得到矢量图形,一个矢量绘图可能包含下面几种:
举例说明在使用coregraphic 进行绘制
/** 绘图所用曲线(成员属性)*/
@property(nonatomic,strong)UIBezierPath *path
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint point = [[touches anyObject] locationInView:self.view];
//用户手指点击作为起点
[self.drawPath moveToPoint:point];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint point = [[touches anyObject]locationInView:self.view];
//开始绘制线路
[self.drawPath moveToPoint:point];
//绘制完成之后进行重绘,该方法会调用当前视图的drawrect 方法
[self.view setNeedsDisplay];
}
/**在重绘图层时进行颜色填充*/
-(void)drawRect:(CGRect)rect{
[[UIColor clearColor] setFill];
[[UIColor redColor] setStroke];
[self.drawPath stroke];
}
用户在每次进行手势移动的时候就会进行图层的重绘,对app 的性能而言就是一种灾难。
这样的实现问题在于我们画的越多,程序就会越慢。
CoreAnimation
提供了专门的类,并给他们提供硬件支持。CAShapeLayer
可以用来绘制多边形,直线和曲线,CAGradientLayer
可用来绘制渐变,CATextLayer
可以绘制文本,这些总体上都比Coregraphics
更加快 ,同时他们也避免创造了一个寄宿图。
使用CAShapeLayer
来实现上述功能,只需要将touchmoved
方法里面的setNeedDisplay
换成self.layer.path = self.path.CGPath
图片的加载需要经过一个过程
具体的图片加载和解压,可以看FFMPeg 的使用,其中提到的metadata 和imageBuffer以及decode 的过程。
解压过程是一个大量占用CPU资源的工作,因此UIImage 会retain存储解压后信息的Image Buffer以便给重复的渲染工作提供信息,Image Buffer与图片的实际尺寸有关,与图片文件的大小无关。
相关参考资料:WWDC2018图像最佳实践 Image and Graphics Best Practices
绘图实际消耗的时间通常并不是影响性能的因素。图片消耗很大一部分内存,而且不太可能把需要显示的图片都保留在内存里面,所以需要在应用运行的时候周期性地加载和卸载图片。 图片文件加载的速度被CPU 和 IO 同时影响,虽然iOS 设备中的闪存已经比传统的硬盘快很多,但是仍然比 RAM 慢了将近200倍。
拓展:在对本地编译App速度的优化可以从Xcode缓存读取来进行,相关操作可参考一下链接 加快Xcode编译读写
在加载本地可以缓存的图片文件时可以使用: imageNamed
方法,而imageWithContentsOfFile:
不会进行缓存。
但是上述的方法只能加载本地存在的图片,所以我们要用到另外一种方式:
ImageIO 框架 >ImageIo框架详解
/** 加载本地图片**/
// NSString *currentPath = [[NSBundle mainBundle]pathForResource:@"liu_04" ofType:@"png"];
// NSURL *url = [NSURL fileURLWithPath:currentPath];
NSString *currentPath = @"云端地址";
NSURL *url = [NSURL URLWithString:currentPath];
CGImageSourceRef imageSourceRef = CGImageSourceCreateWithURL((CFURLRef)url, NULL);
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(imageSourceRef, 0, NULL);
CFRelease(imageSourceRef);
UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 400, 100, 100)];
imageView.image = [UIImage imageWithCGImage:imageRef];
[self.view addSubview:imageView];