Рисование 2D графики в окне программы Печать
Добавил(а) microsin   

Android framework API предоставляет набор классов для плоского рисования на экране приложения, которые позволяют Вам нарисовать любую графику и текст, или модифицировать имеющиеся представления View согласно Вашим пожеланиям и чувствам. Когда рисуете 2D-графику, обычно это делают двумя способами:

a. Рисование графики или анимации в пределах объекта View, находящегося на разметке окна программы (layout). В этом случае рисование графики обрабатывается системой по обычной иерархии процесса рисования View — Вы просто задаете нужные графические представления внутри View.
b. Рисование графики напрямую на экране программы как на холсте (на Canvas). В этом случае Вы персонально вызываете подходящий метод класса onDraw() (передавая ему Ваш Canvas), или одним из методов Canvas, начинающимся на draw...() (наподобие drawPicture()). При этом Вы также контролируете любую анимацию.

Опция "a", рисование в View, является лучшим выбором, когда Вы хотите нарисовать простые графические объекты, которые не нужно динамически изменять, и которые не являются частью требовательной к быстроте прорисовки игры. Например, Вы должны рисовать Вашу графику в View, когда хотите отобразить статическую графику или заранее предопределенную анимацию в некотором статическом приложении. Для получения подробностей см. ниже раздел "Рисуемые объекты (Drawables)".

Опция "b", рисование на Canvas, лучше подходит, когда Ваше приложение требует регулярной собственной перерисовки. Приложения наподобие видеоигр должны самостоятельно рисовать на Canvas. Однако есть несколько способов такого рисования:

• В том же самом потоке, который обрабатывает UI Activity приложения, где Вы создаете пользовательское представление View в Вашей разметке окна (layout), вызываете invalidate() и затем обрабатываете обратный вызов onDraw().
• Или в отдельном потоке, где Вы обрабатываете SurfaceView и выполняете рисование на Canvas так быстро, как позволяет Ваш поток (в этом случае не нужно запрашивать invalidate()).

[Рисование с помощью Canvas]

Когда Вы пишете приложение, которое должно выполнять отрисовку некоторой специализированной графики и/или управлять некоторыми сложными графическими анимациями (игра), это следует делать с помощью Canvas. Canvas работает для Вас как специальный интерфейс, символизирующий реальную поверхность, на которой вы будете рисовать — он содержит в себе все нужные методы для рисования (их названия начинаются на "draw"). С помощью Canvas Ваше рисование в действительности происходит на нижележащую растровую картинку (Bitmap), которая размещена на экране программы.

В событии (event), где Вы рисуете что-то в теле метода обратного вызова onDraw(), Canvas предоставляется Вам для рисования, Вам только нужно разместить картинку на нем вызовами подпрограмм для рисования. Вы также можете получить Canvas из SurfaceHolder.lockCanvas(), когда работаете с объектом SurfaceView (оба этих сценария будут рассмотрены далее). Однако, если Вам нужно создать новый Canvas, то Вы должны определить поверхность Bitmap, на которой реально будет происходить рисование. Bitmap всегда требуется для Canvas. Вы можете установить новый Canvas примерно так:

Bitmap b = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);

Теперь Ваш Canvas будет рисовать на заданной картинке Bitmap. После рисования вместе с Canvas Вы можете перенести Вашу картинку Bitmap на другой Canvas одним из методов Canvas.drawBitmap(Bitmap,...). Это рекомендуется, если Вы рисуете конечную графику через Canvas, предоставленный Вам в View.onDraw() или SurfaceHolder.lockCanvas() (см. это в последующих секциях).

Класс Canvas имеет свои собственные методы рисования, которые Вы можете использовать, наподобие drawBitmap(...), drawRect(...), drawText(...) и многие другие. Другие классы, которые Вы можете использовать, также имеют методы draw(). Например, у Вас возможно будут некоторые Drawable-объекты, который захотите поместить на Canvas. Drawable имеет свой собственный метод draw(), который получает Ваш Canvas как аргумент.

[Рисование на View]

Если Ваша программа не требует большой частоты перерисовки кадров (к примеру это игра в шахматы, змейка, или другая программа, которая имеет только медленные анимации), то Вы можете рассмотреть вариант с созданием компонента пользовательского вида (custom View), с рисованием на Canvas в методе View.onDraw(). Самый удобный вариант задействовать предоставляемый фреймворком Android предопределенный Canvas, в котором Вы разместите свои вызовы методов для рисования.

Чтобы начать, расширьте класс View (или произведите от него дочерний) и определите метод обратного вызова onDraw(). Этот метод будет вызван фреймворком Android framework для запроса перерисовки View самого себя. В этом месте будут все Ваши вызовы рисования на Canvas, который будет передан через обратный вызов onDraw().

Фреймворк Android будет вызывать onDraw() только при необходимости. Каждый раз, когда Ваше приложение будет подготовлено к отрисовке, Вы должны запросить View стать недействительным вызовом invalidate(). Это показывает, что нужно перерисовать View, и Android сделает вызов onDraw() (хотя не гарантируется, что обратный вызов будет исполнен немедленно).

Внутри метода onDraw() Вашего компонента View используйте предоставленный Вам Canvas для всех отрисовок, используя многочисленные методы Canvas.draw...() или другие методы draw() класса (Canvas передается в виде аргумента). Как только Ваш onDraw() завершится, фреймворк Android будет использовать Ваш Canvas для отрисовки Bitmap с обработкой на уровне системы.

Примечание: чтобы сделать запрос перерисовки invalidate из другого потока, не из главного потока активности (main Activity thread), Вы должны вызвать postInvalidate().

Для дополнительной информации по расширению класса View прочитайте руководство Building Custom Components.

Для примера приложение см. игру змейка (Snake game), находящуюся в папке примеров из SDK: < your-sdk-directory >/samples/Snake/.

[Рисование на SurfaceView]

SurfaceView является специальным подклассом View, который предоставляет выделенную поверхность рисования в пределах иерархии View. Цель состоит в том, чтобы предоставить эту вторичную поверхность рисования второму потоку, чтобы приложению не требовалось ждать, пока системная иерархия View будет готова к отрисовке. Вместо этого второй поток, который имеет ссылку на SurfaceView, может рисовать в своем Canvas и в своем темпе.

Чтобы начать, нужно создать новый класс, который расширяет (extends) класс SurfaceView. Класс должен реализовать SurfaceHolder.Callback. Этот подкласс является интерфейсом, который уведомит Вас о событиях нижележащей поверхности, таких как создание, изменение или уничтожение. Эти события важны для того, чтобы Вы знали, когда можно начать рисовать, нужно ли внести изменения на основе новых свойств поверхности, и когда нужно остановить рисование и потенциально прибить некоторые задачи. Хорошим решением будет также определить внутри Вашего класса Ваш класс второго потока Thread, который будет выполнять все процедуры рисования на Вашем Canvas.

Вместо того, чтобы напрямую обрабатывать объект поверхности Surface, Вы должны делать это через SurfaceHolder. Когда Ваш SurfaceView проинициализирован, получите SurfaceHolder вызовом getHolder(). Затем Вы должны оповестить SurfaceHolder, что хотите принимать методы обратного вызова SurfaceHolder (из SurfaceHolder.Callback) путем вызова addCallback(). Затем перезадайте каждый из методов SurfaceHolder.Callback внутри Вашего класса SurfaceView.

Чтобы рисовать на Surface Canvas из второго потока, Вы должны передать потоку ваш SurfaceHandler и получить Canvas вызовом lockCanvas(). Теперь Вы можете взять Canvas, предоставленный Вам SurfaceHolder, и сделать на нем все нужные отрисовки. Как только Вы завершили рисовать вместе с Canvas, вызовите unlockCanvasAndPost(), передав при этом Ваш объект Canvas. Теперь Surface перерисуется в соответствии с оставленным Вами Canvas. Каждый раз, когда нужно перерисовать, выполняйте эту последовательность захвата (locking) и освобождения (unlocking) Canvas.

Примечание: на каждом проходе получения Canvas от SurfaceHolder будет возвращено предыдущее состояние Canvas. Чтобы правильно анимировать графику, Вы должны перерисовать всю поверхность. Например, Вы можете очистить предыдущее состояние Canvas путем заливки цветом с drawColor() или установкой фоновой картинки с drawBitmap(). Иначе Вы увидите ранее выполненные отрисовки.

Для примера приложения см. игру Lunar Lander в каталоге примеров из SDK: < your-sdk-directory >/samples/LunarLander/.

