这是我一直在离开的问题,现在又回来了。我从未真正解决过这个问题。
我一直在尝试使用CADisplayLink动态绘制饼图样式进度。当我同时更新1到4个uiviews时,我的代码工作正常。当我添加不止于此时,馅饼的绘图变得非常生涩。
我想解释一下我一直在尝试的事情,希望有人能指出效率低下并提出更好的绘图方法。
我创建了16个uiviews并为每个子视图添加了一个CAShapeLayer子视图。这是我想要绘制饼图的地方。
我预先计算代表0到360度圆的360 CGPath并将它们存储在一个数组中以尝试提高性能。
在主视图中,我启动了一个displaylink,遍历所有其他视图,计算它应显示的完整饼图的数量,然后找到正确的路径并将其分配给我的shapelayer。
-(void)makepieslices
{
pies=[[NSMutableArray alloc]initWithCapacity:360];
float progress=0;
for(int i=0;i<=360;i++)
{
progress= (i* M_PI)/180;
CGMutablePathRef thePath = CGPathCreateMutable();
CGPathMoveToPoint(thePath, NULL, 0.f, 0.f);
CGPathAddLineToPoint(thePath, NULL, 28, 0.f);
CGPathAddArc(thePath, NULL, 0.f,0.f, 28, 0.f, progress, NO);
CGPathCloseSubpath(thePath);
_pies[i]=thePath;
}
}
- (void)updatePath:(CADisplayLink *)dLink {
for (int idx=0; idx<[spinnydelegates count]; idx++) {
id<SyncSpinUpdateDelegate> delegate = [spinnydelegates objectAtIndex:idx];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[delegate updatePath:dLink];
});
}
}
- (void)updatePath:(CADisplayLink *)dLink {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
currentarc=[engineref getsyncpercentForPad:cid pad:pid];
int progress;
progress = roundf(currentarc*360);
dispatch_async(dispatch_get_main_queue(), ^{
shapeLayer_.path = _pies[progress];
});
});
}
当尝试同时更新超过4或5个馅饼时,这项技术直接用于我。同时16个屏幕更新听起来应该真的不是我对ipad的大交易。所以这让我觉得我做了一件非常根本错误的事情。
我真的很感激,如果有人能告诉我为什么这种技术会导致抖动屏幕更新,并且如果他们可以提出一种不同的技术,我可以进行调查,这将允许我顺利地执行16个同时的shapelayer更新。
编辑只是为了让您了解性能有多糟糕,当我有16个馅饼时,绘制cpu的比例高达20%
* 编辑*
这是基于studevs的建议,但我没有看到任何被绘制的内容。 segmentLayer是一个CGLayerRef,作为我的pieview的属性。
-(void)makepies
{
self.layerobjects=[NSMutableArray arrayWithCapacity:360];
CGFloat progress=0;
CGContextRef context=UIGraphicsGetCurrentContext();
for(int i =0;i<360;i++)
{
progress= (i*M_PI)/180.0f;
CGLayerRef segmentlayer=CGLayerCreateWithContext(context, CGSizeMake(30, 30), NULL);
CGContextRef layerContext=CGLayerGetContext(segmentlayer);
CGMutablePathRef thePath = CGPathCreateMutable();
CGPathMoveToPoint(thePath, NULL, 0.f, 0.f);
CGPathAddLineToPoint(thePath, NULL, 28, 0.f);
CGPathAddArc(thePath, NULL, 0.f,0.f, 28, 0.f, progress, NO);
CGPathCloseSubpath(thePath);
[layerobjects addObject:(id)segmentlayer];
CGLayerRelease(segmentlayer);
}
}
-(void)updatePath
{
int progress;
currentarc=[engineref getsyncpercent];
progress = roundf(currentarc*360);
//shapeLayer_.path = _pies[progress];
self.pieView.segmentLayer=(CGLayerRef)[layerobjects objectAtIndex:progress];
[self.pieView setNeedsDisplay];
}
-(void)drawRect:(CGRect)rect
{
CGContextRef context=UIGraphicsGetCurrentContext();
CGContextDrawLayerInRect(context, self.bounds, segmentLayer);
}
答案 0 :(得分:5)
我认为您应该做的第一件事就是使用CGPath
对象在屏幕外缓冲您的细分(目前由CGLayer
个对象表示)。来自文档:
图层适用于以下内容:
您打算重复使用的高质量离线渲染图纸。 例如,您可能正在构建场景并计划重用该场景 背景。将背景场景绘制到图层然后绘制 层,无论何时需要它。一个额外的好处是你不需要 知道要绘制的颜色空间或设备相关信息 层
重复绘图。例如,您可能想要创建一个 由反复绘制的相同项目组成的模式。画出来 将项目添加到图层然后重复绘制图层,如图所示 12-1。您重复绘制的任何Quartz对象 - 包括CGPath, 如果,CGShading和CGPDFPage对象可以从改进的性能中受益 你把它绘制成CGLayer。请注意,图层不仅适用于屏幕 画画;你可以将它用于非图形上下文 面向屏幕的,例如PDF图形上下文。
创建一个绘制饼图的UIView
子类。为该饼图的当前进度提供一个实例变量,并覆盖drawRect:
以绘制表示该进度的图层。视图需要首先获得所需CGLayer
对象的引用,因此使用方法实现委托:
- (CGLayerRef)pieView:(PieView *)pieView segmentLayerForProgress:(NSInteger)progress context:(CGContextRef)context;
然后它将成为委托的工作,以返回现有的CGLayerRef
,或者如果它还不存在,则创建它。由于CGLayer
只能在drawRect:
内创建,因此应从PieView
的{{1}}方法调用此委托方法。 drawRect:
应该是这样的:
PieView
PieView.h
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
@class PieView;
@protocol PieViewDelegate <NSObject>
@required
- (CGLayerRef)pieView:(PieView *)pieView segmentLayerForProgress:(NSInteger)progress context:(CGContextRef)context;
@end
@interface PieView : UIView
@property(nonatomic, weak) id <PieViewDelegate> delegate;
@property(nonatomic) NSInteger progress;
@end
PieView.m
您的#import "PieView.h"
@implementation PieView
@synthesize delegate, progress;
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGLayerRef segmentLayer = [delegate pieView:self segmentLayerForProgress:self.progress context:context];
CGContextDrawLayerInRect(context, self.bounds, segmentLayer);
}
@end
代表(很可能是您的视图控制器)然后实现:
PieView
因此,对于每个饼图,创建一个新的NSString *const SegmentCacheKey = @"SegmentForProgress:";
- (CGLayerRef)pieView:(PieView *)pieView segmentLayerForProgress:(NSInteger)progress context:(CGContextRef)context
{
// First, try to retrieve the layer from the cache
NSString *cacheKey = [SegmentCacheKey stringByAppendingFormat:@"%d", progress];
CGLayerRef segmentLayer = (__bridge_retained CGLayerRef)[segmentsCache objectForKey:cacheKey];
if (!segmentLayer) { // If the layer hasn't been created yet
CGFloat progressAngle = (progress * M_PI) / 180.0f;
// Create the layer
segmentLayer = CGLayerCreateWithContext(context, layerSize, NULL);
CGContextRef layerContext = CGLayerGetContext(segmentLayer);
// Draw the segment
CGContextSetFillColorWithColor(layerContext, [[UIColor blueColor] CGColor]);
CGContextMoveToPoint(layerContext, layerSize.width / 2.0f, layerSize.height / 2.0f);
CGContextAddArc(layerContext, layerSize.width / 2.0f, layerSize.height / 2.0f, layerSize.width / 2.0f, 0.0f, progressAngle, NO);
CGContextClosePath(layerContext);
CGContextFillPath(layerContext);
// Cache the layer
[segmentsCache setObject:(__bridge_transfer id)segmentLayer forKey:cacheKey];
}
return segmentLayer;
}
并设置它的委托。当您需要更新饼图时,请更新PieView
的{{1}}媒体资源并致电PieView
。
我在这里使用progress
,因为存储了大量图形,并且可能占用大量内存。您还可以限制绘制的细分数量 - 100可能很多。此外,我同意其他评论/答案,您可能会尝试更少地更新视图,因为这会消耗更少的CPU和电池电量(可能不需要60fps)。
我在iPad(第一代)上对这种方法进行了一些粗略的测试,并设法以30fps的速度获得超过50个馅饼。
答案 1 :(得分:3)
dubbeat:... CADisplayLink ...
贾斯汀:你需要以显示器的刷新率绘制吗?
dubbeat:饼图绘制的进度应该代表mp3播放进度的进度,所以我猜测显示刷新率至少为。
这比必要的要快得多,除非你试图展示一些真正的,真正非常奇特的可视化器,如果你的微调器的半径是28pt,这是不太可能的。此外,没有理由比绘制频率更快地绘制。
一个副作用是你的微调器的超级视图也可能以这么高的频率更新。如果您可以使微调器视图不透明,那么您可以减少超级视图的过度绘制(如果有的话,还可以减少子视图)。
对于真正快速的桌面游戏来说,60fps是一个很好的数字。对于装饰/进度条,它远远超过必要。试试这个:
CADisplayLink
,而是使用标准视图系统NSTimer
,以8 Hz * 然后告诉我们这是否足够快。
*定时器回调调用[spinner setNeedsDisplay]
答案 2 :(得分:0)
嗯,通过预先组装背景视图,捕获图像,然后在图像视图中使用图像作为背景,可以实现性能提升。您可以通过捕获图表中“相对静态”部分的视图,仅在必要时更新该静态视图。
答案 3 :(得分:0)
将360个圆弧片段存储为纹理,并使用OpenGL为序列设置动画。