Пишем Live Wallpapers (живые обои) для Android


Начиная с версии 2.1 в устройствах на базе системы Android появилась возможность использования Live Wallpapers (живых  обоев). Это позволяет установить  в качестве фона рабочего стола не только статичную картинку, но и произвольную программу создающую любое динамичное изображение или даже игру. Написанию простейшей заставки и посвящена эта статья. Предполагается, что у читателя уже установлена среда Eclipse и Android SDK и что он как минимум умеет импортировать и запускать готовые примеры, доступные для скачивания изархива.

Для начала создадим новый проект, с параметрами указанными на рисунке ниже:



После нажатия кнопки Finish мы получим стандартный шаблон проекта с одной созданной формой Activity. Так как Live Wallpapers появились только в 7 версии SDK, мы указали минимально допустимую версию системы, на которой программа может запуститься. Шаблон можно протестировать, запустив результат в эмуляторе и убедившись что надпись на форме выводится успешно ;).

Перейдем к написанию заставки. Для этого нам понадобится создать новый класс, унаследованный от WallpaperService. Назовем его StarFieldsShow, внутри класса необходимо реализовать метод onCreateEngine() и класс StarFieldsEngine,  наследующий Engine. Именно внутри  StarFieldsEngine проводится вся работа по рисованию заставки. Большая часть класса - это необходимая оболочка для взаимодействия заставки с системой, здесь обрабатываются  действия запуска и остановки анимации, менеджмент ресурсов и определение видимости. Для нас интересными являются всего несколько основных методов. При смене размера экрана или его повороте вызывается метод onSurfaceChanged, внутри которого доступны  параметры текущего экрана. Размер экрана сохраняется в глобальных переменных screen_width, screen_height класса StarFieldsEngine и служат для последующих расчетов динамичной графики, в этом методе мы выполняем инициализацию данных связанных с размером экрана.

@Override
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height){
super.onSurfaceChanged(holder, format, width, height);
// Определяем размер текущей области для рисования
screen_width= width;
screen_height= height;
drawFrame();
 

Рисование кадра выполняется методом drawFrame(), который является оболочкой для метода рисования drawScreen(c) и реализует задержку между кадрами. Все действия по рисованию на Canvas выполняет метод drawScreen(c), в который передается контекст рисования, полученный через holder.lockCanvas(). Частота вывода кадров задается в mHandler.postDelayed в миллисекундах, по возможности устройство будет пытаться выводить кадры с заданной частотой. В примере принята скорость 30 кадров в секунду для большей плавности. 

// Перерисовка экрана
void drawFrame(){
final SurfaceHolder holder= getSurfaceHolder();
Canvas c=null;
try{
c= holder.lockCanvas();
if(c!=null) drawScreen(c);
} finally{
if(c!=null) holder.unlockCanvasAndPost(c);
}
mHandler.removeCallbacks(draw_frame);
// Добавляем задержку между кадрами анимации
if(mVisible) mHandler.postDelayed(draw_frame,1000/30);
}
 

Реализовав класс StarFieldsShow, мы получили шаблон для заставки, но система пока не подозревает о ее существовании. Теперь необходимо добавить описание нового сервиса в AndroidManifest.xml. Проще всего это сделать открыв AndroidManifest.xml и добавив туда следующий текст:

 <uses-feature android:name="android.software.live_wallpaper" /> 
<service
android:label="@string/wallp_name"
android:name="StarFieldsShow"
android:permission="android.permission.BIND_WALLPAPER">
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService" />
</intent-filter>
<meta-data android:name="android.service.wallpaper" android:resource="@xml/star_fields" />
</service> 


Атрибут android:name должен иметь значение, совпадающее с именем класса, реализующего заставку, а android:permission разрешает устанавливать класс как заставку. Кроме корректировки манифеста, в ресурсах проекта необходимо создать папку xml, внутри которой создаем файл star_fields.xml, в будущем он нам понадобится для возможности настройки заставки пользователем. В ресурсы строк добавляем строку wallp_name с именем обоев в общем списке.

Запустив проект мы не обнаружим видимых изменений в отображаемой форме но, зайдя в настройки экрана и выбрав список Live Wallpapers, мы обнаружим там обои с именем Star Fields. Цель достигнута - перед нами готовый шаблон для написания живых обоев. (Сегодня совсем не проблема найти графику для создания живых обоев. Есть Яндекс Картинки, есть куча сайтов, которые предлагаютбесплатные обои.) Вархиве этот вариант находится в папке Template. Не смотря на то, что шаблон работает - он ничего не  рисует на экране. Добавим функциональности.
    
В качестве эффекта реализуем простейшее движущееся звездное небо. Для этого нам понадобится структура данных о каждой точке - координата на экране и скорость по двум осям. Напишем класс StarItem, который будет не только содержать все необходимые данные, но и реализует два метода - вывод точки на экран и обработка ее перемещения. Приводить исходный текст не буду из-за его банальности, смотрим его текст вархиве. Теперь осталось добавить в основной класс создание массива таких точек, заполнение его координатами и вывод на экран. Добавим в класс StarFieldsEngine две статические переменные с параметрами заставки

// Количество точек
final static int STAR_MAX=128;
// Максимальная скорость точки в пикселях
final static int SPEED_MAX=5;

и напишем метод initStars(), инициализирующий массив точек со случайными координатами и скоростью от 0 до SPEED_MAX пикселей за кадр по оси y. Это даст эффект перемещения влево с иллюзией глубины, как в демо конца 80-х годов прошлого века ;)

 
// Инициализация массива точек
void initStars(){
stars=new StarItem[STAR_MAX];
for(int i=0; i<STAR_MAX; i){
stars[i]=new
StarItem(rnd.nextInt(screen_width), rnd.nextInt(screen_height),
SPEED_MAX*rnd.nextFloat(),0);
}
}