[Рисуемые объекты (Drawables)]

Android предоставляет отдельную библиотеку 2D-графики для рисования фигур и картинок. В пакете android.graphics.drawable можно найти общие классы, используемые для рисования в двух измерениях. В этом разделе обсуждаются базовые основы использования объектов Drawable, чтобы рисовать графику, и как использовать связанные подклассы класса Drawable. Для дополнительной информации как делать покадровую анимацию с помощью Drawable см. [2].

Drawable является главной абстракцией для "чего-нибудь, что можно нарисовать". Вы обнаружите, что класс Drawable был расширен для определения различных видов рисуемой графики, включая BitmapDrawable, ShapeDrawable, PictureDrawable, LayerDrawable и некоторых других. Конечно, Вы также можете расширить их для того, чтобы определить собственные Drawable-объекты, которые будут работать уникальным образом.

Есть 3 способа определить и задействовать Drawable:

1. Использовать картинку, сохраненную в ресурсах проекта.
2. Использовать файл XML, который будет задавать свойства Drawable.
3. Использовать традиционные конструкторы класса.

Ниже будут обсуждаться первые две техники (использование конструкторов не представляет собой ничего нового для опытного разработчика).

[Создание графики из ресурсов картинок]

Простой способ добавить графику в приложение - обращаться к файлу картинки, который сохранен в ресурсах проекта. Поддерживаются форматы PNG (рекомендуемый формат), JPG (допустимый формат) и GIF (этот формат использовать не рекомендуется). Эта техника использования картинок предпочтительна для иконок приложения, логотипов, или других растровых графических изображений, которые могут использоваться в игре.

Чтобы использовать ресурс изображения (image resource), просто добавьте файл картинки (файл с расширением .png, .jpg или .gif) в каталог res/drawable/ Вашего проекта. С этого момента Вы можете обращаться к этому изображению из кода Java или из разметки XML. В любом случае доступ осуществляется на основе числового идентификатора ресурса (resource ID), который создается на основе имени файла без расширения. Например, к файлу my_image.png можно обращаться по идентификатору my_image. Подробнее про предоставление ресурсов и получение к ним доступа см. [4, 5].

Примечание: ресурсы изображений, размещенные в res/drawable/, могут быть автоматически оптимизированы сжатием без потерь (lossless image compression) утилитой aapt во время процесса сборки. Например, true-color PNG, не требующий большего количества цветов, чем 256, может быть преобразован в 8-bit PNG с цветовой палитрой. В результате получится картинка того же самого качества, но она займет меньше места в памяти. Поэтому имейте в виду, что бинарные изображения, размещенные в этой директории, во время сборки приложения могут изменить свой размер. Если вместо этого Вы планировали читать картинки в виде потока бит, то преобразуйте их в папку res/raw/, где изображения не будут оптимизированы.

Следующие куски кода демонстрируют, как построить ImageView, использующий картинку их ресурсов drawable, и как добавить её в разметку интерфейса программы (layout).

LinearLayout mLinearLayout;
protected void onCreate(Bundle savedInstanceState)
{
   super.onCreate(savedInstanceState);
// Создание LinearLayout, в котором будет добавлен ImageView. mLinearLayout = new LinearLayout(this);
// Создание ImageView и определение его свойств. ImageView i = new ImageView(this); i.setImageResource(R.drawable.my_image); // Установка границ ImageView в соответствие размерам Drawable: i.setAdjustViewBounds(true); i.setLayoutParams(new Gallery.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
// Добавление ImageView к layout, и установка layout в качестве // содержимого представления. mLinearLayout.addView(i); setContentView(mLinearLayout); }

В других случаях Вы можете обрабатывать Ваш ресурс изображения (image resource) как объект Drawable. Чтобы сделать это, создайте Drawable из ресурса примерно так:

    Resources res = mContext.getResources();
    Drawable myImage = res.getDrawable(R.drawable.my_image);

Примечание: каждый уникальный ресурс в Вашем проекте может поддерживать только одно состояние, независимо от того, сколько разных объектов Вы создадите из него. Например, если Вы создадите два Drawable-объекта из одного и того же ресурса картинки, затем поменяете свойство (такое как alpha) для одного из этих Drawable, то это также повлияет и на другого. Поэтому когда имеете дело с несколькими экземплярами ресурса изображения, то вместо прямого трансформирования Drawable Вы должны выполнить tween animation [см. 3].

Кусок кода XML ниже показывает, как добавить ресурс Drawable к ImageView в XML layout (для забавы с некоторым красным оттенком).

      < ImageView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:tint="#55ff0000"
      android:src="@drawable/my_image"/>

Дополнительную информацию по типам ресурсов и их использованию см. в [3, 4, 5].

[Создание графики из ресурса XML]

Теперь Вы познакомились с принципами оформления интерфейса пользователя программы Android. Таким образом, Вы понимаете мощь и гибкость, свойственную определению объектов в XML. Эта философия переносится и на классы View, как и на Drawable. Если есть некий объект Drawable, который Вы хотите создать, то начальные значения свойств объекта можно задать не только в коде приложения через переменные, но и в путем определения Drawable в XML, что является хорошим решением. Даже если Вы ожидаете, что Ваш Drawable изменит свои свойства в результате взаимодействия с пользователем в приложении, Вы должны рассмотреть определение объекта в XML, поскольку в дальнейшем Вы все равно можете менять его свойства после создания экземпляра объекта.

Когда Вы определили Drawable в XML, сохраните его файл в каталоге res/drawable/ Вашего проекта. Затем запросите и создайте объект вызовом Resources.getDrawable(), передав ему resource ID от Вашего файла XML (пример см. ниже).

Любой подкласс Drawable, который поддерживает метод inflate(), может быть задан в XML и его экземпляр может быть получен в приложении. Каждый Drawable, который поддерживает XML inflation, утилизирует специальные атрибуты XML, которые помогают задать свойства объекта - см. документацию на класс, чтобы получить информацию по этим атрибутам. Также см. документацию на каждый подкласс Drawable для получения информации, как его определить в XML.

Вот пример файла XML, который определяет TransitionDrawable:

< transition xmlns:android="http://schemas.android.com/apk/res/android" >
< item android:drawable="@drawable/image_expand" >
< item android:drawable="@drawable/image_collapse" >
< /transition >

С этим XML, сохраненным в файл res/drawable/expand_collapse.xml, следующий код создает экземпляр TransitionDrawable и устанавливает его как содержимое ImageView:

Resources res = mContext.getResources();
TransitionDrawable transition = 
   (TransitionDrawable)res.getDrawable(R.drawable.expand_collapse);
ImageView image = (ImageView) findViewById(R.id.toggle_image);
image.setImageDrawable(transition);

Затем этот переход (transition) может быть запущен вперед (на одну секунду):

transition.startTransition(1000);

Дополнительную информацию по поддерживаемым атрибутом вышеперечисленных классов Drawable см. в их документации.

[Рисуемая фигура (Shape Drawable)]

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

ShapeDrawable является расширением от Drawable, так что его можно использовать везде, где ожидается Drawable — возможно для заднего фона View, что можно сделать вызовом setBackgroundDrawable(). Конечно, Вы также можете рисовать фигуры и использовать их как custom View, чтобы добавить в разметку интерфейса приложения (layout) так, как пожелаете. Поскольку ShapeDrawable имеет собственный метод draw(), Вы можете создать подкласс View, который рисует ShapeDrawable в методе View.onDraw(). Здесь приведен базовый пример расширения класса View, который рисует ShapeDrawable как View:

public class CustomDrawableView extends View
{
   private ShapeDrawable mDrawable;
   public CustomDrawableView(Context context)
   {
      super(context);
int x = 10; int y = 10; int width = 300; int height = 50;
mDrawable = new ShapeDrawable(new OvalShape()); mDrawable.getPaint().setColor(0xff74AC23); mDrawable.setBounds(x, y, x + width, y + height); }
protected void onDraw(Canvas canvas) { mDrawable.draw(canvas); } }

В конструкторе ShapeDrawable задан как OvalShape. Затем устанавливается цвет и границы фигуры. Если Вы не установите границы, то фигура не будет нарисована, и если не установите цвет, то по по умолчанию фигура будет черной.

С заданным custom View он может быть отрисован любым способом, который Вам нравится. Вышеприведенный пример фигуры может быть отрисован программно в Activity:

CustomDrawableView mCustomDrawableView;
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mCustomDrawableView = new CustomDrawableView(this);
setContentView(mCustomDrawableView); }

