Кварцевый метод 2D drawRect (iPhone)
Передо мной 4 разные книги по iPhone/Cocoa/Core Animation/Objective-C, а также многочисленные примеры кода из Интернета. Тем не менее, почему-то мне все еще не хватает фундаментального понимания того, как рисование работает в Quartz 2D.
Является drawRect()
означает просто быть крючком для выполнения вашего кода рисования? Или этот метод должен также перерисовать регионы, которые "повреждены" и нуждаются в перерисовке? Могу ли я просто нарисовать свои вещи один раз, а потом они "прилипнут", или я должен перекрасить всю сцену в любое время через drawRect()
? Java-объект Graphics2D работает таким образом - вы должны рисовать все свое "изображение" каждый раз, когда вызывается paint(), поэтому вы должны быть готовы к его пересозданию в любое время (или кешированию).
Как бы вы реализовали простую программу для рисования? Придется ли вам "запоминать" каждую линию / точку / обводку, которую нарисовал пользователь, и повторять ее каждый раз drawRect()
называется? Как насчет рендеринга за кадром? Вы можете сделать все свои рисунки, а затем позвонить [self setNeedsDisplay]
чтобы ваши записи всплывали на экране?
Допустим, в ответ на прикосновение пользователя я хочу поставить "Х" на экране, где он коснулся. X должен оставаться там, и каждое новое прикосновение производит еще один X. Нужно ли мне помнить все эти координаты касания и затем рисовать их все в drawRect()
?
РЕДАКТИРОВАТЬ:
Если я не правильно понял, ответы Джоконора и Гектора Рамоса, приведенные ниже, противоречат друг другу. И это хорошая демонстрация моего замешательства по этому вопросу.:-)
4 ответа
Некоторая путаница между различными ссылками на Какао происходит из-за введения в Leopard представлений со слоями. На iPhone все UIViews имеют слоистую поддержку, тогда как в представлениях Leopard необходимо вручную включить поддержку слоев.
Для слоистого представления контент рисуется один раз, используя то, что вы указали в drawRect()
, но затем буферизируется в слой. Слой действует как прямоугольная текстура, поэтому, когда вы перемещаете представление со слоем или закрываете его, перерисовка не требуется, текстура просто перемещается в это место через графический процессор. Если вы не установите needsDisplayOnBoundsChange property
в YES
для слоя изменение размера слоя (или его содержимого) просто масштабирует содержимое. Это может привести к размытости графики в вашем представлении или слое, поэтому в этом случае вы можете вызвать перерисовку. setNeedsDisplay
вызовет ручную перерисовку содержимого представления или слоя и последующее повторное получение этого содержимого в слое.
Для обеспечения оптимальной производительности рекомендуется избегать частых звонков drawRect
, потому что рисование и повторение кварца в слое являются дорогостоящими операциями. Лучше всего попробовать сделать анимацию, используя отдельные слои, которые вы можете перемещать или масштабировать.
Ссылки на основе какао, которые вы видели и относящиеся к рабочему столу, могут предполагать представления без слоя, которые вызывают drawRect: в любое время, когда необходимо обновить представление, будь то движение, масштабирование или наличие части представления затемняется. Как я уже сказал, все UIViews являются слоистыми, так что это не так на iPhone.
Тем не менее, для вашего приложения для рисования один из способов сделать это - сохранить массив нарисованных объектов и вызвать drawRect: каждый раз, когда пользователь добавляет что-то новое, перебирает каждый из ранее нарисованных объектов по порядку. Я мог бы предложить альтернативный подход, при котором вы создаете новый UIView или CALayer для каждой операции рисования. Содержимое этой операции рисования (линия, дуга, X и т. Д.) Будет нарисовано отдельным видом или слоем. Таким образом, вам не придется перерисовывать все при новом прикосновении, и вы могли бы сделать некоторое аккуратное редактирование в векторном стиле, перемещая каждый из нарисованных элементов независимо друг от друга. Для сложных рисунков в этом может быть некоторая компромиссность памяти, но я бы поспорил, что она будет иметь гораздо лучшую производительность рисования (минимальное использование процессора и мерцание).
drawRect() будет рисовать в буфер за пределами экрана. Вам не нужно перерисовывать каждый раз, когда регионы "повреждены", так как iPhone OS заботится о наложении слоев. Вы просто записываете один раз в буфер и позволяете ОС обрабатывать все остальное. Это не так, как в других средах программирования, где вам нужно продолжать перерисовывать всякий раз, когда что-то проходит над вашим представлением.
Является ли drawRect() просто крючком для выполнения вашего кода рисования?
Он предназначен для перерисовки области (прямоугольника), которая передается вам, используя текущий стек графического контекста. Больше не надо.
Или этот метод должен также перерисовать регионы, которые "повреждены" и нуждаются в перерисовке?
Нет. Если грязные области не перекрываются, вы можете получить несколько вызовов drawRect:
с разными ректами, переданными вам. Rects признаны недействительными, используя setNeedsDisplayInRect:
, Если необходимо перерисовать только часть поверхности вашего вида, вам будет предложено нарисовать эту часть, когда придет время рисовать.
Могу ли я просто нарисовать свой материал один раз, а потом он "залипнет", или я должен перекрасить всю сцену в любое время с помощью drawRect()?
Он не "прилипает". Rects становятся недействительными во время выполнения вашего приложения, и вам предлагается перерисовать эти reect, когда системе представления необходимо обновить экран. Вы перекрашиваете только тот прямоугольник, который запрашивается.
В некоторых случаях простая реализация может (довольно лениво) аннулировать весь прямоугольник представления всякий раз, когда часть становится недействительной. Это, как правило, плохо, потому что обычно требует больше рисунка, чем необходимо, и особенно расточительно, когда виды не непрозрачны.
Java-объект Graphics2D работает таким образом - вы должны рисовать все свое "изображение" каждый раз, когда вызывается paint(), поэтому вы должны быть готовы к его повторному построению в любое время (или кешированию).
Не так с AppKit или UIKit.
Как бы вы реализовали простую программу для рисования? Нужно ли вам "помнить" каждую линию / точку / обводку, которую нарисовал пользователь, и повторять это каждый раз, когда вызывается drawRect()?
Вам нужно будет помнить контекст (например, каждую линию / точку / обводку), необходимый для рисования вашего вида. Вам нужно только нарисовать регион, который запрашивается. Технически графическая система не будет жаловаться, если вы будете рисовать вне этого прямоугольника, но это может привести к артефактам.
Для сложного рендеринга может быть проще или более эффективно отрисовать внешний буфер (например, растровое изображение), а затем использовать это предварительно представленное растровое представление, чтобы получить информацию на экране, находясь в drawrect:
, (см. также ответ Брэда для слоев)
Как насчет рендеринга за кадром? Можете ли вы сделать все свои рисунки, а затем вызвать [self setNeedsDisplay], чтобы ваши записи были сброшены на экран?
Да, вы можете сделать это. В частности, вы должны выполнить рендеринг во внешний буфер (например, растровое изображение), когда вы закончите рендеринг в растровое изображение, сделаете недействительным прямоугольник, который вы хотите нарисовать, а затем начнете рисовать на экране, используя данные в растровом изображении, когда drawRect:
называется.
Допустим, в ответ на прикосновение пользователя я хочу поставить "Х" на экране, где он коснулся. X должен оставаться там, и каждое новое касание производит еще один X. Нужно ли мне помнить все эти координаты касания и затем рисовать их все в drawRect()?
Ну, у вас есть несколько вариантов. Ваше предложение одно (при условии, что вы рисуете в переданном вам прямоугольнике). Другой способ - создать представление "X" и просто запомнить точки, необходимые для восстановления представления, если вам нужно, чтобы эти X сохранялись при запусках. Во многих случаях вы можете легко разделить сложные задачи на слои (простая 2D игра):
- 1) Фоновое изображение с горизонтом.
- 2) Некоторые вещи на переднем плане, которые не часто меняются.
- 3) Персонаж, которого игрок использует для навигации по игре.
Таким образом, большинство проблем можно легко разделить, поэтому вам не нужно все время рендерить. Это уменьшает сложность и повышает производительность, если все сделано хорошо. Если сделано плохо, это может быть несколько намного хуже.
Всегда будьте готовы нарисовать соответствующую область вашего зрения, когда drawRect:
называется.
Хотя система может буферизовать ваше представление, это только позволит избежать drawRect:
от того, чтобы быть призванным. Если по какой-либо причине система должна аннулировать буфер, ваш drawRect:
метод может быть вызван снова. Также, drawRect:
будет вызываться для разных областей вашего вида, поскольку они становятся видимыми в результате прокрутки и других операций, которые влияют на видимость областей вашего вида.