Осталось добавить инициализацию внутрь метода onSurfaceChanged, что позволит нам пересоздавать корректный набор данных в случае поворота экрана и добавить вывод на экран. Для этого переписываем метод drawScreen(Canvas c), вписав в его начало заливку экрана черным цветом, а потом вывод всех точек в цикле. В этом же цикле будем обрабатывать перемещение методом Move(), параметром которого служит размер экрана. Сохранять и восстанавливать контекст экрана парой методов c.save() - c.restore(), в принципе не обязательно для нашей задачи, но может пригодиться для заставок трансформирующих контекст.

// Здесь выполняем всю работу по рисованию
void drawScreen(Canvas c){
c.save();
// Очищаем экран
Rect clear=new Rect(0,0,screen_width,screen_height);
Paint black=new Paint();
black.setColor(0xff000000);
c.drawRect(clear, black);
// Выводим и перемещаем звезды
for(int i=0; i<STAR_MAX; i){
stars[i].Render(c);
stars[i].Move(screen_width, screen_height);
}
c.restore();
}

Запустив проект, зайдя в настройки экрана и выбрав обои с именем Star Fields мы получим достаточно симпатичное окно в космос. Простейший вариант живых обоев готов к распространению. Этот вариант проекта вы найдете в папке Alfa архива. Недостатки этого варианта очевидны. У пользователя нет настроек количества звезд, поэтому на разных разрешениях экрана будет разная плотность заполнения. Конечно, можно ввести коэффициент плотности на сантиметр экрана - но гораздо удобнее возложить выбор количества звезд на пользователя. Для этого необходимо добавить кнопку настроек.

Сохранять и восстанавливать настройки можно несколькими способами - написав собственную форму и сохраняя данные в файловой системе или используя стандартную форму предпочтений PreferenceActivity. Первый вариант проще для понимания, но добавляет несколько проблем, таких как вопрос где хранить файл с настройками и типизация настроек. Использование PreferenceActivity сложнее, зато обеспечивает стандартный для Android механизм хранения, изменения и восстановления любых пользовательских настроек в приложении, его и реализуем.

Для начала создадим разметку нашего окна предпочтений, для этого в папке ресурсов xml создадим файл star_settings.xml следующего содержания

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/star_settings" android:key="star_settings">
<EditTextPreference
android:defaultValue="128"
android:enabled="true"
android:selectable="true"
android:title="Star counts"
android:key="star_max">
</EditTextPreference>
</PreferenceScreen>


Среда Eclipse позволяет работать с окнами свойств в режиме редактора, но для одного управляющего элемента проще все написать руками. Внутри разметки PreferenceScreen размещен только один элемент - это поле ввода текста EditTextPreference в котором мы задаем количество звезд. Здесь два основных атрибута android:defaultValue задает значение параметра по умолчанию, а android:key определяет текстовую метку, по которой к этому параметру можно получить доступ. В строковые ресурсы необходимо добавить текст заголовка с именем star_settings. В класс StarFieldsShow необходимо добавить строковую константу SHARED_PREFS_NAME которая будет содержать имя набора  настроек, по этому имени мы будем к ним обращаться из класса.

