как сделать вид прогресса, как в приложении KAYAK

Интересно, как сделать просмотр прогресса, похожий на тот, что в приложении KAYAK (это приложение для путешествий для поиска рейсов и отелей), снимок экрана:

введите описание изображения здесь

Я покопался в ресурсах приложения KAYAK на взломанном iPhone и нашел следующие 3 изображения, которые составляют это представление прогресса:

[email protected]

введите описание изображения здесь

[email protected]

введите описание изображения здесь

[email protected]

введите описание изображения здесь

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

любые идеи или примеры кода будут высоко оценены.


person JAHelia    schedule 19.09.2013    source источник
comment
Посмотрите - (UIImage *) resizableImageWithCapInsets: (UIEdgeInsets) capInsets. Это с любым из предложений github должно быть работоспособным.   -  person EricLeaf    schedule 27.09.2013


Ответы (8)


Я сделал весь рабочий пакет, который имитирует опубликованное вами представление прогресса. Однако, чтобы сделать его более настраиваемым, я не использовал изображения, но использовал CoreGraphics для их рисования. Пакет можно найти по адресу lightdesign / LDProgressView. Я также, вероятно, сделаю из него CocoaPod, если вы знаете, что это такое.

Как нарисовать прогресс в стиле KAYAK

Всю внутреннюю работу представления прогресса и, следовательно, то, как имитировать представление прогресса KAYAK, можно найти в этот файл. Я добавил несколько комментариев сюда, в блоки кода, для облегчения понимания. Вот метод drawRect:

- (void)drawRect:(CGRect)rect {
    [self setAnimateIfNotSet];
    CGContextRef context = UIGraphicsGetCurrentContext();
    [self drawProgressBackground:context inRect:rect];
    if (self.progress > 0) {
        [self drawProgress:context withFrame:rect];
    }
}

Это довольно понятно. Я устанавливаю свойство animate, если оно еще не установлено, и рисую фон. Затем, если прогресс больше 0, я нарисую прогресс в пределах всего кадра. Перейдем к drawProgressBackground:inRect: методу:

- (void)drawProgressBackground:(CGContextRef)context inRect:(CGRect)rect {
    CGContextSaveGState(context);

    // Draw the background with a gray color within a rounded rectangle
    UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:10];
    CGContextSetFillColorWithColor(context, [UIColor colorWithRed:0.51f green:0.51f blue:0.51f alpha:1.00f].CGColor);
    [roundedRect fill];

    // Create the inner shadow path
    UIBezierPath *roundedRectangleNegativePath = [UIBezierPath bezierPathWithRect:CGRectMake(-10, -10, rect.size.width+10, rect.size.height+10)];
    [roundedRectangleNegativePath appendPath:roundedRect];
    roundedRectangleNegativePath.usesEvenOddFillRule = YES;
    CGSize shadowOffset = CGSizeMake(0.5, 1);
    CGContextSaveGState(context);
    CGFloat xOffset = shadowOffset.width + round(rect.size.width);
    CGFloat yOffset = shadowOffset.height;
    CGContextSetShadowWithColor(context,
            CGSizeMake(xOffset + copysign(0.1, xOffset), yOffset + copysign(0.1, yOffset)), 5, [[UIColor blackColor] colorWithAlphaComponent:0.7].CGColor);

    // Draw the inner shadow
    [roundedRect addClip];
    CGAffineTransform transform = CGAffineTransformMakeTranslation(-round(rect.size.width), 0);
    [roundedRectangleNegativePath applyTransform:transform];
    [[UIColor grayColor] setFill];
    [roundedRectangleNegativePath fill];

    CGContextRestoreGState(context);
}

Здесь я создаю прямоугольник с закругленными углами внутри вида с радиусом 10 (который позже я могу разрешить настраивать) и заполняю его. Затем остальная часть кода рисует внутреннюю тень, о которой мне действительно не нужно вдаваться в подробности. Теперь вот код для рисования прогресса в методе drawProgress:withFrame::

- (void)drawProgress:(CGContextRef)context withFrame:(CGRect)frame {
    CGRect rectToDrawIn = CGRectMake(0, 0, frame.size.width * self.progress, frame.size.height);
    CGRect insetRect = CGRectInset(rectToDrawIn, 0.5, 0.5);

    UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:insetRect cornerRadius:10];
    if ([self.flat boolValue]) {
        CGContextSetFillColorWithColor(context, self.color.CGColor);
        [roundedRect fill];
    } else {
        CGContextSaveGState(context);
        [roundedRect addClip];
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        CGFloat locations[] = {0.0, 1.0};
        NSArray *colors = @[(__bridge id)[self.color lighterColor].CGColor, (__bridge id)[self.color darkerColor].CGColor];
        CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef) colors, locations);

        CGContextDrawLinearGradient(context, gradient, CGPointMake(insetRect.size.width / 2, 0), CGPointMake(insetRect.size.width / 2, insetRect.size.height), 0);
        CGContextRestoreGState(context);

        CGGradientRelease(gradient);
        CGColorSpaceRelease(colorSpace);
    }

    CGContextSetStrokeColorWithColor(context, [[self.color darkerColor] darkerColor].CGColor);
    [self drawStripes:context inRect:insetRect];
    [roundedRect stroke];

    [self drawRightAlignedLabelInRect:insetRect];
}

