Android — Как сделать живые обои

July 22, 2011

Вольный перевод с английского статьи из блога Кирилла Грушникова

Живые обои – это анимированные интерактивные обои для главного экрана Android. По своей сути они похожи на обычное приложение под Android и в них могут быть использованы те же самые функции API платформы. Сегодня мы займемся написанием своих собственных живых обоев, которые будут выглядеть так:

Процесс создания собственных живых обоев состоит из:

1) создания XML-файлов, содержащих описание характеристик обоев

2) создания сервиса, расширяющего базовый класс WallpaperService для создания анимации и обработки сообщений

Создайте новый проект «com.sample.livewallpaper.bokehrainbow» без активити.

Как и с любым другим приложением под Android, нам нужно будет создать несколько XML файлов, которые дадут понять Android-маркету/устройству, можно ли наше приложение запустить на определенном устройстве. Первым делом нам нужно модифицировать файл AndroidManifest.xml :

На что обратить внимание:

Тэг service дает платформе понять, что ваш сервис является живыми обоями

Тэг uses-sdk показывает минимальную требуемую версию Android

Тэг uses-feature показывает, что мы будем запускать живые обои

Атрибут android:resource тэга meta-data из описания нашего сервиса указывает на файл bokeh.xml. расположенный в папке res/xml. Вот содержимое этого файла:

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

Строка названия задается атрибутомandroid:label в теге service файла AndroidManifest.xml

Описание, соответственно, определено атрибутом android:description в файле bokeh.xml

Изображение предпросмотра задано там же атрибутом android:thumbnail

Теперь содержимое файла текстовых строк res/values/strings.xml

Изображение для предпросмотра добавляем в папку res/drawable.

Как работают живые обои

Программирование живых обоев состоит из трех основных вещей:

1) События жизненного цикла – создание, уничтожение, движение, показ, скрытие, прикосновение

2) Анимация

3) Отрисовка

Атрибут нашего сервиса android:name должен указывать на класс, унаследованный от класса android.service.wallpaper.WallpaperService. Это базовый класс для всех живых обоев в системе. Класс этот абстрактный и нам нужно будет написать свой метод onCreateEngine() для возврата собственного движка живых обоев. Движок отвечает за обработку всех перечисленных выше функций – событий жизненного цикла, анимацию и отрисовку.

Внутренний класс android.service.wallpaper.WallpaperService.Engine имеет несколько методов, которые можно переопределить для обработки различных событий, происходящих с нашими обоями. Давайте вкратце рассмотрим основные из них:

1) onCreate() – вызывается, когда движок инициализируется. В этот момент поверхность для рисования еще не создана

2) onDestroy() – вызывается, когда движок уничтожается. После вызова этого метода, к движку больше нельзя обратиться

3) onSurfaceCreated() – вызывается, когда создается поверхность для рисования

4) onSurfaceChanged() – вызывается, когда произошли структурные изменения поверхности для рисования (например, изменился ее размер или формат)

5) onSurfaceDestroyed() – вызывается, когда поверхность для рисования уничтожается

6) onVisibilityChanged() – вызывается, когда живые обои становятся видимыми или скрываются. Когда обои становятся скрытыми, необходимо останавливать анимацию и ничего не рисовать, чтобы сэкономить ресурсы процессора

7) onOffsetsChanged() – вызывается, когда мы изменяем позицию обоев, проводя пальцем влево-вправо на главном экране. Метод можно использовать для создания эффекта “параллакса”, когда обои двигаются вместе с главным экраном

8) onTouchEvent() – вызывается, когда пользователь дотрагивается до экрана для взаимодействия с окном, показывающим обои

9) onCommand() – вызывается для обработки команд

Живые обои, как ожидается, будут содержать динамический контент (поэтому они и “живые”). Базовые классы (WallpaperService и WallpaperService.Engine) не предоставляют никаких специальных средств для создания анимации. В наши обязанности входит создание цикла анимации, обработка событий в цикле при приостановке/возобновлении анимации, а также обработка каждого ее шага. Одним из способов обработки цикла анимации является использование классов android.os.Handler иjava.lang.Runnable .

Давайте посмотрим на пример реализации цикла анимации для живых обоев, который будет обрабатывать соответствующие события:

Итак, у нас есть абстрактное расширение класса WallpaperService, которое обрабатывает главные события жизненного цикла живых обоев:

1) Объект mIteration типа Runnable будет вызываться на каждую итерацию для отрисовки одного кадра и задания времени следующей итерации.

2) Объект mHandler. обработчик очереди сообщений, будет обрабатывать сообщения из нашего главного потока. Мы будем использовать его, чтобы ждать следующей итерации и запускать mIteration

3) ФлагmVisible хранит признак видимости наших обоев

4) Когда анимацию нужно остановить – если в методы onDestroy(). onSurfaceDestroyed() и onVisibilityChanged() передается false – мы даем обработчику знать, что он не должен вызывать методы-коллбэки. Это эффективным образом останавливает наш цикл анимации, удаляя запросы на анимацию/перерисовку.