Теперь нам нужен класс для использования нашей разметки. Создадим класс StarSettings наследующий свойства PreferenceActivity, в котором перезагружаем метод onCreate(Bundle icicle)

@Override
protected void onCreate(Bundle icicle){
super.onCreate(icicle);
getPreferenceManager().setSharedPreferencesName(StarFieldsShow.SHARED_PREFS_NAME);
addPreferencesFromResource(R.xml.star_settings);
getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}

Важным здесь является привязка набора настроек к ранее заданной константе методом setSharedPreferencesName и  установка ресурса формы с настройками addPreferencesFromResource из файла star_settings. При необходимости обрабатывать корректность вводимых данных используется метод onSharedPreferenceChanged, который вызывается при каждом изменении настроек. Так как наша форма настроек будет вызываться на экран, добавляем информацию о ней в AndroidManifest.xml,  не забыв добавить ссылку на стиль настройки живых обоев


 <activity
android:label="@string/set_name"
android:theme="@android:style/Theme.Light.WallpaperSettings"
android:exported="true" android:name="StarSettings">
</activity>

Для вызова окна настроек нам осталось сделать последний шаг - в файле star_fields.xml добавляем ссылку на класс StarSettings , содержащий настройки.

android:settingsActivity="com.shadwork.livew.start_fields.StarSettings"

После сборки этого варианта обоев и установки их на устройство, если выбрать из списка Star Fields, на фоне звездного поля отобразится уже две кнопки - установка обоев и настройка. Выбрав настройку, мы увидим один пункт с именем Star counts, внутри которого  находится поле ввода со значением по умолчанию. Если это значение изменить, выйти, а потом войти в настройки повторно, значение сохранится. Но ведь это еще не все, введенный параметр не влияет на количество звезд.

Для работы с сохраненными параметрами, необходимо включить в класс StarFieldsEngine обработку события изменения настроек. Для начала добавим переменную mPrefs, которая послужит ссылкой на класс настроек, потом в определение класса добавим включение метода SharedPreferences.OnSharedPreferenceChangeListener,  и определим его в теле класса

public void onSharedPreferenceChanged(SharedPreferences prefs, Stringkey){
String counter= prefs.getString("star_max","128");
try{
STAR_MAX= Integer.parseInt(counter);
}catch(NumberFormatException ex){};
initStars();
}


Теперь откорректируем тип переменной STAR_MAX (раньше она была константой, убираем квалификатор static), добавим в конструктор класса StarFieldsEngine инициализацию ссылки на объект свойств и установим обработчик события на класс StarFieldsEngine

 mPrefs= StarFieldsShow.this.getSharedPreferences(SHARED_PREFS_NAME,0);
mPrefs.registerOnSharedPreferenceChangeListener(this);
onSharedPreferenceChanged(mPrefs,null);

Вся работа с параметрами осуществляется внутри метода onSharedPreferenceChanged, для начала мы получаем текст введенный пользователем в поле количество, а потом заново инициализируем массив звезд вызвав метод initStars(). Собрав проект, мы получим работающий вариант настроек. Не хватает только обработки исключительных ситуаций, на случай, если пользователь вместо цифр начнет вводить буквы или захочет указать отрицательное количество звезд. В таком случае мы просто заменим число звезд на значение по умолчанию. Реализуем простейшую проверку в методе onSharedPreferenceChanged класса StarFieldsShow. Для этого добавим проверку на выход количества точек из заданных границ и попытку ввести буквы вместо цифр. Корректное значение необходимо вернуть в окно свойств, для этого значение преобразовывается к типу Editor, меняется и возвращает значение параметра после вызова метода commit().

try{
STAR_MAX= Integer.parseInt(counter);
if(STAR_MAX<1| STAR_MAX>16384)thrownew NumberFormatException();
}catch(NumberFormatException ex){
Editorcurrent= prefs.edit();
current.putString("star_max", Integer.toString(STAR_DEFAULT));
current.commit();
};

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

АвторShadowsshot



Наши соцсети

Подписаться Facebook Подписаться Вконтакте Подписаться Twitter Подписаться Google Подписаться Telegram

Популярное

Ссылки

Новости [1] [2] [3]... Android/ iOS/ J2ME[1] [2] [3]) Android / Архив

Рейтинг@Mail.ru Яндекс.Метрика
MobiLab.ru © 2005-2018
При использовании материалов сайта ссылка на www.mobilab.ru обязательна