Этот метод состоит из 4 основных частей. Сначала я вычисляю кадр, который займет прогресс, на основе свойства self.progress. Во-вторых, я рисую либо сплошным цветом, если установлено свойство flat, либо рисую рассчитанный градиент (методы lighterColor и darkerColor относятся к категории UIColor). В-третьих, я рисую полосы и, наконец, рисую метку процента. Давайте быстро рассмотрим эти 2 метода. Вот метод drawStripes:inRect::

- (void)drawStripes:(CGContextRef)context inRect:(CGRect)rect {
    CGContextSaveGState(context);
    [[UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:10] addClip];
    CGContextSetFillColorWithColor(context, [[UIColor whiteColor] colorWithAlphaComponent:0.2].CGColor);
    CGFloat xStart = self.offset, height = rect.size.height, width = STRIPE_WIDTH;
    while (xStart < rect.size.width) {
        CGContextSaveGState(context);
        CGContextMoveToPoint(context, xStart, height);
        CGContextAddLineToPoint(context, xStart + width * 0.25, 0);
        CGContextAddLineToPoint(context, xStart + width * 0.75, 0);
        CGContextAddLineToPoint(context, xStart + width * 0.50, height);
        CGContextClosePath(context);
        CGContextFillPath(context);
        CGContextRestoreGState(context);
        xStart += width;
    }
    CGContextRestoreGState(context);
}

Вот где происходит «волшебство» анимации. По сути, я рисую эти полосы на основе self.offset, который находится где-то между -STRIPE_WIDTH и 0, увеличиваемый таймером. Затем я создаю простой цикл, так что я создаю достаточно полос, чтобы полностью заполнить часть прогресса представления. Я также оставляю 25% STRIPE_WIDTH пустыми, чтобы полосы не слипались друг с другом. Вот последний метод рисования drawRightAlignedLabelInRect::

- (void)drawRightAlignedLabelInRect:(CGRect)rect {
    UILabel *label = [[UILabel alloc] initWithFrame:rect];
    label.backgroundColor = [UIColor clearColor];
    label.textAlignment = NSTextAlignmentRight;
    label.text = [NSString stringWithFormat:@"%.0f%%", self.progress*100];
    label.font = [UIFont boldSystemFontOfSize:17];
    UIColor *baseLabelColor = [self.color isLighterColor] ? [UIColor blackColor] : [UIColor whiteColor];
    label.textColor = [baseLabelColor colorWithAlphaComponent:0.6];
    [label drawTextInRect:CGRectOffset(rect, -6, 0)];
}

В этом методе я создаю метку с текстом, который преобразуется из числа с плавающей запятой (от 0.0 до 1.0) в процентное значение (от 0% до 100%). Затем я либо устанавливаю темный, либо светлый цвет в зависимости от темноты выбранного цвета прогресса и рисую метку в CGContext.

Возможность настройки

Есть три свойства, которые можно установить либо непосредственно в экземпляре LDProgressView, либо заранее в UIAppearance методе.

  • Цвет

Цвет, очевидно, будет определять общий вид сборщика. На этом основании определяются градиенты, полосы и / или цвета контура. Метод UIAppearance будет примерно таким:

 [[LDProgressView appearance] setColor:[UIColor colorWithRed:0.87f green:0.55f blue:0.09f alpha:1.00f]];
  • Плоский

Это определит, будет ли фон окна прогресса градиентом или просто свойством color. Этот UIAppearance метод будет выглядеть примерно так:

[[LDProgressView appearance] setFlat:@NO];
  • Анимировать

Наконец, это определит, будут ли полосы анимированы. Этот UIAppearance метод также может быть в общем установлен для всех экземпляров LDProgressView и выглядит следующим образом:

[[LDProgressView appearance] setAnimate:@YES];

Вывод

Ух! Это был длинный ответ. Надеюсь, я не слишком утомлял вас, ребята. Если вы просто пропустили сюда, вот суть рисования в коде, а не с изображениями. Я думаю, что CoreGraphics - лучший способ рисования на iOS, если у вас есть время / опыт, поскольку он позволяет больше настраивать и, как мне кажется, имеет тенденцию быть быстрее.

Вот изображение окончательного рабочего продукта:

LDProgressView

person Christian Di Lorenzo    schedule 28.09.2013
comment
Я загрузил ваш проект с Github, но анимация вообще не работает, я попытался подключить представление прогресса с помощью IBOutlet, а затем запустить анимацию с помощью setAnimate, но безрезультатно ... в коде чего-то не хватает правильно работать? - person JAHelia; 29.09.2013
comment
Я обновил его ... Я специально протестирую IBOutlet, чтобы увидеть, есть ли у меня проблемы. Обновление: я протестировал последнюю версию с помощью IBOutlet, и она работает нормально. Попробуйте и посмотрите, не работает ли он по-прежнему. - person Christian Di Lorenzo; 30.09.2013
comment
Большое спасибо, я работал сейчас, но в консоли появляется повторяющаяся ошибка: ‹Error›: clip: empty path, как я могу удалить эту досадную ошибку? - person JAHelia; 30.09.2013
comment
Хорошо, я исправил ошибку пути. В моем коде случайно была добавлена ​​CGContextClip функция. - person Christian Di Lorenzo; 30.09.2013
comment
Это может быть глупый вопрос, но как я могу использовать это в построителе интерфейса? Я поддерживаю iPad, все размеры экрана iPhone, а также вращение ... и мне было интересно, могу ли я заменить UIProgressBar, который я сейчас использую в IB, на это ...? - person edhnb; 21.03.2014
comment
Вы захотите перетащить UIView на свой контроллер, а затем изменить его класс на LDProgressView. Наконец, я бы добавил IBOutlet в ваш код и настроил там свойства. Надеюсь это поможет! - person Christian Di Lorenzo; 21.03.2014

Вы можете использовать NSTimer для обновления «смещения», с которого вы начинаете рендеринг наложения. Вы можете найти пример здесь. Это элемент управления OS X, и он использует пользовательский рисунок, а не изображения, но принцип тот же.

person Tom    schedule 20.09.2013
comment
у вас есть iOS-совместимая версия этого проекта? - person JAHelia; 22.09.2013
comment
К сожалению, нет, но взгляните на Элементы управления какао. Я уверен, что вы можете найти там нечто подобное. - person Tom; 22.09.2013
comment
Просто нашел это на случай, если вы все еще ищете: github.com/lightdesign/LDProgressView - person Tom; 09.10.2013

Отрегулируйте цвет и толщину, и все готово! https://www.cocoacontrols.com/controls/ylprogressbar

Этот тоже вроде неплохой ... но я им еще не пользовался. https://github.com/JonasGessner/JGProgressView

Удачи!

person Unikorn    schedule 26.09.2013

Вот несколько примеров, в которых вы можете просто изменить часть кода и получить представление о ходе выполнения по своему усмотрению: проверьте Example1 и Example2

person Nirmalsinh    schedule 27.09.2013

Ближайшей связанной полосой выполнения в вашем случае является ADVProgressBar.

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


Снимок экрана:

введите описание изображения здесь

person Bhavin    schedule 28.09.2013

Вы можете использовать этот настраиваемый элемент управления какао

ADV control

person user1673099    schedule 19.09.2013
comment
Это полезно для вас ?? - person user1673099; 19.09.2013
comment
это не удовлетворяет требованию в вопросе - person JAHelia; 19.09.2013
comment
Почему это не соответствует вашим требованиям? - person user1673099; 19.09.2013
comment
Мне нужно динамическое отображение прогресса с наложенными фреймами, загрузите приложение Kayak и опробуйте его. - person JAHelia; 19.09.2013

Попробуйте Cocoa Controls для ProgressView ....... что касается наложенных изображений, вам нужно использовать NSTimer, который передает абсолютную синхронизацию с прогрессом

person Debanjan Chakraborty    schedule 25.09.2013

Когда вы настраиваете свой UIProgressView (self.progress), мы настраиваем следующие параметры, чтобы получить тот же вид:

        UIImage *mynd =[[UIImage imageNamed:@"KgcoJ.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(11, 13, 11, 13)];
        UIImage *bakMynd =[[UIImage imageNamed:@"UoS0A.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(11, 13, 11, 13)];
        [self.progress setProgressImage:mynd];
        [self.progress setTrackImage:bakMynd];

Обратите внимание, что вам может потребоваться изменить имена изображений и числа в UIEdgeInsetsMake в зависимости от размеров вашего изображения. Это даст вам следующее: Индикатор выполнения с изображениями

person Sverrisson    schedule 28.09.2013