Наименее ресурсоемкий способ часто и многократно рисовать много просмотров - PullRequest
2 голосов
/ 07 ноября 2011

Это проблема, к которой я уже давно ухожу и возвращаюсь. Я никогда не решал эту проблему.

То, что я пытался сделать, использует CADisplayLink для динамического рисования прогресса стиля круговой диаграммы. Мой код работает нормально, когда у меня одновременно обновляется 1 - 4 интерфейса. Когда я добавляю что-то еще, рисунок пирогов становится очень резким.

Я хочу объяснить, что я пытался, в надежде, что кто-нибудь укажет на недостатки и предложит лучший метод рисования.

Я создаю 16 uiviews и добавляю подпредставление CAShapeLayer к каждому. Вот где я хочу нарисовать свои кусочки пирога.

Я предварительно вычислил 360 CGPath, представляющих круг от 0 до 360 градусов, и сохранил их в массиве, чтобы попытаться улучшить производительность.

В главном представлении я запускаю ссылку на дисплей, перебираю все мои другие представления, вычисляю, сколько должен отображаться полный круг, затем нахожу правильный путь и назначаю его моему слою фигур.

-(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 одновременных обновлений шейплайера.

РЕДАКТИРОВАТЬ Просто, чтобы дать вам представление о том, насколько плоха производительность, когда у меня все 16 пирогов на чертеже, процессор увеличивается до 20%

* РЕДАКТИРОВАТЬ *

Это основано на совете студентов, но я не вижу ничего нарисованного. сегментLayer является CGLayerRef как свойство моего кругового обзора.

-(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);

}

Ответы [ 4 ]

5 голосов
/ 11 ноября 2011

Я думаю, что первое, что вы должны сделать, - это буферизовать ваши сегменты (в настоящее время представленные 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 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 и установите его делегат.Когда вам нужно обновить круговую диаграмму, обновите свойство PieView progress и вызовите setNeedsDisplay.

Я использую здесь NSCache, так как хранится много графики,и это может занять много памяти.Вы также можете ограничить количество нарисованных сегментов - 100, вероятно, достаточно.Кроме того, я согласен с другими комментариями / ответами о том, что вы можете пытаться обновлять представления реже, так как это потребляет меньше ресурсов процессора и батареи (вероятно, нет необходимости в 60 кадр / с).



Я провел грубое тестирование этого метода на iPad (1-го поколения), и мне удалось получить более 50 обновлений пирога со скоростью 30 кадров в секунду.

3 голосов
/ 11 ноября 2011

дублирование: ... CADisplayLink ...

Джастин: вам нужно рисовать с частотой обновления дисплея?

dubbeat: Ход кругового рисунка должен отражать ход воспроизведения в формате mp3, поэтому я предполагаю, что частота обновления экрана минимальна.

Это намного быстрее, чем необходимо, если только вы не пытаетесь отобразить действительно очень-очень экзотический визуализатор, что очень маловероятно, если радиус вашего счетчика составляет 28pt. Кроме того, нет причин рисовать быстрее, чем частота дисплея.

Одним из побочных эффектов является то, что супервидения вашего счетчика могут также обновляться с этой высокой частотой. Если вы можете сделать просмотр блесна непрозрачным, то вы можете уменьшить перерасход суперпредставлений (и подпредставлений, если они у вас есть).

60fps - хорошее число для действительно быстрой настольной игры. Для орнамента / индикатора выполнения это гораздо больше, чем необходимо.

Попробуйте это:

  • не использует CADisplayLink, но стандартная система просмотра
  • используйте NSTimer в главном цикле запуска, начинайте с частоты 8 Гц *
  • настроить таймер по вкусу

, тогда дайте нам знать, если это достаточно быстро.

* таймер обратного вызова [spinner setNeedsDisplay]

0 голосов
/ 11 ноября 2011

Сохраните ваши 360 круговых сегментов как текстуры и используйте OpenGL для анимации последовательностей.

0 голосов
/ 11 ноября 2011

Что ж, вы могли бы добиться некоторого улучшения производительности, предварительно собрав фоновый вид, захватив его изображение, а затем просто используя изображение в виде изображения для фона. Вы можете пойти дальше, захватив представление «относительно статических» частей диаграммы, обновляя это статическое представление только при необходимости.

...