Если Вы хотите рисовать этот пользовательский рисунок из XML вместо того, чтобы это делать в из Activity, то класс CustomDrawable должен перезадать конструктор View(Context, AttributeSet), который вызывается при создании объекта, заданного из XML. Затем добавьте элемент CustomDrawable в XML, примерно так:

      < com.example.shapedrawable.CustomDrawableView
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      />

Класс ShapeDrawable (как и многие другие типы Drawable в пакете the android.graphics.drawable) позволяют Вам задать различные свойства рисуемых фигур через публичные методы. Вы можете захотеть подстроить некоторые свойства, включая alpha transparency (прозрачность), color filter (фильтрация цвета), dither (дизеринг), opacity (непрозрачность) и цвет.

Вы также можете с использование XML задать примитивные отрисованные фигуры. Для дополнительной информации см. [3], описание типа ресурса Shape Drawable.

[Nine-patch]

Графика NinePatchDrawable является растягиваемой растровой картинкой (bitmap image), размер которой Android автоматически изменит, чтобы она соответствовала содержимому View, в котором она размещена в качестве фона (background). Пример использования NinePatch - фон для стандартных кнопок (button) Android — кнопки должны растягиваться, чтобы разместить в себе строки различной длины. NinePatch drawable - это стандартная картинка PNG, которая включает в себя дополнительный бордюр шириной в 1 пиксел. NinePatch drawable должен быть сохранен в файл с расширением .9.png, в каталог res/drawable/ Вашего проекта.

Бордюр используется для задания растягиваемой и статической областей картинки. Вы показываете растягиваемую секцию путем рисования одной (или большего количества) 1-пиксельной по толщине линии (или линий) в левой и верхней части бордюра (другие пикселы бордюра должны быть полностью прозрачными или белыми). Вы можете иметь столько растягиваемых секций, сколько захотите: их относительные размеры останутся теми же самыми, так что самые большие секции так и останутся самыми большими.

Вы также можете определить опциональную drawable-секцию изображения (обычно для дополнительных строк, padding lines) путем рисования линии справа и внизу. Если объект View установит NinePatch в качестве фона (background) и затем укажет свой текст, то он View сам растянется так, чтобы текст уместился в обозначенные линиями области справа и внизу (если эти линии есть). Если padding lines не заданы, то Android будет использовать линии слева и вверху для определения рисуемой области.

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

Вот пример файла NinePatch для определения кнопки:

Android-ninepatch raw

Этот NinePatch задает одну растягиваемую область с левой и верхней линиями, и рисуемую область нижней и правой линиями. На верхней картинке серым пунктиром показаны регионы картинки, которые будут реплицированы при растягивании изображения. Розовый прямоугольник на нижней картинке показывает область, в которой разрешено размещение содержимого View. Если содержимое не поместится в этот регион, то изображение будет растянуто, пока содержимое не поместится.

Инструмент Draw 9-patch tool предоставляет очень удобный путь для создания изображений NinePatch, используя редактор WYSIWYG. Он даже выдает предупреждения, если заданный Вами регион для растягиваемой области создает риск для создания артефактов в результате реплицирования пикселов.

Здесь приведен пример layout XML, который демонстрирует, как добавить картинку NinePatch для набора кнопок (картинка NinePatch сохранена в файл res/drawable/my_button_background.9.png).

< Button id="@+id/tiny"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerInParent="true"
        android:text="Tiny"
        android:textSize="8sp"
        android:background="@drawable/my_button_background"/>
< Button id="@+id/big"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerInParent="true"
        android:text="Biiiiiiig text!"
        android:textSize="30sp"
        android:background="@drawable/my_button_background"/>

Обратите внимание, что ширина width и высота height установлены в значение "wrap_content" для того, чтобы кнопка по размеру подходила содержащемуся в ней тексту. Ниже показано, как выглядят две кнопки, которые отрисованы из XML и картинки NinePatch, показанной выше. Обратите внимание, как ширина и высота кнопки меняются в зависимости от текста, и как растягивается фоновая картинка, чтобы разместить текст.

Android-ninepatch examples

[Ссылки]

1. Canvas and Drawables site:developer.android.com.
2. Drawable Animation в приложении Android.
3. Типы ресурсов приложения Android.
4. Предоставление ресурсов для приложения Android.
5. Получение доступа к ресурсам приложения Android.