iOS SDK — CoreAnimation, программируем красивые кнопки

    Многие наверняка сталкивались с необходимостью быстрого создания контролов в мобильном пользовательском интерфейсе. Рассмотрим стандартные кнопки UIButton. Базовый контрол мало устраивает взыскательного заказчика и зачастую стандартное решение, это натягивание на кнопки битмапов состояния. Растянутая ли это картинка или целиком вырезаная кнопка — решение требует дополнительных временных затрат на дизайн пользовательского интерфейса. Хорошо было бы иметь универсальный контрол, с более широкими визуальными возможностями, чем базовый UIButton.


    Самым логичным представляется экстендить UIButton — так и сделаем
    создаем свой класс наследованный от UIButton (не забываем включить в проект QuartzCore.framework), он нам очень облегчит совместную жизнь с визулкой в iOS.

    @class CAGradientLayer;

    @interface CustomButton : UIButton {
    @private
    UIColor* _gradientStartColor;
    UIColor* _gradientEndColor;

    CAGradientLayer* _gradientLayer;
    }

    @property (nonatomic, retain) UIColor* gradientStartColor;
    @property (nonatomic, retain) UIColor* gradientEndColor;

    @end


    Как многие догадались, мы будем использовать CAGradientLayer для отрисовки градиентного фона кнопки. Этот класс предоставляет широкие возможности заливки. Мы рассмотрим лишь малую (базовую) их часть.

    #import "CustomButton.h"
    #import <QuartzCore/QuartzCore.h>

    @implementation CustomButton

    @synthesize gradientStartColor = _gradientStartColor;
    @synthesize gradientEndColor = _gradientEndColor;


    коль скоро мы планируем использовать наш контрол в InterfaceBuilder (далее IB), инициализация должна происходить в методе ниже

    -(void)awakeFromNib;
    {
    _gradientLayer = [[CAGradientLayer alloc] init];
    _gradientLayer.bounds = self.bounds;

    _gradientLayer.position = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2);

    [self.layer insertSublayer:_gradientLayer atIndex:0];

    self.layer.cornerRadius = 5.0f; // пусть будет хардкод, для теста
    self.layer.masksToBounds = YES;
    self.layer.borderWidth = 1.0f;

    }


    отрисовочка градиентного слоя

    - (void)drawRect:(CGRect)rect;
    {
    if (_gradientStartColor && _gradientEndColor)
    {
    [_gradientLayer setColors:
    [NSArray arrayWithObjects: (id)[_gradientStartColor CGColor]
    , (id)[_gradientEndColor CGColor], nil]];
    }

    [super drawRect:rect];
    }


    мы ничего не забыли?

    - (void)dealloc {
    [_gradientEndColor release];
    [_gradientStartColor release];
    [_gradientLayer release];
    [super dealloc];
    }

    @end


    вот наш маленький классец и заиплеменчен, давайте попробуем вставить его в интерфейс.

    Создаем в контроллере вида аутлет типа нашего класса (CustomButton)

    @interface GlossyButtonTestViewController : UIViewController {
    @private
    IBOutlet CustomButton* btn;
    }


    Открываем IB, всавляем кнопку с типом Custom и связываем ее с аутлетом в контроллере.
    В теле имплементации контроллера дописываем инициализауию значений цветов градиента фона кнопки.

    - (void)viewDidLoad {
    [super viewDidLoad];

    btn.gradientStartColor = [UIColor whiteColor];
    btn.gradientEndColor = [UIColor grayColor];
    [self.view addSubview:btn];
    }


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



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

    Сказано сделано — добавляем блик на кнопку.

    В интерфейс вставляем еще один слой и добавляем в awakeFromNib его инициализацию

    _glossyLayer = [[CAGradientLayer alloc] init];
    _glossyLayer.bounds = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height/2);
    _glossyLayer.position = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/4);
    [self.layer addSublayer:_glossyLayer];


    в drawRect дописываем инициализацию цветов градиента слоя


    [_glossyLayer setColors:
    [NSArray arrayWithObjects:
    (id)[[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.99f] CGColor]
    , (id)[[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.2f] CGColor], nil]];


    и… запускаем
    Вот и все.



    Не забываем, что слою CoreAnimation можно добавить транзакции переходов и т.п.
    Наш класс носит исключительно познавательный характер, для полноценного использования в проектах его необходимо расширить анимациями состояний.

    Автор поста Marshet15, но у него недостаточно кармы для публикации статьи.
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 35
    • +5
      кнопка не нажимается. ;)
      • 0
        «Наш класс носит исключительно познавательный характер, для полноценного использования в проектах его необходимо расширить анимациями состояний.»
        • +5
          Если это кнопка, то она должна нажиматься.
          Конечно есть разница между кнопкой, поддерживающей состояния pressed, disable, hover, focus,… и картинкой.

          Статья несколько провокационная.
          С одной стороны она показывает как быстро сделать 80% работы по кастомизации кнопки, с другой не показывает сколько еще работы придется сделать, чтобы поучить остальные 20%
          • +1
            Работы на самом деле не много на те 20%, легко инвертировать градиент задав его вектора в противоположную сторону. 10 строк кода дописать — максимум.
        • +1
          Вы прям хотите magic class в howto-статье. ;)
        • 0
          А чем плох растянутый битмап?
          • 0
            Всем хорош, кроме того6 что его надо вычитывать из бандла и растягивать. Это существенно дольше, ну и дополнительный мусор в бандле
            • 0
              Ну чтобы сделать правильную iPhone-style кнопку, боюсь drawRect разрастется очень сильно, что нивелирует весь выигрыш по сравнению с вычитыванием из бандла и растягиванием (которое вообще стоит копейки). Если на чем и сэкономите, то на самом битмапе — не надо будет его в памяти держать.
              • 0
                Есть еще очевидный плюс — это переносимость кода. Таскать битмапы из проекта в проект не сильно удобно. Хотя, как кому нравится.
                • 0
                  Переносимость между чем и чем?
                  • +1
                    Между iPhone 3G и iPhone 4, например. Там, где можно обойтись одним градиентом, битмапа придется делать два.
                  • 0
                    Да ну что вы… Битмап, по крайней мере, 100500% сработает как надо ;)
            • +2
              [NSArray arrayWithObjects:
              (id)[[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.99f] CGColor]
              , (id)[[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.2f] CGColor]
              , nil]

              так точно можно делать? В смысле преобразовывать CGColor к id.
              • –1
                Да.
                • –1
                  Вероятно вы не поняли контекста для чего это приведение.
                  • +1
                    Объясните человеку тогда, что именно из контекста он не понял и расскажите почему такое приведение работает :)
                    А вообще надо заворачивать такие вещи в NSValue. В документации к property colors написано, что принимает объект типа NSArray, состоящий из CGColorRef, поэтому в данном случае это корректно. Приведение к id нужно исключительно для скрытия ошибок компиляции :) Как устроено оно внутри, я могу только догадываться.
              • +1
                Хорошая статья. Но всеравно по-большей части прихоится работать с PNG-кнопочками.
                • +1
                  Для данного стиля кнопок в iOS не используется эффект блика.
                  В добавок можно сказать что текст на аналогичных кнопках не синий а темно-серый.
                  • –3
                    ужас какой О_О как я рад что использую Qt.
                    • –2
                      аналогично, AS3
                      • +1
                        В Qt все точно так же. Разве что dealloc не надо писать.
                        • –1
                          Можно не писать dealloc, если если в декларации свойств писать @property (retain, autorelease).
                          Это «by design», так сказать.
                          • 0
                            тогда тем более :)
                            • +1
                              Нет атрибута autorelease у свойств в ObjC.
                              • 0
                                Сорри, заработался и обманул; действительно так. Можно конечно сделать [[[obj alloc] init] autorelease], но тогда аллоченый объект будет висеть до [pool drain];
                                На самом деле лучше все делать в -(void)dealloc;
                          • +1
                            Работал лет эдак 3 на Qt.
                            Сейчас вот уже 2-й год как под ифоны пишу.
                            И хотя подходы к построению интерфейса очень сильно отличаются — мне и там и там удобно.
                            Самое главное не лезть в чужой фреймворк со своим апи :)
                          • +3
                            Как вариант, чтобы не делать drawRect каждый раз, можно один раз сгенерировать картинку и использовать его как фон кнопки. А при вынесении этого в категорию даже и наследовать ничего не надо.
                            Объяснение, примеры и код можно найти тут:
                            www.mlsite.net/blog/?page_id=372
                            • 0
                              Хорошее решение.
                              • 0
                                А еще лучше заставить дизайнера сделать картинку вида:
                                (левая сторона) — (1-2 пикселя серединки) — (правая сторона)

                                А потом установить фон кнопки вот так:
                                UIImage *strechableOne = [img stretchableImageWithLeftCapWidth:5 topCapHeight:0];
                                [btn setBackgroundImage:strechableOne forState:...]

                                Сам текст пишем текстом. В итоге — при локализации меньше гемора (просто строками текст переводим). И кнопку можно делать разной ширины. (если точнее — то не уже исходной картинки)

                                Естественно всякие хитрые шрифты и эффекты таким способом рисуются очень сложно. Но для большинства задач — отличное решение.
                              • 0
                                Объясните мне кто-нибудь, зачем этот «блекс» на кнопках?
                                • 0
                                  Данный эффект использовался Эпл в интерфейсе Аква. Он должен показывать блик который оставляют софт-боксы на глянцевых поверхностях.
                                • 0
                                  Советую посмотреть в сторону Three20 (http://three20.info/) чтоб не строить велосипеды.

                                  В ней есть универсальная реализация кнопок в том числе и вообще очень гибкий механизм стилей – TTStyle.
                                  • 0
                                    Иногда велосипеды полезно поизобретать, в целях образования.
                                    • 0
                                      Библиотека прикольная и автору явно зачет. Правда что бы в ней разобраться пришлось убить довольно много времени
                                    • 0
                                      следующие уроки мне показались будут интересны читателю, хоть и на английском. Они содержат и готовые проекты во всех случаях, и рассказ, как работать с «нажатием».

                                      www.cimgf.com/2010/01/28/fun-with-uibuttons-and-core-animation-layers/
                                      www.icodejunkie.com/?p=74
                                      undefinedvalue.com/2010/02/27/shiny-iphone-buttons-without-photoshop

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