5) Когда обои становятся видимыми – если в метод onVisibilityChanged() передается true, мы запускаем одиночную итерацию и отрисовываем один кадр.

6) Вызов метода iteration() одновременно задает время следующей итерации – через 40 миллисекунд.

7) Как только происходят изменения поверхности – в методах onSurfaceChanged() и onOffsetsChanged() мы сразу отвечаем на эти изменения, перерисовывая обои и переопределяя время следующей итерации.

Наконец, настало время поговорить о рисовании. Метод drawFrame(). определенный в нашем абстрактном классе, вызывается на каждый шаг анимации. Вот как он будет выглядеть:

Здесь мы получаем объект типа Canvas из SurfaceHolder, выполняем само рисование, затем освобождаем canvas и уведомляем платформу о том, что она должна нарисовать содержимое canvas на экране.

Работа с деталями

Теперь, когда наш

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

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

Метод tick() вызывается на каждую итерацию анимации. Этот метод обновляет значение альфа-канала для круга, его радиус и позицию центра для создания исчезающего, растущего и движущегося круга – который все равно сохраняет свой цвет.

Теперь давайте посмотрим на реализацию самих обоев. Мы начнем с реализации метода onCreateEngine, который будет возвращать наш собственный движок(обратите внимание, что и обои, и их движок унаследованы от наших базовых классов):

Как было указано выше, главный экран можно сдвигать в стороны (влево или вправо). Живые обои могут отвечать на эти события и создавать эффект параллакса – обои будут сдвигаться вместе с экраном, но на меньшее расстояние. Конкретно в нашем случае живые обои в два раза шире ширины экрана.Так как Nexus One, устройство на котором тестировался этот пример, позволяет сдвигаться на два экрана в каждую сторону, это создает интересный эффект того, что обои двигаются медленнее, чем содержимое главного экрана, и кажется, что они располагаются дальше от пользователя, чем остальная графика на экране.

Информация о текущем размере и смещении обоев хранится в следующих полях:

int offsetX;

int offsetY;

int height;

int width;

int visibleWidth;

Они определяются в следующих событиях жизненного цикла нашего движка:

Обратите внимание, как в методе onSurfaceChanged мы вычисляем общую ширину обоев на основании их состояния предварительного просмотра (обои нельзя скроллить в этом режиме). Сохраненные в методе onOffsetsChanged значения смещений используются при отрисовке – см. вызовы метода drawCircle ниже:

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

Круги создаются в двух местах. Во-первых, мы создаем 20 кругов в методе onSurfaceChanged, показанном выше. Кроме того, мы создаем круг каждый раз, когда пользователь дотрагивается до экрана. Это происходит в методе onCommand:

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

Теперь давайте посмотрим, что происходит на каждую итерацию анимации. Мы переопределяем метод iteration() нашего супер класса и:

1) Оповещаем каждый круг о том, что выполнена еще одна итерация

2) Удаляем все круги, жизненный цикл которых завершен (чтобы наша модель не росла до бесконечности)

3) Вызываем метод суперкласса для задания времени следующей итерации

Чтобы завершить код, давайте посмотрим, как мы создаем один круг:

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

Упаковка и тестирование

Если вы разрабатываете живые обои в Eclipse, при запуске плагин ADT создаст .apk файл для вас. Он будет располагаться в папке bin вашего проекта. Вы можете использовать Android Debug Bridge – adb – для закачивания ваших обоев на устройство и тестирования. Когда приложение будет установлено, вызовите меню выбора обоев на главном экране, наши обои должны появиться в списке. Теперь вы можете использовать обои на вашем устройства.

Как только вы будете довольны вашими обоями, вы сможете подписать их при помощи сертификата и опубликовать в Google Play. Поздравляю! Вы только что создали свои первые живые обои.

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

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

Одним из вариантов является использования предварительно созданных растровых изображений (bitmaps) и какой-либо реализации двойной буферизации для тех из них, которые приходятся на невидимые зоны экрана. Однако, использование большого количества таких изображений может привести к ошибкам OutOfMemoryErrors – в зависимости от того, сколько памяти доступно конкретно для вашего процесса и вообще в системе. Вы также можете поиграть с различными API Canvas и посмотреть, что будет быстрее конкретно для вашей анимации. Еще одним вариантом является использование OpenGL – если у вас есть желание его изучать.

Среди статей для разработчиков на сайте Android есть материалы, описывающие способы улучшения производительности, избегания утечек памяти и отслеживания использования памяти. В частности, рекомендуется использовать повторно как можно больше экземпляров – во избежание сборки мусора для большого количества маленьких объектов – как это происходит с объектами Paint в нашем коде.

Исходный код обоев доступен на GitHub (но у меня скачанный оттуда проект не запустился), поэтому есть еще прямая ссылка на архив с проектом. Обои из примера также можно скачать в Google Play – ищите по “Bokeh Rainbow”.

Источник: vlad8.com

Категория: Выбор и покупка

Похожие статьи: